面试准备(安卓部分)
面试准备(安卓部分)
四大组件
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 步:
- 绘制背景
- 保存 Canvas 图层为后续淡出做准备(可选)
- 绘制 View 的内容
- 绘制子 View (dispatchDraw)
- 绘制淡出边缘并恢复 Canvas 图层(可选)
- 绘制装饰(比如 foreground 和 scrollbar)
一般情况下第 2 步和第 5 步是不执行的。