面试准备(安卓部分)

四大组件

Activity,Broadcast Receiver,Service以及Content Provider。

Fragment也写到这里来吧

Activity

官方给出的生命周期图片,对照了Activity和Fragment的生命周期

四个状态

活动状态(Running/Active)、暂停状态(Paused)、停止状态(Stopped)、销毁状态(Killed)

四个启动模式

Standard

SingleTask

SingleTop

SingleInstance

Flags标识

FLAG_ACTIVITY_NEW_TASK 跟在AndroidManifest设置launchMode为SingleTask一样效果,但是Flags的优先级更高

FLAG_ACTIVITY_SINGLE_TOP 跟在AndroidManifest设置launchMode为SingleTop一样效果

FLAG_ACTIVITY_CLEAR_TOP 一般跟SingleTop同时出现,当然也可以跟其他一起用,会将这个实例连同他之上的Activity都出栈,然后再新建一个实例

FLAG_ACTIVITY_EXCLUE_FROM_RECENTS 不会出现在历史Activity的列表中,跟android:excludeFromRecents="true"等同

IntentFilter匹配

action和data有必须要设置一个去匹配,category设置了必须要存在,要么不设置,有默认值,具体内容看详细版本

Fragment

View的首次绘制(绘制部分也在这里)

view的第一次绘制是在activityThread中的handleResumeActivity中去实现的,activityThread是操作activity的一个线程,非常重要的一个类。这里先简单地说下吧。

  • 首先整个系统开机(加载boot_loader)的时候
  • 会先出现一个init进程
  • init进程fork一个zygote进程
  • zygote进程fork出system_server
  • system_server启动后,很多系统的进程例如AMS,WMS,PMS都是该进程创建后启动的
  • launcher进程启动后,触发了startActivity,通过binder机制告诉system_server
  • system_server接收到要启动activity的消息,system_server中的AMS会去通过socket告诉zygote进程,fork出app的进程。
  • app进程执行activityThread的main入口,并初始化ApplicationThread(继承了activityThread,同时实现了IBinder的接口)用于和AMS交互
  • applicationThread通过binder机制告诉system_server,我要绑定AMS,system_server收到通知,向app进程发送handleBindApplication请求,并scheduleLaunchActivity请求。
  • app进程收到请求后,通过handler向activityThread发送对应的message,执行对应的消息。最后完成对应的生命周期方法onCreate/onResume等。

需要注意的是先走的onCreate的生命周期,也就是先走的setContentView,然后当视图加载到DecorView中的时候才会触发requestLayout方法

可以看到在handleResumeActivity中通过wm去addview来添加decorview,wm是windowmanagerImpl。 windowmangerImpl.addView->windowManagerGlobal.addView->ViewRootImpl.setView->ViewRootImpl.requestLayout就触发了第一次的view的绘制。

setContentView源码分析

以上就是setContentView加载view的流程,至此我们简单总结一下从activity创建到view的创建的流程:

  • 1.应用启动之后,执行activity的oncreate方法,在方法里setContentView;
  • 2.通过调用phonewindow的setContentView,执行layoutinfalter的inflate方法去加载布局;
  • 3.在inflate方法里,用XmlPullParser解析xml;
  • 4.解析过程中,可以手动设置factory,如果未设置走默认创建view的流程,也就是通过反射调用类的构造新建view。

requestLayout触发了view的第一次绘制后的具体View绘制

从上面可以看到是ViewRootImpl去执行requestLayout去进行第一次View的绘制的

首先检查是否在主线程中,最后执行scheduleTraversals,这个方法中的主要流程是performTraversals -> performMeasure -> performLayout -> performDraw performMeasure都会先去判断是否需要重新测量?需要的话调用measure,其他两个方法同理。

  • View的测量-onMeasure

对于 ViewGroup,除了要完成自己的测量,还要遍历调用子元素的 measure() 方法,而 View 只需要通过 measure() 方法就能确定测量规格。

View 的测量过程由 View 的 measure() 方法完成,measure() 方法是一个 final 类型的方法,子类不能重写。

View 的 measure() 方法会调用 onMeasure() 方法,这个方法我们是可以重写的,onMeasure() 的实现如下。

