Android(2)--知识点

223 阅读7分钟

1. View的绘制流程

View的绘制流程始于ViewRootImplperformTraversals方法,经过measure、layout、draw三个过程完成绘制。

  • measure过程测量View的宽高
  • layout过程确定View在父容器的位置
  • draw过程将View绘制在屏幕上

performTraversals方法会依次调用performMeasure、performLayout、performDraw三个方法。这三个方法会对顶级View(DecorView)进行measure、layout、draw

performMeasure方法为例,该方法会调用顶级View(DecorView)的measure方法,measure方法又会调用onMeasure方法对所有子View依次进行measure,子View又会重复父容器这样的measure过程,如此遍历整个view树进行measure。

layout、draw过程与measure流程类似。

MeasureSpec

MeasureSpec父容器子View的dimension共同作用得到的对子View施加的约束。 子 View 的 MeasureSpec 是由子 View 的 dimension 和父 View 的 MeasureSpec 共同计算得来的。

  • MeasureSpec = MODE + SIZE

  • MODE:约束类型

  • SIZE:

虽然 MeasureSpec 中已经包含了尺寸约束信息,但是子 View 仍然需要在 onMeasure 中进一步确定子 View 具体应该有多大.

大牛博客

大牛博客

2. 事件分发与拦截

事件分发机制是指对一次屏幕触摸事件的响应过程,从触摸行为开始,到触摸行为结束,将点击事件传递到某个具体的View以及处理的整个过程。

主要方法

  • dispatchTouchEvent 分发事件
  • onInterceptTouchEbent 拦截事件,ViewGroup中才有
  • onTouchEvent 消费事件
  • requestDisallowInterceptTouchEvent(true|false) 父View是否拦截事件,true不拦截,false拦截

主要流程

  1. 当触摸事件发生时,始于ViewRootImpl,经过DecorViewActivity,Activity通过PhoneWindow再传递给DecorView
  2. 随后事件传递给DecorViewdispatchTouchEnvent方法处理;
  3. dispatchTouchEnvent方法会通过onInterceptTouchEvent方法判断事件是否拦截,拦截即交给自己的onTouchEvent方法处理,不拦截就传递给子View的dispatchTouchEnvent方法处理;
  4. 子View是ViewGroup,重复以上的操作,不是ViewGroup,交由自己的onTouchEvent处理。

滑动冲突

场景

  1. 滑动方向不同,例如ScrollView里面嵌套ViewPager
  2. 滑动方向相同,例如ViewPager嵌套ViewPager
  3. 两种叠加,多层嵌套

解决方案

  • 原因

父View和子View都需要响应触摸事件,但一个触摸事件 同一时刻只能被某一个View或者ViewGroup拦截消费,因此产生了滑动冲突。

  • 思路

我们就只需要决定在某个时刻由这个View拦截事件,另外的某个时刻由另外一个View拦截事件,就可以解决滑动冲突。

  • 方法
  1. 外部解决方法 重写父View的onInterceptTouchEvent方法,在父View需要消费事件的时候拦截

  2. 内部拦截方法 重写自子View的dispatchTouchEnvent方法,在ACTION_DOWN事件调用requestDisallowInterceptTouchEvent(true)方法,请求父View不要拦截事件,子View在随后的ACTION_MOVE事件中,判断是否消费事件,不消费即调用requestDisallowInterceptTouchEvent(false)通知父View可以拦截事件。

3. Handler

Handler机制是Android的消息机制,其本质可以简单概括一个线程开启循环模式持续监听并依次处理其消息队列中的消息,其他线程有消息需要该线程处理,就把消息添加到该线程的消息队列,如果现在队列为空,就阻塞

主要有Handler,Looper, Message,和MessageQueue四个类完成工作。

Message是消息实体,将需要处理的消息或者任务,封装成一个Message对象,交由Handler对象的sendMessage方法,添加到目标线程的Looper的MessageQueue对象中,Looper的loop方法会持续遍历MessageQueue中的Message,取出并交给Message对象中target变量引用的Handler对象的handleMessage方法处理。

内存泄漏

原因

Activity中使用匿名内部类方式使用使用Handler有可能引起内存泄露问题。匿名内部类隐式持有Activity的实例,如果Handler往主线程消息队列添加了一个延时Message,消息处理结束之前,Message中一直持有Handler引用,Handler又隐式持有Activity引用,所有Activity无法回收。

Handler mHandler = new Handler(){
       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
       }
};

解决方案

  1. 声明静态内部类,需要引用Activity时使用弱引用
  2. Activity退出时,调用handler.removeMessage等方法移除消息

阻塞方式

nativePollOnce方法中使用eventfd/epoll机制机制。 在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。和Looper监听MessageQueue,Handler添加message是比较类似的。

大牛博客

大牛博客

4. App启动流程

相关进程服务介绍

  • init进程 Android系统启动后,首先启动的linux根进程init进程,然后init进程启动Zygote进程
  • Zygote进程 所有Android进程的父进程,包括SystemServer进程
  • SystemServer进程 系统服务进程,负责系统事务,最重要的是启动了ActivityManagerServicePackageManagerServiceWindowManagerServicebinder线程池
  • ActivityManagerService 主要负责系统中四大组件的启动、切换、调度以及应用进程的管理和调度,启动进程都会通过Binder通信机制传递给AMS,再给Zygote进程处理
  • PackageManagerService 主要负责应用包的一些操作,比如安装、卸载、解析AndroidManifest.xml文件,扫描文件信息等
  • WindowManagerService 主要负责窗口相关的一服务,比如窗口的启动、添加、删除等
  • Launcher 桌面应用,属于应用,也有自己的Activity,开机默认启动,通过设置Intent.CATEGORY_HOME的Category隐式启动

启动流程

  1. Launcher被调用点击事件,转到Instrumentation类的startActivity方法;
  2. Instrumentation通过跨进程通信告诉AMS要启动应用的需求;
  3. AMS反馈Launcher,让Launcher进入Paused状态;
  4. Launcher进入Paused状态,AMS转到ZygoteProcess类,并通过socketZygote通信,告知Zygote需要新建进程;
  5. Zygote fork子进程,并调用ActivityThreadmain方法,也就是app的入口;
  6. ActivityThreadmain方法新建了ActivityThread实例,并新建了Looper实例,开始loop循环;
  7. 同时ActivityThread也告知AMS,进程创建完毕,开始创建ApplicationProvider,并调用ApplicaitonattachonCreate方法;
  8. 最后就是创建上下文,通过类加载器加载Activity,调用ActivityonCreate方法。

启动优化

  • Application的attach方法,MultiDexApplication会在方法里面会去执行MultiDex逻辑。所以这里可以进行MultiDex优化,比如今日头条方案就是单独启动一个进程的activity去加载MultiDex。
  • Application的onCreate方法,大量三方库的初始化都在这里进行,所以我们可以开启线程池,懒加载等等。把每个启动任务进行区分,哪些可以子线程运行,哪些有先后顺序。
  • Activity的onCreate方法,同样进行线程处理,懒加载。或者预创建Activity,提前类加载等等。

为什么fork的时候不用Binder而用socket了呢?

主要是因为fork不允许存在多线程,Binder通讯是多线程。fork不允许存在多线程主要是防止死锁。

大牛博客

大牛博客

5. 为什么Looper的死循环不会卡死?(虽然有点弱智,但是真有人问)

线程是一段可执行代码,执行完成,线程的生命周期就结束了,线程推出。但是我们App的主线程,我们肯定不希望他这么快就退出,最简单的做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。