widthMeasureSpec 和 heightMeasureSpec 是从父 View 传过来的宽高测量规格,getDefaultSize() 方法是用来获取默认宽高的,getDefaultSize() 的实现如下。

从 getDefaultSize() 方法中可以看出,当测量模式为 UNSPECIFIED 时,宽/高就是最小宽/高,当测量模式为 AT_MOST 或 EXACTLY 时,宽/高就是 ViewGroup 指定的 SpecSize。

View 的宽/高由 specSize 决定,直接继承 View 的自定义控件需要重写 onMeasure() 方法并设置 wrap_content 时的自身大小,否则咋布局中使用 wrap_content 相当于使用 match_parent 。

从前面的代码可以了解到,如果 View 在布局中使用 wrap_content,那么它的 specMode 是 AT_MOST 模式,这时它的宽/高为 specSize ,这时 View 的 specSize 为 ViewGroup 的 specSize。

MeasureSpec

MeasureSpec代表了宽高的尺寸要求,是一个int型的32位参数。前两位代表的是mode后面30位代表的是size。 mode一般分三种:

  • UNSPECIFIED 不指定大小;
  • EXACTLY 精准大小;
  • AT_MOST 最大值模式;

makeMeasureSpec将mode和size打包成int型的MeasureSpec;

LayoutParams

LayoutParams被view用于告诉父布局想被怎样包裹。

MATCH_PARENT:该view希望和父布局一样大。
WRAP_CONTENT:该view希望包裹住其内容。

onMeasure的方法很简单,就是调用了setMeasuredDimension().这个方法其实就是最终将width和height赋值给了全局变量保存了起来,因此一旦调用了这个方法意味着此view的测量结束。注意注释里提到的一句话:必须重写setMeasureDimension这个方法,不然会由measure抛出异常。 继续看getDefaultSize().

如果specMode是AT_MOST和EXACTLY,则尺寸就是specSize。而默认的specSize就是父布局传入进来的。

最小的建议宽度和高度是由view的bg和设置的大小决定的。

测量

ViewGroup的测量从MeasureChilderen开始,实际内部是递归调用了MeasureChild(属性为gone的不测量),让我们直接看MeasureChild这个方法:

结合父布局的MeasureSpec和子view的宽高params等,再调用getChildMeasureSpec来调整子view的measureSpec,最后调用子view的measure方法进行处理。那继续看下getChildMeasureSpec是怎么调整的

(childDimension是宽高,padding是边界大小)三个mode其实差不多,简单来说就是:如果发现childDimension是一个具体数值>=0,那么设为exactly,如果是match_parent,resultmode设置为exactly;如果是wrap_content,设为AT_MOST; ok。至此就是viewGroup的测量。 使用view的getMeasureWidth/Height可以获取view的大小,但是必须在onMeasure流程结束后获取。

  • View的布局 onLayout

layout() 方法的作用是 ViewGroup 用于确定子元素的位置,当 ViewGroup 的位置确定后,会在 onLayout() 方法中遍历所有子 View 并调用子 View 的 layout() 方法。

layout() 方法用于确定 View 自己的位置,而 onLayout() 方法则用于确定所有子元素的位置,View 的 layout() 方法首先会通过 setFrame() 方法设定 View 的边框,也就是 mLeft、mRight、mTop 和 mBottom 四个顶点的值,这时 View 在父 View 中的位置就确定了。

设定了四个顶点后,layout() 方法就会调用 onLayout() 方法确定子 View 的位置,View 和 ViewGroup 都没有实现 onLayout() 方法,由子类去实现

  • onDraw

View 绘制分为下面 6 步:

  1. 绘制背景
  2. 保存 Canvas 图层为后续淡出做准备(可选)
  3. 绘制 View 的内容
  4. 绘制子 View (dispatchDraw)
  5. 绘制淡出边缘并恢复 Canvas 图层(可选)
  6. 绘制装饰(比如 foreground 和 scrollbar)

一般情况下第 2 步和第 5 步是不执行的。

View的事件传递

【图解】Android View 事件传递机制 - 掘金 (juejin.cn)