Android应用层View绘制流程与源码分析

最后更新于:2022-04-01 09:53:42

版权声明:本文为博主原创文章,未经博主允许不得转载。 目录[(?)](http://blog.csdn.net/yanbober/article/details/46128379# "系统根据文章中H1到H6标签自动生成文章目录")[[+]](http://blog.csdn.net/yanbober/article/details/46128379# "展开") 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 1 背景 还记得前面[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7959677.jpg "") 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现。 前面[《Android触摸屏事件派发机制详解与源码分析一(View篇)》](http://blog.csdn.net/yanbober/article/details/45887547)文章的3-1小节说过Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View实现的,当然也包括我们后面一步一步引出的自定义控件也不例外,所以说这些View应该都具有相同的绘制流程与机制才能显示到屏幕上(因为他们都具备相同的父类View,可能每个控件的具体绘制逻辑有差异,但是主流程都是一样的)。经过总结发现每一个View的绘制过程都必须经历三个最主要的过程,也就是measure、layout和draw。 既然一个View的绘制主要流程是这三步,那一定有一个开始地方呀,就像一个类从main函数执行一样呀。对于View的绘制开始调运地方这里先给出结论,本文后面会反过来分析原因的,先往下看就行。具体结论如下: 整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法(这个方法巨长)开始的,该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个,如下: ~~~ private void performTraversals() { ...... //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来 //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... mView.draw(canvas); ...... } ~~~ ~~~ /** * Figures out the measure spec for the root view in a window based on it's * layout params. * * @param windowSize * The available width or height of the window * * @param rootDimension * The layout params for one dimension (width or height) of the * window. * * @return The measure spec to use to measure the root view. */ private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; ...... } return measureSpec; } ~~~ 可以看见这个方法的注释说是用来测Root View的。上面传入参数后这个函数走的是MATCH_PARENT,使用MeasureSpec.makeMeasureSpec方法组 装一个MeasureSpec,MeasureSpec的specMode等于EXACTLY,specSize等于windowSize,也就是为何根视图总是全屏的原因。 其中的mView就是View对象。如下就是整个流程的大致流程图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7e80fd7.jpg "") 如下我们就依据View绘制的这三个主要流程进行详细剖析(基于Android5.1.1 API 22源码进行分析)。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 2 View绘制流程第一步:递归measure源码分析 整个View树的源码measure流程图如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7e945a0.jpg "") ### 2-1 measure源码分析 先看下View的measure方法源码,如下: ~~~ /** * <p> * This is called to find out how big a view should be. The parent * supplies constraint information in the width and height parameters. * </p> * * <p> * The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses. * </p> * * * @param widthMeasureSpec Horizontal space requirements as imposed by the * parent * @param heightMeasureSpec Vertical space requirements as imposed by the * parent * * @see #onMeasure(int, int) */ //final方法,子类不可重写 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ...... //回调onMeasure()方法 onMeasure(widthMeasureSpec, heightMeasureSpec); ...... } ~~~ 看见注释信息没有,他告诉你了很多重要信息。为整个View树计算实际的大小,然后设置实际的高和宽,每个View控件的实际宽高都是由父视图和自身 决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以 View子类只能通过重载onMeasure来实现自己的测量逻辑。 这个方法的两个参数都是父View传递过来的,也就是代表了父view的规格。他由两部分组成,高16位表示MODE,定义在MeasureSpec类(View的内部类)中,有三种类型,MeasureSpec.EXACTLY表示确定大小, MeasureSpec.AT_MOST表示最大大小, MeasureSpec.UNSPECIFIED不确定。低16位表示size,也就是父View的大小。对于系统Window类的DecorVIew对象Mode一般都为MeasureSpec.EXACTLY ,而size分别对应屏幕宽高。对于子View来说大小是由父View和子View共同决定的。 在这里可以看出measure方法最终回调了View的onMeasure方法,我们来看下View的onMeasure源码,如下: ~~~ /** * <p> * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overriden by subclasses to provide accurate and efficient * measurement of their contents. * </p> * * <p> * <strong>CONTRACT:</strong> When overriding this method, you * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the * measured width and height of this view. Failure to do so will trigger an * <code>IllegalStateException</code>, thrown by * {@link #measure(int, int)}. Calling the superclass' * {@link #onMeasure(int, int)} is a valid use. * </p> * * <p> * The base class implementation of measure defaults to the background size, * unless a larger size is allowed by the MeasureSpec. Subclasses should * override {@link #onMeasure(int, int)} to provide better measurements of * their content. * </p> * * <p> * If this method is overridden, it is the subclass's responsibility to make * sure the measured height and width are at least the view's minimum height * and width ({@link #getSuggestedMinimumHeight()} and * {@link #getSuggestedMinimumWidth()}). * </p> * * @param widthMeasureSpec horizontal space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * @param heightMeasureSpec vertical space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * * @see #getMeasuredWidth() * @see #getMeasuredHeight() * @see #setMeasuredDimension(int, int) * @see #getSuggestedMinimumHeight() * @see #getSuggestedMinimumWidth() * @see android.view.View.MeasureSpec#getMode(int) * @see android.view.View.MeasureSpec#getSize(int) */ //View的onMeasure默认实现方法 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } ~~~ 看见没有,其实注释已经很详细了(自定义View重写该方法的指导操作注释都有说明),不做过多解释。 对于非ViewGroup的View而言,通过调用上面默认的onMeasure即可完成View的测量,当然你也可以重载onMeasure并调用setMeasuredDimension来设置任意大小的布局,但一般不这么做,因为这种做法不太好,至于为何不好,后面分析完你就明白了。 我们可以看见onMeasure默认的实现仅仅调用了setMeasuredDimension,setMeasuredDimension函数是一个很关键的函数,它对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值,measure的主要目的就是对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值,所以一旦这两个变量被赋值意味着该View的测量工作结束。既然这样那我们就看看设置的默认尺寸大小吧,可以看见setMeasuredDimension传入的参数都是通过getDefaultSize返回的,所以再来看下getDefaultSize方法源码,如下: ~~~ public static int getDefaultSize(int size, int measureSpec) { int result = size; //通过MeasureSpec解析获取mode与size int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; } ~~~ 看见没有,如果specMode等于AT_MOST或EXACTLY就返回specSize,这就是系统默认的规格。 回过头继续看上面onMeasure方法,其中getDefaultSize参数的widthMeasureSpec和heightMeasureSpec都是由父View传递进来的。getSuggestedMinimumWidth与getSuggestedMinimumHeight都是View的方法,具体如下: ~~~ protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); } ~~~ 看见没有,建议的最小宽度和高度都是由View的Background尺寸与通过设置View的miniXXX属性共同决定的。 到此一次最基础的元素View的measure过程就完成了。上面说了View实际是嵌套的,而且measure是递归传递的,所以每个View都需要measure。实际能够嵌套的View一般都是ViewGroup的子类,所以在ViewGroup中定义了measureChildren, measureChild, measureChildWithMargins方法来对子视图进行测量,measureChildren内部实质只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也作为子视图的大小。如下我们以ViewGroup中稍微复杂的measureChildWithMargins方法来分析: ~~~ /** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins. The child must have MarginLayoutParams The heavy lifting is * done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param widthUsed Extra space that has been used up by the parent * horizontally (possibly by other children of the parent) * @param parentHeightMeasureSpec The height requirements for this view * @param heightUsed Extra space that has been used up by the parent * vertically (possibly by other children of the parent) */ protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { //获取子视图的LayoutParams final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //调整MeasureSpec //通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); //调运子View的measure方法,子View的measure中会回调子View的onMeasure方法 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } ~~~ 关于该方法的参数等说明注释已经描述的够清楚了。该方法就是对父视图提供的measureSpec参数结合自身的LayoutParams参数进行了调整,然后再 来调用child.measure()方法,具体通过方法getChildMeasureSpec来进行参数调整。所以我们继续看下getChildMeasureSpec方法代码,如下: ~~~ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //获取当前Parent View的Mode和Size int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0 int size = Math.max(0, specSize - padding); //定义返回值存储变量 int resultSize = 0; int resultMode = 0; //依据当前Parent的Mode进行switch分支逻辑 switch (specMode) { // Parent has imposed an exact size on us //默认Root View的Mode就是EXACTLY case MeasureSpec.EXACTLY: if (childDimension >= 0) { //如果child的layout_wOrh属性在xml或者java中给予具体大于等于0的数值 //设置child的size为真实layout_wOrh属性值,mode为EXACTLY resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { //如果child的layout_wOrh属性在xml或者java中给予MATCH_PARENT // Child wants to be our size. So be it. //设置child的size为size,mode为EXACTLY resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { //如果child的layout_wOrh属性在xml或者java中给予WRAP_CONTENT //设置child的size为size,mode为AT_MOST // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; ...... //其他Mode分支类似 } //将mode与size通过MeasureSpec方法整合为32位整数返回 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } ~~~ 可以看见,getChildMeasureSpec的逻辑是通过其父View提供的MeasureSpec参数得到specMode和specSize,然后根据计算出来的specMode以及 子View的childDimension(layout_width或layout_height)来计算自身的measureSpec,如果其本身包含子视图,则计算出来的measureSpec将作 为调用其子视图measure函数的参数,同时也作为自身调用setMeasuredDimension的参数,如果其不包含子视图则默认情况下最终会调用onMeasure 的默认实现,并最终调用到setMeasuredDimension。 所以可以看见onMeasure的参数其实就是这么计算出来的。同时从上面的分析可以看出来,最终决定View的measure大小是View的setMeasuredDimension方法,所以我们可以通过setMeasuredDimension设定死值来设置View的mMeasuredWidth和mMeasuredHeight的大小,但是一个好的自定义View应该会根据子视图的measureSpec来设置mMeasuredWidth和mMeasuredHeight的大小,这样的灵活性更大,所以这也就是上面分析onMeasure时说View的onMeasure最好不要重写死值的原因。 可以看见当通过setMeasuredDimension方法最终设置完成View的measure之后View的mMeasuredWidth和mMeasuredHeight成员才会有具体的数值,所以如果我们自定义的View或者使用现成的View想通过getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。 还记得前面[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)文章3-3小节探讨的inflate方法加载一些布局显示时指定的大小失效问题吗?当时只给出了结论,现在给出了详细原因分析,我想不需要再做过多解释了吧。 至此整个View绘制流程的第一步就分析完成了,可以看见,相对来说还是比较复杂的,接下来进行小结。 ### 2-2 measure原理总结 通过上面分析可以看出measure过程主要就是从顶层父View向子View递归调用view.measure方法(measure中又回调onMeasure方法)的过程。具体measure核心主要有如下几点: - MeasureSpec(View的内部类)测量规格为int型,值由高16位规格模式specMode和低16位具体尺寸specSize组成。其中specMode只有三种值: ~~~ MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定; MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值; MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定; ~~~ - View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。 - 最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。 - ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。 - 只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。 - View的布局大小由父View和子View共同决定。 - 使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 3 View绘制流程第二步:递归layout源码分析 在上面的背景介绍就说过,当ViewRootImpl的performTraversals中measure执行完成以后会接着执行mView.layout,具体如下: ~~~ private void performTraversals() { ...... mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... } ~~~ 可以看见layout方法接收四个参数,这四个参数分别代表相对Parent的左、上、右、下坐标。而且还可以看见左上都为0,右下分别为上面刚刚测量的 width和height。至此又回归到View的layout(int l, int t, int r, int b)方法中去实现具体逻辑了,所以接下来我们开始分析View的layout过程。 整个View树的layout递归流程图如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7ebf1e8.jpg "") ### 3-1 layout源码分析 layout既然也是递归结构,那我们先看下ViewGroup的layout方法,如下: ~~~ @Override public final void layout(int l, int t, int r, int b) { ...... super.layout(l, t, r, b); ...... } ~~~ 看着没有?ViewGroup的layout方法实质还是调运了View父类的layout方法,所以我们看下View的layout源码,如下: ~~~ public void layout(int l, int t, int r, int b) { ...... //实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量 //判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //需要重新layout if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //回调onLayout onLayout(changed, l, t, r, b); ...... } ...... } ~~~ 看见没有,类似measure过程,lauout调运了onLayout方法。 对比上面View的layout和ViewGroup的layout方法可以发现,View的layout方法是可以在子类重写的,而ViewGroup的layout是不能在子类重写的,言外之意就是说ViewGroup中只能通过重写onLayout方法。那我们接下来看下ViewGroup的onLayout方法,如下: ~~~ @Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b); ~~~ 看见没有?ViewGroup的onLayout()方法竟然是一个抽象方法,这就是说所有ViewGroup的子类都必须重写这个方法。所以在自定义ViewGroup控件 中,onLayout配合onMeasure方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,然后调用onLayout方法动 态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,重载onLayout通常做法 就是写一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。 再看下View的onLayout方法源码,如下: ~~~ protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } ~~~ 我勒个去!是一个空方法,没啥可看的。 既然这样那我们只能分析一个现有的继承ViewGroup的控件了,就拿LinearLayout来说吧,如下是LinearLayout中onLayout的一些代码: ~~~ public class LinearLayout extends ViewGroup { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } } } ~~~ 看见没有,LinearLayout的layout过程是分Vertical和Horizontal的,这个就是xml布局的orientation属性设置的,我们为例说明ViewGroup的onLayout 重写一般步骤就拿这里的VERTICAL模式来解释吧,如下是layoutVertical方法源码: ~~~ void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // Where right end of child should go //计算父窗口推荐的子View宽度 final int width = right - left; //计算父窗口推荐的子View右侧位置 int childRight = width - mPaddingRight; // Space available for child //child可使用空间大小 int childSpace = width - paddingLeft - mPaddingRight; //通过ViewGroup的getChildCount方法获取ViewGroup的子View个数 final int count = getVirtualChildCount(); //获取Gravity属性设置 final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; //依据majorGravity计算childTop的位置值 switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = mPaddingTop; break; } //重点!!!开始遍历 for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { //LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值 final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); //获取子View的LayoutParams final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); //依据不同的absoluteGravity计算childLeft位置 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; } if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; //通过垂直排列计算调运child的layout设置child的位置 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } } ~~~ 从上面分析的ViewGroup子类LinearLayout的onLayout实现代码可以看出,一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth 和mMeasuredHeight来安排子View在父View中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定 义的ViewGroup,其子View的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子 View的具体位置。到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别(上面分析measure过程已经说过getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)。可以看出来getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效。那我们看下View源码中这些方法的实现吧,如下: ~~~ public final int getMeasuredWidth() { return mMeasuredWidth & MEASURED_SIZE_MASK; } public final int getMeasuredHeight() { return mMeasuredHeight & MEASURED_SIZE_MASK; } public final int getWidth() { return mRight - mLeft; } public final int getHeight() { return mBottom - mTop; } public final int getLeft() { return mLeft; } public final int getRight() { return mRight; } public final int getTop() { return mTop; } public final int getBottom() { return mBottom; } ~~~ 这也解释了为什么有些情况下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()会得到不同的值,所以这里不做过多解释。 到此整个View的layout过程分析就算结束了,接下来进行一些总结工作。 ### 3-2 layout原理总结 整个layout过程比较容易理解,从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。具体layout核心主要有以下几点: - View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑。 - measure操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来说的。 - 凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的(前面[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)也有提到过)。 - 使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程之后被调用才能返回有效值。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 4 View绘制流程第三步:递归draw源码分析 在上面的背景介绍就说过,当ViewRootImpl的performTraversals中measure和layout执行完成以后会接着执行mView.layout,具体如下: ~~~ private void performTraversals() { ...... final Rect dirty = mDirty; ...... canvas = mSurface.lockCanvas(dirty); ...... mView.draw(canvas); ...... } ~~~ draw过程也是在ViewRootImpl的performTraversals()内部调运的,其调用顺序在measure()和layout()之后,这里的mView对于Actiity来说就是 PhoneWindow.DecorView,ViewRootImpl中的代码会创建一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工。所以又回归到了 ViewGroup与View的树状递归draw过程。 先来看下View树的递归draw流程图,如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7ed816b.jpg "") 如下我们详细分析这一过程。 ### 4-1 draw源码分析 由于ViewGroup没有重写View的draw方法,所以如下直接从View的draw方法开始分析: ~~~ public void draw(Canvas canvas) { ...... /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed ...... if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) ...... // Step 2, save the canvas' layers ...... if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } ...... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ...... if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } ...... // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); ...... } ~~~ 看见整个View的draw方法很复杂,但是源码注释也很明显。从注释可以看出整个draw过程分为了6步。源码注释说 (”skip step 2 & 5 if possible (common case)”)第2和5步可以跳过,所以我们接下来重点剩余四步。如下: 第一步,对View的背景进行绘制。 可以看见,draw方法通过调运drawBackground(canvas);方法实现了背景绘制。我们来看下这个方法源码,如下: ~~~ private void drawBackground(Canvas canvas) { //获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable final Drawable background = mBackground; ...... //根据layout过程确定的View位置来设置背景的绘制区域 if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; rebuildOutline(); } ...... //调用Drawable的draw()方法来完成背景的绘制工作 background.draw(canvas); ...... } ~~~ 第三步,对View的内容进行绘制。 可以看到,这里去调用了一下View的onDraw()方法,所以我们看下View的onDraw方法(ViewGroup也没有重写该方法),如下: ~~~ /** * Implement this to do your drawing. * * @param canvas the canvas on which the background will be drawn */ protected void onDraw(Canvas canvas) { } ~~~ 可以看见,这是一个空方法。因为每个View的内容部分是各不相同的,所以需要由子类去实现具体逻辑。 第四步,对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制。 我们来看下View的draw方法中的dispatchDraw(canvas);方法源码,可以看见如下: ~~~ /** * Called by draw to draw the child views. This may be overridden * by derived classes to gain control just before its children are drawn * (but after its own view has been drawn). * @param canvas the canvas on which to draw the view */ protected void dispatchDraw(Canvas canvas) { } ~~~ 看见没有,View的dispatchDraw()方法是一个空方法,而且注释说明了如果View包含子类需要重写他,所以我们有必要看下ViewGroup的dispatchDraw 方法源码(这也就是刚刚说的对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制的原因,因为如果是View调运该方法 是空的,而ViewGroup才有实现),如下: ~~~ @Override protected void dispatchDraw(Canvas canvas) { ...... final int childrenCount = mChildrenCount; final View[] children = mChildren; ...... for (int i = 0; i < childrenCount; i++) { ...... if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } ...... // Draw any disappearing views that have animations if (mDisappearingChildren != null) { ...... for (int i = disappearingCount; i >= 0; i--) { ...... more |= drawChild(canvas, child, drawingTime); } } ...... } ~~~ 可以看见,ViewGroup确实重写了View的dispatchDraw()方法,该方法内部会遍历每个子View,然后调用drawChild()方法,我们可以看下 ViewGroup的drawChild方法,如下: ~~~ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); } ~~~ 可以看见drawChild()方法调运了子View的draw()方法。所以说ViewGroup类已经为我们重写了dispatchDraw()的功能实现,我们一般不需要重写该方法 ,但可以重载父类函数实现具体的功能。 第六步,对View的滚动条进行绘制。 可以看到,这里去调用了一下View的onDrawScrollBars()方法,所以我们看下View的onDrawScrollBars(canvas);方法,如下: ~~~ /** * <p>Request the drawing of the horizontal and the vertical scrollbar. The * scrollbars are painted only if they have been awakened first.</p> * * @param canvas the canvas on which to draw the scrollbars * * @see #awakenScrollBars(int) */ protected final void onDrawScrollBars(Canvas canvas) { //绘制ScrollBars分析不是我们这篇的重点,所以暂时不做分析 ...... } ~~~ 可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。 到此,View的draw绘制部分源码分析完毕,我们接下来进行一些总结。 ### 4-2 draw原理总结 可以看见,绘制过程就是把View对象绘制到屏幕上,整个draw过程需要注意如下细节: - 如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View。 - View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。 - View的绘制是借助onDraw方法传入的Canvas类来进行的。 - 区分View动画和ViewGroup布局动画,前者指的是View自身的动画,可以通过setAnimation添加,后者是专门针对ViewGroup显示内部子视图时设置的动画,可以在xml布局文件中对ViewGroup设置layoutAnimation属性(譬如对LinearLayout设置子View在显示时出现逐行、随机、下等显示等不同动画效果)。 - 在获取画布剪切区(每个View的draw中传入的Canvas)时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制即可。 - 默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 5 View的invalidate和postInvalidate方法源码分析 你可能已经看见了,在上面分析View的三步绘制流程中最后都有调运一个叫invalidate的方法,这个方法是啥玩意?为何出现频率这么高?很简单,我们拿出来分析分析不就得了。 ### 5-1 invalidate方法源码分析 来看一下View类中的一些invalidate方法(ViewGroup没有重写这些方法),如下: ~~~ /** * Mark the area defined by dirty as needing to be drawn. If the view is * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some * point in the future. * <p> * This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. * <p> * <b>WARNING:</b> In API 19 and below, this method may be destructive to * {@code dirty}. * * @param dirty the rectangle representing the bounds of the dirty region */ //看见上面注释没有?public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View public void invalidate(Rect dirty) { final int scrollX = mScrollX; final int scrollY = mScrollY; //实质还是调运invalidateInternal方法 invalidateInternal(dirty.left - scrollX, dirty.top - scrollY, dirty.right - scrollX, dirty.bottom - scrollY, true, false); } /** * Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The * coordinates of the dirty rect are relative to the view. If the view is * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some * point in the future. * <p> * This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. * * @param l the left position of the dirty region * @param t the top position of the dirty region * @param r the right position of the dirty region * @param b the bottom position of the dirty region */ //看见上面注释没有?public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View public void invalidate(int l, int t, int r, int b) { final int scrollX = mScrollX; final int scrollY = mScrollY; //实质还是调运invalidateInternal方法 invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false); } /** * Invalidate the whole view. If the view is visible, * {@link #onDraw(android.graphics.Canvas)} will be called at some point in * the future. * <p> * This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. */ //看见上面注释没有?public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个View public void invalidate() { //invalidate的实质还是调运invalidateInternal方法 invalidate(true); } /** * This is where the invalidate() work actually happens. A full invalidate() * causes the drawing cache to be invalidated, but this function can be * called with invalidateCache set to false to skip that invalidation step * for cases that do not need it (for example, a component that remains at * the same dimensions with the same content). * * @param invalidateCache Whether the drawing cache for this view should be * invalidated as well. This is usually true for a full * invalidate, but may be set to false if the View's contents or * dimensions have not changed. */ //看见上面注释没有?default的权限,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个View void invalidate(boolean invalidateCache) { //实质还是调运invalidateInternal方法 invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } //!!!!!!看见没有,这是所有invalidate的终极调运方法!!!!!! void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { ...... // Propagate the damage rectangle to the parent view. final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; //设置刷新区域 damage.set(l, t, r, b); //传递调运Parent ViewGroup的invalidateChild方法 p.invalidateChild(this, damage); } ...... } ~~~ 看见没有,View的invalidate(invalidateInternal)方法实质是将要刷新区域直接传递给了父ViewGroup的invalidateChild方法,在invalidate中,调 用父View的invalidateChild,这是一个从当前向上级父View回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集 。所以我们看 下ViewGroup的invalidateChild方法,源码如下: ~~~ public final void invalidateChild(View child, final Rect dirty) { ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; ...... do { ...... //循环层层上级调运,直到ViewRootImpl会返回null parent = parent.invalidateChildInParent(location, dirty); ...... } while (parent != null); } ~~~ 这个过程最后传递到ViewRootImpl的invalidateChildInParent方法结束,所以我们看下ViewRootImpl的invalidateChildInParent方法,如下: ~~~ @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { ...... //View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法 scheduleTraversals(); ...... return null; } ~~~ 看见没有?这个ViewRootImpl类的invalidateChildInParent方法直接返回了null,也就是上面ViewGroup中说的,层层上级传递到ViewRootImpl的 invalidateChildInParent方法结束了那个do while循环。看见这里调运的scheduleTraversals这个方法吗?scheduleTraversals会通过Handler的 Runnable发送一个异步消息,调运doTraversal方法,然后最终调用performTraversals()执行重绘。开头背景知识介绍说过的,performTraversals就 是整个View数开始绘制的起始调运地方,所以说View调运invalidate方法的实质是层层上传到父级,直到传递到ViewRootImpl后触发了scheduleTraversals 方法,然后整个View树开始重新按照上面分析的View绘制流程进行重绘任务。 到此View的invalidate方法原理就分析完成了。 ### 5-2 postInvalidate方法源码分析 上面分析invalidate方法时注释中说该方法只能在UI Thread中执行,其他线程中需要使用postInvalidate方法,所以我们来分析分析postInvalidate这个方法源码。如下: ~~~ public void postInvalidate() { postInvalidateDelayed(0); } ~~~ 继续看下他的调运方法postInvalidateDelayed,如下: ~~~ public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; //核心,实质就是调运了ViewRootImpl.dispatchInvalidateDelayed方法 if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); } } ~~~ 我们继续看他调运的ViewRootImpl类的dispatchInvalidateDelayed方法,如下源码: ~~~ public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); } ~~~ 看见没有,通过ViewRootImpl类的Handler发送了一条MSG_INVALIDATE消息,继续追踪这条消息的处理可以发现: ~~~ public void handleMessage(Message msg) { ...... switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; ...... } ...... } ~~~ 看见没有,实质就是又在UI Thread中调运了View的invalidate();方法,那接下来View的invalidate();方法我们就不说了,上名已经分析过了。 到此整个View的postInvalidate方法就分析完成了。 ### 5-3 invalidate与postInvalidate方法总结 依据上面对View的invalidate分析我总结绘制如下流程图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7ef350e.jpg "") 依据上面对View的postInvalidate分析我总结绘制如下流程图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7f15d8e.jpg "") 关于这两个方法的具体流程和原理上面也分析过了,流程图也给出了,相信已经很明确了,没啥需要解释的了。所以我们对其做一个整体总结,归纳出重点如下: invalidate系列方法请求重绘View树(也就是draw方法),如果View大小没有发生变化就不会调用layout过程,并且只绘制那些“需要重绘的”View,也就是哪个View(View只绘制该View,ViewGroup绘制整个ViewGroup)请求invalidate系列方法,就绘制该View。 常见的引起invalidate方法操作的原因主要有: - 直接调用invalidate方法.请求重新draw,但只会绘制调用者本身。 - 触发setSelection方法。请求重新draw,但只会绘制调用者本身。 - 触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。 - 触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。 - 触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。 ### 5-4 通过invalidate方法分析结果回过头去解决一个背景介绍中的疑惑 分析完invalidate后需要你回过头去想一个问题。还记不记得这篇文章的开头背景介绍,我们说整个View绘制流程的最初代码是在ViewRootImpl类的performTraversals()方法中开始的。上面当时只是告诉你了这个结论,至于这个ViewRootImpl类的performTraversals()方法为何会被触发没有说明原因。现在我们就来分析一下这个触发的源头。 让我们先把大脑思考暂时挪回到[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)这篇博文的setContentView机制分析中(不清楚的请点击先看这篇文章再回过头来继续看)。我们先来看下那篇博文分析的PhoneWindow的setContentView方法源码,如下: ~~~ @Override public void setContentView(View view, ViewGroup.LayoutParams params) { ...... //如果mContentParent为空进行一些初始化,实质mContentParent是通过findViewById(ID_ANDROID_CONTENT);获取的id为content的FrameLayout的布局(不清楚的请先看《Android应用setContentView与LayoutInflater加载解析机制源码分析》文章) if (mContentParent == null) { installDecor(); } ...... //把我们的view追加到mContentParent mContentParent.addView(view, params); ...... } ~~~ 这个方法是Activity中setContentView的实现,我们继续看下这个方法里调运的addView方法,也就是ViewGroup的addView方法,如下: ~~~ public void addView(View child) { addView(child, -1); } public void addView(View child, int index) { ...... addView(child, index, params); } public void addView(View child, int index, LayoutParams params) { ...... //该方法稍后后面会详细分析 requestLayout(); //重点关注!!! invalidate(true); ...... } ~~~ 看见addView调运invalidate方法没有?这不就真相大白了。当我们写一个Activity时,我们一定会通过setContentView方法将我们要展示的界面传入 该方法,该方法会讲我们界面通过addView追加到id为content的一个FrameLayout(ViewGroup)中,然后addView方法中通过调运invalidate(true) 去通知触发ViewRootImpl类的performTraversals()方法,至此递归绘制我们自定义的所有布局。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 6 View的requestLayout方法源码分析 ### 6-1 requestLayout方法分析 和invalidate类似,其实在上面分析View绘制流程时或多或少都调运到了这个方法,而且这个方法对于View来说也比较重要,所以我们接下来分析一下他。如下View的requestLayout源码: ~~~ public void requestLayout() { ...... if (mParent != null && !mParent.isLayoutRequested()) { //由此向ViewParent请求布局 //从这个View开始向上一直requestLayout,最终到达ViewRootImpl的requestLayout mParent.requestLayout(); } ...... } ~~~ 看见没有,当我们触发View的requestLayout时其实质就是层层向上传递,直到ViewRootImpl为止,然后触发ViewRootImpl的requestLayout方法, 如下就是ViewRootImpl的requestLayout方法: ~~~ @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; //View调运requestLayout最终层层上传到ViewRootImpl后最终触发了该方法 scheduleTraversals(); } } ~~~ 看见没有,类似于上面分析的invalidate过程,只是设置的标记不同,导致对于View的绘制流程中触发的方法不同而已。 ### 6-2 requestLayout方法总结 可以看见,这些方法都是大同小异。对于requestLayout方法来说总结如下: requestLayout()方法会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身。 ### 7 View绘制流程总结 至此整个关于Android应用程序开发中的View绘制机制及相关重要方法都已经分析完毕。关于各个方法的总结这里不再重复,直接通过该文章前面的目录索引到相应方法的总结小节进行查阅即可。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】
';

Android构建过程简述

最后更新于:2022-04-01 09:53:39

Android的构建过程涉及到许多工具和流程,并会产生一系列中间件,最终生成一个APK文件,可以根据官方提供的流程图来具体了解构建的过程。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7e14e78.jpg) 通常的构建过程就是如上图所示,下面是具体描述: 1.AAPT(Android Asset Packaging Tool)工具会打包应用中的资源文件,如AndroidManifest.xml、layout布局中的xml等,并将xml文件编译为二进制形式,当然assets文件夹中的文件不会被编译,图片及raw文件夹中的资源也会保持原来的形态,需要注意的是raw文件夹中的资源也会生成资源id。AAPT编译完成之后会生成R.java文件。 2.AIDL工具会将所有的aidl接口转化为java接口。 3.所有的java代码,包括R.java与aidl文件都会被Java编译器编译成.class文件。 4.Dex工具会将上述产生的.class文件及第三库及其他.class文件编译成.dex文件(dex文件是Dalvik虚拟机可以执行的格式),dex文件最终会被打包进APK文件。 5.ApkBuilder工具会将编译过的资源及未编译过的资源(如图片等)以及.dex文件打包成APK文件。 6.生成APK文件后,需要对其签名才可安装到设备,平时测试时会使用debug keystore,当正式发布应用时必须使用release版的keystore对应用进行签名。 7.如果对APK正式签名,还需要使用zipalign工具对APK进行对齐操作,这样做的好处是当应用运行时会减少内存的开销。  在构建APK的过程中,当APK过大,应用中的方法数量超过65536限制的时候,可能会报如下错误:Unable to execute dex: method ID not in [0, 0xffff]: 65536。一旦遇到上述错误,就需要使用MultiDex方案来解决,但官方的MultiDex方案有一些限制,因此还得使用各种策略填坑才行,具体可参考网上不少较优秀的解决方案。 参考文档:https://developer.android.com/sdk/installing/studio-build.html#detailed-build
';

APK安装过程及原理详解

最后更新于:2022-04-01 09:53:37

**应用程序包的安装是android的特点** APK为AndroidPackage的缩写 Android应用安装有如下四种方式: 1.系统应用安装――开机时完成,没有安装界面 2.网络下载应用安装――通过market应用完成,没有安装界面 3.ADB工具安装――没有安装界面。 4.第三方应用安装――通过SD卡里的APK文件安装,有安装界面,由         packageinstaller.apk应用处理安装及卸载过程的界面。 应用安装的流程及路径  应用安装涉及到如下几个目录:         system/app ---------------系统自带的应用程序,获得adb root权限才能删除 data/app  ---------------用户程序安装的目录。安装时把                                                                                                      apk文件复制到此目录 data/data ---------------存放应用程序的数据 data/dalvik-cache--------将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一) 安装过程: 复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。 卸载过程: 删除安装过程中在上述三个目录下创建的文件及目录。 安装应用的过程解析 一.开机安装  PackageManagerService处理各种应用的安装,卸载,管理等工作,开机时由systemServer启动此服务 (源文件路径:android\frameworks\base\services\java\com\android\server\PackageManagerService.java) PackageManagerService服务启动的流程: 1.首先扫描安装“system\framework”目录下的jar包 ~~~ // Find base frameworks (resource packages without code).              mFrameworkInstallObserver = new AppDirObserver(                  mFrameworkDir.getPath(), OBSERVER_EVENTS, true);              mFrameworkInstallObserver.startWatching();              scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM                      | PackageParser.PARSE_IS_SYSTEM_DIR,                      scanMode | SCAN_NO_DEX, 0);   ~~~ 2.扫描安装系统system/app的应用程序 ~~~ // Collect all system packages.             mSystemAppDir = new File(Environment.getRootDirectory(), "app");             mSystemInstallObserver = new AppDirObserver(                 mSystemAppDir.getPath(), OBSERVER_EVENTS, true);             mSystemInstallObserver.startWatching();             scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM                     | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);   ~~~ 3.制造商的目录下/vendor/app应用包 ~~~ // Collect all vendor packages.              mVendorAppDir = new File("/vendor/app");              mVendorInstallObserver = new AppDirObserver(                  mVendorAppDir.getPath(), OBSERVER_EVENTS, true);              mVendorInstallObserver.startWatching();              scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM                      | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);   ~~~ 4.扫描“data\app”目录,即用户安装的第三方应用 ~~~ scanDirLI(mAppInstallDir, 0, scanMode, 0);   ~~~ 5.扫描" data\app-private"目录,即安装DRM保护的APK文件(一个受保护的歌曲或受保 护的视频是使用 DRM 保护的文件) ~~~ scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,                       scanMode, 0);   ~~~ 扫描方法的代码清单 ~~~ private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {           String[] files = dir.list();           if (files == null) {               Log.d(TAG, "No files in app dir " + dir);               return;           }           if (false) {               Log.d(TAG, "Scanning app dir " + dir);           }           int i;           for (i=0; i<files.length; i++) {               File file = new File(dir, files[i]);               if (!isPackageFilename(files[i])) {                   // Ignore entries which are not apk's                   continue;               }               PackageParser.Package pkg = scanPackageLI(file,                       flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime);               // Don't mess around with apps in system partition.               if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&                       mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {                   // Delete the apk                   Slog.w(TAG, "Cleaning up failed install of " + file);                   file.delete();               }           }       }   ~~~ 并且从该扫描方法中可以看出调用了scanPackageLI() private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanMode, long currentTime) 跟踪scanPackageLI()方法后发现,程序经过很多次的if else 的筛选,最后判定可以安装后调用了 mInstaller.install ~~~ if (mInstaller != null) {                       int ret = mInstaller.install(pkgName, useEncryptedFSDir,  pkg.applicationInfo.uid,pkg.applicationInfo.uid);                       if(ret < 0) {                           // Error from installer                           mLastScanError =    PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;                           return null;                       }                   }   mInstaller.install()  通过       LocalSocketAddress address = new LocalSocketAddress(                 "installd", LocalSocketAddress.Namespace.RESERVED); ~~~ 指挥installd在C语言的文件中完成工作 PackageManagerService小节 :1)从apk, xml中载入pacakge信息, 存储到内部成员变量中, 用于后面的查找. 关键的方法是scanPackageLI(). 2)各种查询操作, 包括query Intent操作. 3)install package和delete package的操作. 还有后面的关键方法是installPackageLI(). 二、从网络上下载应用: 下载完成后,会自动调用Packagemanager的安装方法installPackage() /* Called when a downloaded package installation has been confirmed by the user */ 由英文注释可见PackageManagerService类的installPackage()函数为安装程序入口。 ~~~ public void installPackage(              final Uri packageURI, final IPackageInstallObserver observer, final int flags,              final String installerPackageName) {          mContext.enforceCallingOrSelfPermission(                  android.Manifest.permission.INSTALL_PACKAGES, null);          Message msg = mHandler.obtainMessage(INIT_COPY);          msg.obj = new InstallParams(packageURI, observer, flags,                  installerPackageName);          mHandler.sendMessage(msg);      }   ~~~ 其中是通过PackageHandler的实例mhandler.sendMessage(msg)把信息发给继承Handler的类HandleMessage()方法 ~~~ class PackageHandler extends Handler{                       *****************省略若干********************            public void handleMessage(Message msg) {               try {                   doHandleMessage(msg);               } finally {                   Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);               }           }      ******************省略若干**********************    }   ~~~ 把信息发给doHandleMessage()方法,方法中用switch()语句进行判定传来Message ~~~  void doHandleMessage(Message msg) {               switch (msg.what) {                                 case INIT_COPY: {                       if (DEBUG_SD_INSTALL) Log.i(TAG, "init_copy");                       HandlerParams params = (HandlerParams) msg.obj;                       int idx = mPendingInstalls.size();                       if (DEBUG_SD_INSTALL) Log.i(TAG, "idx=" + idx);                       // If a bind was already initiated we dont really                       // need to do anything. The pending install                       // will be processed later on.                       if (!mBound) {                           // If this is the only one pending we might                           // have to bind to the service again.                           if (!connectToService()) {                               Slog.e(TAG, "Failed to bind to media container service");                               params.serviceError();                               return;                           } else {                               // Once we bind to the service, the first                               // pending request will be processed.                               mPendingInstalls.add(idx, params);                           }                       } else {                           mPendingInstalls.add(idx, params);                           // Already bound to the service. Just make                           // sure we trigger off processing the first request.                           if (idx == 0) {                               mHandler.sendEmptyMessage(MCS_BOUND);                           }                       }                       break;                   }                   case MCS_BOUND: {                       if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_bound");                       if (msg.obj != null) {                           mContainerService = (IMediaContainerService) msg.obj;                       }                       if (mContainerService == null) {                           // Something seriously wrong. Bail out                           Slog.e(TAG, "Cannot bind to media container service");                           for (HandlerParams params : mPendingInstalls) {                               mPendingInstalls.remove(0);                               // Indicate service bind error                               params.serviceError();                           }                           mPendingInstalls.clear();                       } else if (mPendingInstalls.size() > 0) {                           HandlerParams params = mPendingInstalls.get(0);                           if (params != null) {                               params.startCopy();                           }                       } else {                           // Should never happen ideally.                           Slog.w(TAG, "Empty queue");                       }                       break;                   }               ****************省略若干**********************   }   }                ~~~ public final boolean sendMessage ([Message](http://developer.android.com/reference/android/os/Message.html) msg) public final boolean sendEmptyMessage (int what) 两者参数有别。 然后调用抽象类HandlerParams中的一个startCopy()方法 ~~~ abstract class HandlerParams { final void startCopy() {    ***************若干if语句判定否这打回handler消息******* handleReturnCode(); } } ~~~ handleReturnCode()复写了两次其中有一次是删除时要调用的,只列出安装调用的一个方法 ~~~ @Override          void handleReturnCode() {              // If mArgs is null, then MCS couldn't be reached. When it              // reconnects, it will try again to install. At that point, this              // will succeed.              if (mArgs != null) {                  processPendingInstall(mArgs, mRet);              }          }   ~~~ 这时可以清楚的看见 processPendingInstall()被调用。 其中run()方法如下 ~~~ run(){   synchronized (mInstallLock) {                           ************省略*****************                           installPackageLI(args, true, res);                          }   }   instaPacakgeLI()args,res参数分析   ~~~ ----------------------------------------------------------------------------------------- //InstallArgs 是在PackageService定义的static abstract class InstallArgs 静态抽象类。 ~~~ static abstract class InstallArgs {   *********************************************************************   其中定义了flag标志,packageURL,创建文件,拷贝apk,修改包名称,                       还有一些删除文件的清理,释放存储函数。       *********************************************************************   }     class PackageInstalledInfo {           String name;           int uid;           PackageParser.Package pkg;           int returnCode;           PackageRemovedInfo removedInfo;    }   ~~~ ----------------------------------------------------------------------------------------- ~~~ private void installPackageLI(InstallArgs args,             boolean newInstall, PackageInstalledInfo res) {         int pFlags = args.flags;         String installerPackageName = args.installerPackageName;         File tmpPackageFile = new File(args.getCodePath());         boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);         boolean onSd = ((pFlags & PackageManager.INSTALL_EXTERNAL) != 0);         boolean replace = false;         int scanMode = (onSd ? 0 : SCAN_MONITOR) | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE                 | (newInstall ? SCAN_NEW_INSTALL : 0);         // Result object to be returned         res.returnCode = PackageManager.INSTALL_SUCCEEDED;         // Retrieve PackageSettings and parse package         int parseFlags = PackageParser.PARSE_CHATTY |         (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) |         (onSd ? PackageParser.PARSE_ON_SDCARD : 0);         parseFlags |= mDefParseFlags;         PackageParser pp = new PackageParser(tmpPackageFile.getPath());         pp.setSeparateProcesses(mSeparateProcesses);         final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,                 null, mMetrics, parseFlags);         if (pkg == null) {             res.returnCode = pp.getParseError();             return;         }         String pkgName = res.name = pkg.packageName;         if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {             if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {                 res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;                 return;             }         }         if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {             res.returnCode = pp.getParseError();             return;         }         // Get rid of all references to package scan path via parser.         pp = null;         String oldCodePath = null;         boolean systemApp = false;         synchronized (mPackages) {             // Check if installing already existing package             if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0) {                 String oldName = mSettings.mRenamedPackages.get(pkgName);                 if (pkg.mOriginalPackages != null                         && pkg.mOriginalPackages.contains(oldName)                         && mPackages.containsKey(oldName)) {                     // This package is derived from an original package,                     // and this device has been updating from that original                     // name.  We must continue using the original name, so                     // rename the new package here.                     pkg.setPackageName(oldName);                     pkgName = pkg.packageName;                     replace = true;                 } else if (mPackages.containsKey(pkgName)) {                     // This package, under its official name, already exists                     // on the device; we should replace it.                     replace = true;                 }             }             PackageSetting ps = mSettings.mPackages.get(pkgName);             if (ps != null) {                 oldCodePath = mSettings.mPackages.get(pkgName).codePathString;                 if (ps.pkg != null && ps.pkg.applicationInfo != null) {                     systemApp = (ps.pkg.applicationInfo.flags &                             ApplicationInfo.FLAG_SYSTEM) != 0;                 }             }         }         if (systemApp && onSd) {             // Disable updates to system apps on sdcard             Slog.w(TAG, "Cannot install updates to system apps on sdcard");             res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;             return;         }         if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {             res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;             return;         }         // Set application objects path explicitly after the rename         setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());         pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath();         if (replace) {             replacePackageLI(pkg, parseFlags, scanMode,                     installerPackageName, res);         } else {             installNewPackageLI(pkg, parseFlags, scanMode,                     installerPackageName,res);         }     }   ~~~ 最后判断如果以前不存在那么调用installNewPackageLI() ~~~ private void installNewPackageLI(PackageParser.Package pkg,               int parseFlags,int scanMode,               String installerPackageName, PackageInstalledInfo res) {        ***********************省略若干*************************************************           PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,                  System.currentTimeMillis());        ***********************省略若干**************************************************     }   ~~~ 最后终于回到了和开机安装一样的地方.与开机方式安装调用统一方法。 三、从ADB工具安装  其入口函数源文件为pm.java  (源文件路径:android\frameworks\base\cmds\pm\src\com\android\commands\pm\pm.java) 其中\system\framework\pm.jar 包管理库 包管理脚本 \system\bin\pm 解析 showUsage就是使用方法 ~~~ private static void showUsage() { System.err.println("usage: pm [list|path|install|uninstall]"); System.err.println(" pm list packages [-f]"); System.err.println(" pm list permission-groups"); System.err.println(" pm list permissions [-g] [-f] [-d] [-u] [GROUP]"); System.err.println(" pm list instrumentation [-f] [TARGET-PACKAGE]"); System.err.println(" pm list features"); System.err.println(" pm path PACKAGE"); System.err.println(" pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH"); System.err.println(" pm uninstall [-k] PACKAGE"); System.err.println(" pm enable PACKAGE_OR_COMPONENT"); System.err.println(" pm disable PACKAGE_OR_COMPONENT"); System.err.println(" pm setInstallLocation [0/auto] [1/internal] [2/external]"); **********************省略************************** } ~~~ 安装时候会调用 runInstall()方法 ~~~ private void runInstall() { int installFlags = 0; String installerPackageName = null; String opt; while ((opt=nextOption()) != null) { if (opt.equals("-l")) { installFlags |= PackageManager.INSTALL_FORWARD_LOCK; } else if (opt.equals("-r")) { installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; } else if (opt.equals("-i")) { installerPackageName = nextOptionData(); if (installerPackageName == null) { System.err.println("Error: no value specified for -i"); showUsage(); return; } } else if (opt.equals("-t")) { installFlags |= PackageManager.INSTALL_ALLOW_TEST; } else if (opt.equals("-s")) { // Override if -s option is specified. installFlags |= PackageManager.INSTALL_EXTERNAL; } else if (opt.equals("-f")) { // Override if -s option is specified. installFlags |= PackageManager.INSTALL_INTERNAL; } else { System.err.println("Error: Unknown option: " + opt); showUsage(); return; } } String apkFilePath = nextArg(); System.err.println("\tpkg: " + apkFilePath); if (apkFilePath == null) { System.err.println("Error: no package specified"); showUsage(); return; } PackageInstallObserver obs = new PackageInstallObserver(); try { mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags, installerPackageName); synchronized (obs) { while (!obs.finished) { try { obs.wait(); } catch (InterruptedException e) { } } if (obs.result == PackageManager.INSTALL_SUCCEEDED) { System.out.println("Success"); } else { System.err.println("Failure [" + installFailureToString(obs.result) + "]"); } } } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(PM_NOT_RUNNING_ERR); } } ~~~ 其中的        PackageInstallObserver obs = new PackageInstallObserver();                      mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,                     installerPackageName); 如果安装成功 obs.result == PackageManager.INSTALL_SUCCEEDED) 又因为有 IPackageManage mPm;         mPm = IpackageManager.Stub.asInterface(ServiceManager.getService("package")); Stub是接口IPackageManage的静态抽象类,asInterface是返回IPackageManager代理的静态方法。 因为class PackageManagerService extends IPackageManager.Stub 所以mPm.installPackage 调用      /* Called when a downloaded package installation has been confirmed by the user */     public void installPackage(             final Uri packageURI, final IPackageInstallObserver observer, final int flags,final String installerPackageName)  这样就是从网络下载安装的入口了。 四,从SD卡安装 系统调用PackageInstallerActivity.java(/home/zhongda/androidSRC/vortex-8inch-for-hoperun/packages/apps/PackageInstaller/src/com/android/packageinstaller) 进入这个Activity会判断信息是否有错,然后调用       private void initiateInstall()判断是否曾经有过同名包的安装,或者包已经安装 通过后执行private void startInstallConfirm() 点击OK按钮后经过一系列的安装信息的判断Intent跳转到 ~~~ public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener public void onCreate(Bundle icicle) { super.onCreate(icicle); Intent intent = getIntent(); mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); mPackageURI = intent.getData(); initView(); } ~~~ 方法中调用了initView()方法 ~~~ public void initView() { requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.op_progress); int installFlags = 0; PackageManager pm = getPackageManager(); try { PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName, PackageManager.GET_UNINSTALLED_PACKAGES); if(pi != null) { installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; } } catch (NameNotFoundException e) { } if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) { Log.w(TAG, "Replacing package:" + mAppInfo.packageName); } PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, mAppInfo, mPackageURI); mLabel = as.label; PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet); mStatusTextView = (TextView)findViewById(R.id.center_text); mStatusTextView.setText(R.string.installing); mProgressBar = (ProgressBar) findViewById(R.id.progress_bar); mProgressBar.setIndeterminate(true); // Hide button till progress is being displayed mOkPanel = (View)findViewById(R.id.buttons_panel); mDoneButton = (Button)findViewById(R.id.done_button); mLaunchButton = (Button)findViewById(R.id.launch_button); mOkPanel.setVisibility(View.INVISIBLE); String installerPackageName = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME); PackageInstallObserver observer = new PackageInstallObserver(); pm.installPackage(mPackageURI, observer, installFlags, installerPackageName); } ~~~ 方法最后我们可以看到再次调用安装接口完成安装。
';

框架层理解Activity生命周期(APP启动过程)

最后更新于:2022-04-01 09:53:35

http://m.blog.csdn.net/blog/yhc13429826359/19977429 ## 1生命周期图 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7d55bda.png) ## 2主要类图调用 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7d6f980.jpg) 上面类图关系中包含两个进程,一个是应用程序进程,另一个是AMS进程,所以会涉及到进程间通信,android进程间通信用的是Binder通信。 ### 2.1客户进程 ØActivityThread 可以看到该类有一个main方法,其实它是android一个应用程序的入口,每启动一个应用进程,都会创建ActivityThread与之对应的实例,是应用程序的UI线程,Android进程启动时会建立消息循环。负责管理应用程序的生命周期,执行系统广播及其ActivityManagerService请求执行的操作。属于客户端对象。 ØApplicationThread&ApplicatinThreadNative ApplicationThread用来实现ActivityManagerService与ActivityThread之间的交互。在ActivityManagerService需要管理相关Application中的Activity的生命周期时,通过ApplicationThread与ActivityThread通讯,ApplicationThreadNative是ApplicationThread在客户端的实现。 ØApplicationThreadProxy ApplicationThreadProxy是ApplicationThread在服务器端的代理。负责和服务器端的ApplicatingThreadNative通讯。 AMS就是通过该代理与ActivityThread进行通信的。 ØActivity& Intrumentation Activity是应用程序真正做事情的类,每一个应用程序只有一个Instrumentation对象,每个Activity内都有一个对该对象的引用。Instrumentation可以理解为应用进程的管家,ActivityThread要创建或暂停某个Activity时,都需要通过Instrumentation。通俗的理解,Instrumentation与ActivityThread的区别,前者像是一个“家庭”里的“管家”,后者是负责创建这个“家庭”,并负责对外打交道,比如接收AMS的通知等。 ### 2.2 AMS进程 这里说的AMS进程,实际指的是System_server进程,System_server进程起来的时候启动AMS服务,AMS实际是ActivityManagerService的缩写。 ØActivityManagerService 管理Activity的生命周期 ØActivityManagerNative ActivityManagerService在服务器端的实现,客户端的请求调用ActivityManagerProxy后,通过IBinder,最终会在ActivityManagerNative中实现。ActivityManagerNative再通过调用ActivityManagerService的相关功能,以完成客户端请求。 ØActivityManagerProxy ActivityManagerService的在客户端的代理。负责和服务器端的ActivityManagerNative通讯。 ØActivityStack Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。 ØActivityRecord ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord来记录Activity的状态以及其他的管理信息。 ØTaskRecord AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。 ØProcessRecord 一个Apk文件运行时会对应一个进程,ProcessRecord正是记录一个进程中的相关信息。 ## 3startActivity流程 在Android系统中,应用程序是由Activity组成的,因此,应用程序的启动过程实际上就是应用程序中的默认Activity的启动过程。启动Android应用程序中的Activity的两种情景,第一,在android设备屏幕中点击应用程序图标的情景就会引发Android应用程序中的默认Activity的启动,从而把应用程序启动起来,这种启动方式的特点是会启动一个新的进程来加载相应的Activity。第二,应用程序内部启动非默认Activity的过程的源代码,这种非默认Activity一般是在原来的进程和任务中启动的。在Android的Activity管理机制中,当退出Activity的时候,在某些情况下并没有立即把该Activity杀死,而是将其暂时保存起来,当第二次调用startActivity启动该Activity的时候,就不需要再创建该Activity的实例,直接恢复Activity即可。 ### 3.1调用流程图 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7d8a671.jpg) 对用户来讲,启动一个Activity有以下几种方式: Ø在应用程序中调用startActivity()启动指定的Activity Ø在Home程序中点击一个应用图标,启动新的Activity Ø按“Back”键,结束当前Activity,自动启动上一个Activity Ø长按“Home”键,显示当前列表中,从中选则一个启动 对于AMS内部讲,启动一个Activity有三种方式,如上图中的①②③分支: ①目标Activity的对象已经存在,那么直接resume该Activity ②目标Activity所在的进程不存在,那么需要创建进程,并在新的进程中启动该Activity ③目标Activity所在进程已经存在,那么直接在已存在进程中启动该Activity ### 3.2在新的进程中启动 以在Home程序中点击一个应用图标,启动MainActivity为例子,介绍如下。 时序图如下图: [![框架层理解Activity生命周期](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-30_5632e1cc04fc3.gif "框架层理解Activity生命周期")](http://photo.blog.sina.com.cn/showpic.html#blogid=89f592f501014ayc&url=http://s4.sinaimg.cn/orignal/89f592f5gcdfcee0f5513) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7da4b63.jpg) 以上时序图包含35步骤调用,下面逐一讲解: ### 3.2.1(1~4),Launcher中发送startActivity请求 在Android系统中,应用程序是由Launcher启动起来的,其实,Launcher本身也是一个应用程序,其它的应用程序安装后,就会Launcher的界面上出现一个相应的图标,点击这个图标时,Launcher就会对应的应用程序启动起来。 Launcher继承与Activity,Activity类的有个成员变量mInstrumentation是,它的类型是Intrumentation,它用来监控应用程序和系统的交互。 Instrumentation.execStartActivity: <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="549" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px">publicActivityResult execStartActivity(</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">Context who, IBinder contextThread, IBinder token, Activitytarget,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">Intent intent, int requestCode, Bundle options) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">IApplicationThread whoThread = (IApplicationThread)contextThread;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">try {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">intent.setAllowFds(false);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">intent.migrateExtraStreamToClip<wbr style="padding:0px; margin:0px">Data();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">int result = ActivityManagerNative.getDefault()</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">.startActivity(whoThread, intent,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">intent.resolveTypeIfNeeded(who.getContentResolver()),</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">token, target != null ? target.mEmbeddedID : null,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">requestCode, 0, null, null, options);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">checkStartActivityResult<wbr style="padding:0px; margin:0px">(result, intent);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} catch (RemoteException e) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return null;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> 这里的ActivityManagerNative.getDefault返回ActivityManagerService的远程接口,即ActivityManagerProxy接口。 ### 3.2.2(5-8) AMS接收客户端startActivity请求 客户端通过Binder调用,最终调用到ActivityStack.startActivityLocked: <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px">final intstartActivityLocked(IApplicationThread caller,</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">Intent intent, String resolvedType, ActivityInfo aInfo, IBinderresultTo,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">String resultWho, int requestCode,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">int callingPid, int callingUid, int startFlags, Bundleoptions,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">boolean componentSpecified, ActivityRecord[] outActivity){</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ProcessRecord callerApp = null;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (caller != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">callerApp = mService.getRecordForAppLocked(caller);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (callerApp != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">callingPid = callerApp.pid;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">callingUid = callerApp.info.uid;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} else {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">….</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord sourceRecord = null;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord resultRecord = null;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">….</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">int launchFlags = intent.getFlags();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">….</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if((launchFlags&amp;Intent.FLAG_ACTIVITY_FORWARD_RESULT)!= 0</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">&amp;&amp; sourceRecord != null){</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Transfer the result target from the source activity to thenew</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// one being started, including any failures.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">…….</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord r = new ActivityRecord(mService, this, callerApp,callingUid,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">intent, resolvedType, aInfo, mService.mConfiguration,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">resultRecord, resultWho, requestCode,componentSpecified);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (outActivity != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">outActivity[0] = r;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">err = startActivityUncheckedLo<wbr style="padding:0px; margin:0px">cked(r, sourceRecord,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">startFlags, true, options);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return err;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> startActivityLock()主要做了一下几件事: ①处理传进来的参数caller,得到调用者的进程信息,并保存在callerApp变量中,这里就是Launcher应用程序的进程信息了。 ②处理FLAG_ACTIVITY_FORWARD_RESULT标志。该标志的特殊作用,就是能跨Activity传Result,比如A1->A2,A2带该标志启动A3,那么A3调用setResult,然后finish(),结果将直接返回到A1 ③创建一个临时的ActivityRecord对象,该对象只为了后面调用过程中的各种对比,不一定会最终加入到mHistory列表中。 ④判断mPendingActivityLaunches列表是否有等待的Activity要启动,如果有先启动等待的Activity ⑤调用startActivityUncheckedLocked()方法。此时要启动的Activity已经通过检验,被人认为是一个正当的启动请求。 ### 3.2.3(9)创建新的Task 调用ActivityStack.startActivityUncheckedLocked()处理Task问题,因为这里我们是新启动一个apk,所以将创建新的Task,newTask=true,并调用ActivityStack.startActivityLoacked(): <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px">privatefinal void startActivityLocked(ActivityRecord r, booleannewTask,</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">boolean doResume, boolean keepCurTransition) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (!newTask) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">…….</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Place a new activity at top of stack, so it is next tointeract</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// with the user.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (addPos &lt; 0) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">addPos = NH;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mHistory.add(addPos, r);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">r.putInHistory();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">r.frontOfTask = newTask;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (doResume) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">resumeTopActivityLocked(null);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> 注意AtivityStack中有两个startActivityLoacked()方法,这里调用的是带四个参数的,即startActivityLocked(ActivityRecord r, booleannewTask,boolean doResume, booleankeepCurTransition),其中,r为将要启动的Activity,newTask=true,doResume=true,在这个方法中,将r放到mHistory的最后面doResume=true,所以调用resumeTopActivityLocked(null)。关于Task的概念比较复杂,这里先不讲解。 ### 3.2.4:(10)运行mHistory中最后一个ActivityRecord ActivityStack. resumeTopActivityLocked(null) <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px">finalboolean resumeTopActivityLocked(ActivityRecord prev) {</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Find the first activity that is not finishing.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord next = topRunningActivityLocked<wbr style="padding:0px; margin:0px">(null);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (next == null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// There are no more activities! <wbr style="padding:0px; margin:0px">Let's just startup the</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Launcher...</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mMainStack) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return mService.startHomeActivityLocked();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">next.delayedResume = false;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"/></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// If the top activity is the resumed one, nothing todo.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mResumedActivity == next &amp;&amp;next.state == ActivityState.RESUMED) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Make sure we have executed any pending transitions, sincethere</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// should be nothing left to do at this point.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mService.mWindowManager.executeAppTransition();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mNoAnimActivities.clear();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return false;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// If we are sleeping, and there is no resumed activity, and thetop</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// activity is paused, well that is the state we want.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if ((mService.mSleeping || mService.mShuttingDown)</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">&amp;&amp; mLastPausedActivity == next&amp;&amp; next.state ==ActivityState.PAUSED) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Make sure we have executed any pending transitions, sincethere</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// should be nothing left to do at this point.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mService.mWindowManager.executeAppTransition();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mNoAnimActivities.clear();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">returnfalse;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"/></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// The activity may be waiting for stop, but that is nolonger</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// appropriate for it.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mStoppingActivities.remove(next);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mGoingToSleepActivities.remove(next);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">next.sleeping = false;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mWaitingVisibleActivitie<wbr style="padding:0px; margin:0px">s.remove(next);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mPausingActivity != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: pausing=" +mPausingActivity);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return false;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">…..</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// We need to start pausing the current activity so the topone</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// can be resumed...</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mResumedActivity != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to startpausing");</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">startPausingLocked(userLeaving, false);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……..</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> 调用resumeTopActivityLocked(null)启动真正的Activity。 ①调用topRunningActivityLocked()方法取出当前正在运行的ActivityRecord对象 ②判断mHistory中是否有记录,如果没有就意味着还没有启动任何的Activity,需要首先调用mService.startHomeActivityLocked()方法启动所谓的“主界面程序”。当然我们这里mHistroy已经有记录了。 ③判断正在执行的Activity是否和目标Activity一样,如果一样,则直接返回。 ④判断当前系统是否处于休眠涨停,如果是,则返回。这里继续往下执行。 ⑤从mStoppingActivities、mWaitingVisibleActivities和mGoingToSleepActivities中删除目标对象,因为接下来将要被启动。 ⑥判断当前是否在暂停某个Activity,如果是则还不能运行。这里mPausingActivity=null,所以继续往下执行。 ⑦判断当前是否有Activity在运行,如果有则先需要暂停当前的Activity。因为我们是在Lancher中启动mainActivity,所以当前mResumedActivity!=null,所有调用startPausingLocked(userLeaving, false); ### 3.2.5(11~16)暂停当前运行Activity ①调用ActivityStack.startPausingLocked()暂停当前Activity。 ②判断运行当前Activity的进程是否存在。在这里 if (prev.app != null&& prev.app.thread !=null)为真。其中,prev.app为记录启动Lancher进程的ProcessRecord,prev.app.thread为Lancher进程的远程调用接口IApplicationThead,所以可以调用prev.app.thread.schedulePauseActivity,到Lancher进程暂停指定Activity。 ③在Lancher进程中消息传递,调用ActivityThread.handlePauseActivity(),最终调用ActivityTHread.performPauseActivity暂停指定Activity。接着通过Binder通信,通知AMS已经完成暂停ActivityManagerNative.getDefault().activityPaused(token). ### 3.2.6(17~20) AMS处理暂停Activity事情 在Launcher通过Binder进程间通信机制通知AMS,它已经准备就绪进入Paused状态,在ActivityStack.completePauseLocked()中完成暂停: <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">private final void completePauseLocked(){</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord prev = mPausingActivity;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"/></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (prev != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (prev.finishing) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">prev = finishCurrentActivityLoc<wbr style="padding:0px; margin:0px">ked(prev,FINISH_AFTER_VISIBLE);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} else if (prev.app != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (prev.configDestroy) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">destroyActivityLocked(prev, true, false,"pause-config");</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} else {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mStoppingActivities.add(prev);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mStoppingActivities.size() &gt; 3) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// If we already have a few activities waiting to stop,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// then give up on things going idle and start clearing</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// them out.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcingidle");</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">scheduleIdleLocked();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} else {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">checkReadyForSleepLocked<wbr style="padding:0px; margin:0px">();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} else {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping:" + prev);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">prev = null;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mPausingActivity = null;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (!mService.isSleeping()) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">resumeTopActivityLocked(prev);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} else {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">checkReadyForSleepLocked<wbr style="padding:0px; margin:0px">();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> ①给prev赋值mPausingActivity,即上一个被执行的Activity,即Launcer ②如果prev的finishing为true,说明上一个Activity已经完成,因此需要调用finishCurrentActivityLocked()执行相关操作。一般的流程不会为true,这个条件似乎只有内存回收的时候才会被执行。 ③将mPausingActivity变量置为空 ④调用resumeTopActivityLocked方法正式启动目标Activity,即MainActivity ### 3.2.7:(21~23)正式启动目标Activity 调用AcivityStack.resumeTopActivityLocked: <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">final booleanresumeTopActivityLocked(ActivityRecord prev) {</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord next = topRunningActivityLocked<wbr style="padding:0px; margin:0px">(null);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mResumedActivity != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to startpausing");</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">startPausingLocked(userLeaving, false);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (next.app != null &amp;&amp;next.app.thread != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">startSpecificActivityLoc<wbr style="padding:0px; margin:0px">ked(next, true, true);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> ①该方法在3.2.4步骤中调用过,那时是因为mResumedActivity != null,有Activity正在运行,所以去执行了startPausingLocked暂停Laucher去了。这时候,mResumedActivity=null,所以继续往下执行。 ②判断讲要启动的Activity的客户进程是否存在,这里next.app != null &&next.app.thread != null为false ③调用ActivityStack.startSpecificActivityLocked <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">private final voidstartSpecificActivityLoc<wbr style="padding:0px; margin:0px">ked(ActivityRecord r,</wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">boolean andResume, boolean checkConfig) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Is this activity's application already running?</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ProcessRecord app =mService.getProcessRecordLocked(r.processName,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">r.info.applicationInfo.uid);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (app != null &amp;&amp; app.thread !=null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">realStartActivityLocked(r, app, andResume, checkConfig);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden; text-indent:112px"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">return;</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mService.startProcessLocked(r.processName, r.info.applicationInfo,true, 0,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">"activity", r.intent.getComponent(), false);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> ④客户进程不存在,app!= null && app.thread !=null为false,所以调用mService.startProcessLocked() fork一个新的进程。 ### 3.2.8(24) fork一个新的进程 ①AMS通过Socket通信,向Zygote发送一个创建进程请求,Zygote创建新进程。 ②创建好进程后,调用ActivityThread.main()。到此,我们到了新了一个进程中,也是程序的入口出。 ③调用ActivityThread.attach()开始新的应用程序,接着同过Binder通信通知AMS,新的进程已经创建好了,可以开始新的程序了。 ### 3.2.9(26~28) AMS准备执行目标Activity 目标进程启动后,报告给AMS,自己已经启动完毕可以启动Activity了,这里通过IPC调用AMS的attachApplication方法完成。 ActivityManagerService.attachApplication(): <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">public final voidattachApplication(IApplicationThread thread) {</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">synchronized (this) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">int callingPid = Binder.getCallingPid();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">final long origId = Binder.clearCallingIdentity();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">attachApplicationLocked(thread,callingPid);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">Binder.restoreCallingIdentity(origId);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> ①根据Binder.getCallingPid(),或得客户进程pid,并调用attachApplicationLocked(IApplicationThreadthread,int pid) ②在attachApplicationLocked中,根据pid找到对应的ProcessRecord对象,如果找不到说明改pid客户进程是一个没经过AMS允许的进程。 <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">private final booleanattachApplicationLocked(IApplicationThread thread,</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">int pid) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ProcessRecord app;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (pid != MY_PID &amp;&amp; pid&gt;= 0) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">synchronized (mPidsSelfLocked) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app = mPidsSelfLocked.get(pid);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (app == null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">returnfalse;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// If this application record is still attached to aprevious</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// process, clean it up now.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (app.thread != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">handleAppDiedLocked(app, true, true);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.thread = thread;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.curAdj = app.setAdj = -100;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.setSchedGroup =Process.THREAD_GROUP_BG_NONINTERACTIVE;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.forcingToForeground = null;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.foregroundServices = false;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.hasShownUi = false;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.debugging = false;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ensurePackageDexOpt(app.instrumentationInfo != null</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">? app.instrumentationInfo.packageName</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">: app.info.packageName);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// See if the top visible activity is waiting to run in thisprocess...</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord hr =mMainStack.topRunningActivityLocked<wbr style="padding:0px; margin:0px">(null);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (hr != null &amp;&amp; normalMode){</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (hr.app == null &amp;&amp; app.info.uid== hr.info.applicationInfo.uid</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">&amp;&amp;processName.equals(hr.processName)) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mMainStack.realStartActivityLocked(hr, app, true,true))</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> ③为ProcessRecordapp对象内部变量赋值 ④确保目标程序(APK)文件已经被转换为了odex文件。Android中安装程序是APK文件,实际上是一个zip文件。 ⑤调用ActivityStack.realStartActivityLocked通知客户进程运行指定Activity. ⑥调用ApplicationThread.scheduleLaunchActivity,启动指定Activity。 ### 3.2.10:(29~35)客户进程启动指定Activity AMS通过IPC通行,通知客户进程启动指定Activity ①调用ApplicationThread.scheduleLaunchActivity ②经过Handler消息传动,调用ActivityThread.handleLaunchActivity() ③调用ActivityThread.performLaunchActivity()完成Activity的加载,并最终调用Activity生命周期的onCreate()方法 ④performLaunchActivity返回,继续调用ActivityThread.handleResumeActivity(),该方法内部又调用ActivityThread.performResumeActivity(),其内部仅仅调用了目标Activity的onResume()方法。到此Activity启动完成。 ⑤添加一个IdleHandler对象,因为在一般情况下,该步骤执行完毕后,Activity就会进入空闲状态,所以就可以进行内存回收。 ### 3.3在已有进程中启动 在已有的进程中启动Activity,也就是在一个应用程序中启动内部Activity,其过程跟3.2小节大致一样,这里我们不会像3.2小节详细分析每一步骤,我们只看差别的地方。这里以启动subActivity为例子。时序图如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7dbf647.jpg) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7dd58f7.jpg) 以上时序图包含29步骤调用,下面逐一讲解: ### 3.3.1(1~3)在MainActivity启动Activity 这一步跟3.2.1小节一样 ### 3.3.2(4~7) AMS接收客户端startActivity请求 这一步跟3.2.2小节一样 ### 3.3.3(8)不需要创建新的Task 调用ActivityStack.startActivityUncheckedLocked()处理Task问题,因为这里我们是在已有应用中startActivity,也不设置标志要在新的Task中启动Activity,所以不创建新的Task,newTask=false,并调用 ActivityStack.startActivityLoacked(): <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px">privatefinal void startActivityLocked(ActivityRecord r, booleannewTask,</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">boolean doResume, boolean keepCurTransition) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (!newTask) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">…….</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Place a new activity at top of stack, so it is next tointeract</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// with the user.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (addPos &lt; 0) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">addPos = NH;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mHistory.add(addPos, r);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">r.putInHistory();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">r.frontOfTask = newTask;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (doResume) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">resumeTopActivityLocked(null);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> 注意AtivityStack中有两个startActivityLoacked()方法,这里调用的是带四个参数的,即startActivityLocked(ActivityRecord r, booleannewTask,boolean doResume, booleankeepCurTransition),其中,r为将要启动的Activity,newTask=false,doResume=true,在这个方法中,将r放到mHistory的最后面doResume=true,所以调用resumeTopActivityLocked(null)。 ### 3.3.4(9)准备启动mHistory中最后一个Activity 这一步跟3.2.4小节一样 ### 3.3.5(10~15)暂停MainActivity 这一步跟3.2.5小节一样 ### 3.3.6(16~19) AMS处理暂停MainActivity 这一步跟3.2.6小节一样 ### 3.3.7(20~22)正式启动目标Activity 调用AcivityStack.resumeTopActivityLocked: <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">final booleanresumeTopActivityLocked(ActivityRecord prev) {</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord next = topRunningActivityLocked<wbr style="padding:0px; margin:0px">(null);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mResumedActivity != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to startpausing");</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">startPausingLocked(userLeaving, false);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (next.app != null &amp;&amp;next.app.thread != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">startSpecificActivityLoc<wbr style="padding:0px; margin:0px">ked(next, true, true);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> ①调用startSpecificActivityLocked(next, true,true) <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">private final voidstartSpecificActivityLoc<wbr style="padding:0px; margin:0px">ked(ActivityRecord r,</wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">boolean andResume, boolean checkConfig) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Is this activity's application already running?</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ProcessRecord app =mService.getProcessRecordLocked(r.processName,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">r.info.applicationInfo.uid);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (app != null &amp;&amp; app.thread !=null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">realStartActivityLocked(r, app, andResume, checkConfig);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden; text-indent:112px"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体">return;</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mService.startProcessLocked(r.processName, r.info.applicationInfo,true, 0,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">"activity", r.intent.getComponent(), false);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px; font-family:宋体"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> ②subActivity进程已经存在,app != null&& app.thread !=null为true,所以调用realStartActivityLocked。 <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px">finalboolean realStartActivityLocked(ActivityRecord r,</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ProcessRecord app, boolean andResume, booleancheckConfig)</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">throws RemoteException {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">…..</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">app.thread.scheduleLaunchActivity(new Intent(r.intent),r.appToken,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">System.identityHashCode(r), r.info,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">new Configuration(mService.mConfiguration),</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">r.compat, r.icicle, results, newIntents,!andResume,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mService.isNextTransitionForward(), profileFile,profileFd,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">profileAutoStop);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}<wbr style="padding:0px; margin:0px"/></wbr></wbr></wbr></span></p></td></tr></tbody></table> ③调用ApplicationThread.scheduleLaunchActivity,启动指定Activity。 ### 3.3.8(23~29)客户进程启动指定Activity 这一步跟3.2.10是一样的 ### 3.4在已有的ActivityRecord中恢复指定Activity 经过上面3.2和3.3小节,现在对Activity的启动流程应该是比较清晰的了,这一节就简单的讲下恢复Activity的流程。当ActivityRecord已经记录有一个Activity,如果再次调用startActivity,并没有标志要创建Activity新的实例,那么就可以直接恢复该Activity。 ①启动一个Activity,跟前面3.2节一样,都需要暂停当前正在运行的Activity,暂停流程这里就不讲了,完成暂停后,调用ActivityStack.resumeTopActivityLocked()。 ②因为AMS和ActivityTHread的IPC通信,resumeTopActivityLocked会被反复调用几次,每次都会根据一些变量值的差异,走不同的流程。 <table cellspacing="0" cellpadding="0" style="padding:0px; margin:0px auto 10px; font-size:12px; border-collapse:collapse; color:rgb(85,85,85); font-family:宋体,'Arial Narrow',arial,serif"><tbody style="padding:0px; margin:0px"><tr style="padding:0px; margin:0px"><td valign="top" width="568" style="padding:0cm 7px; margin:0px; border:1px solid rgb(0,0,0)"><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px">finalboolean resumeTopActivityLocked(ActivityRecord prev) {</span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Find the first activity that is not finishing.</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">ActivityRecord next = topRunningActivityLocked<wbr style="padding:0px; margin:0px">(null);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (mResumedActivity != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to startpausing");</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">startPausingLocked(userLeaving, false);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (next.app != null &amp;&amp;next.app.thread != null) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " +next);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">try {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">next.app.thread.scheduleResumeActivity(next.appToken,</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">mService.isNextTransitionForward());</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"/></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">checkReadyForSleepLocked<wbr style="padding:0px; margin:0px">();</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"/></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} catch (Exception e) {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">// Whoops, need to restart this activity!</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">startSpecificActivityLoc<wbr style="padding:0px; margin:0px">ked(next, true, false);</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">} else {</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">……</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">return true;</wbr></wbr></wbr></wbr></wbr></wbr></wbr></span></p><p style="padding-top:0px; padding-bottom:0px; margin-top:0px; margin-bottom:0px; clear:both; height:auto; overflow:hidden"><span lang="EN-US" style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px"><wbr style="padding:0px; margin:0px">}</wbr></wbr></wbr></span></p></td></tr></tbody></table> ③这里,mResumedActivity = null,不走暂停流程。 ④next.app != null &&next.app.thread != null为true,调用ApplicationThead.scheduleResumeActivity(),到客户进程恢复指定Activity。 ⑤经过消息传递,调用ActivityTHread.handleResumeActivity() ⑥调用ActivityTHread.performResumeActivity()正在恢复Activity,接着回调Activity的onResume()方法。 ## 4 stop停止Activity 前面几节汇总,A启动到B时,需要先暂停A,然后再启动B。什么时候停止(stop)或者销毁(Destory)呢? ### 4.1从暂停到停止全过程 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7deabb2.jpg) 4按Home键回到桌面 5按Back键回到上一个Activity 6长按Home键
';

Activity界面显示全解析

最后更新于:2022-04-01 09:53:32

> 前几天凯子哥写的Framework层的解析文章[《Activity启动过程全解析》](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287),反响还不错,这说明“写让大家都能看懂的Framework解析文章”的思想是基本正确的。 > 我个人觉得,深入分析的文章必不可少,但是对于更多的Android开发者——即只想做应用层开发,不想了解底层实现细节——来说,“整体上把握,重要环节深入“是更好的学习方式。因为这样既可以有完整的知识体系,又不会在浩瀚的源码世界里迷失兴趣和方向。 > 所以呢,今天凯子哥又带来一篇文章,接着上一篇的结尾,重点介绍Activity开启之后,Android系统对界面的一些操作及相关知识。 - [本期关键字](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#本期关键字) - [学习目标](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#学习目标) - [写作方式](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#写作方式) - [进入正题](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#进入正题) - [onCreate中的setContentView到底做了什么为什么不能在setContentView之后设置某些Window属性标志](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#oncreate中的setcontentview到底做了什么为什么不能在setcontentview之后设置某些window属性标志) - [Activity中的findViewById本质上是在做什么](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#activity中的findviewbyid本质上是在做什么) - [Window和PhoneWindow是什么关系WindowManager是做什么的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#window和phonewindow是什么关系windowmanager是做什么的) - [Activity中Window类型的成员变量mWindow是什么时候初始化的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#activity中window类型的成员变量mwindow是什么时候初始化的) - [PhoneWindowsetContentView到底发生了什么](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#phonewindowsetcontentview到底发生了什么) - [如何验证上一个问题](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#如何验证上一个问题) - [我们通过setContentView设置的界面为什么在onResume之后才对用户可见呢](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#我们通过setcontentview设置的界面为什么在onresume之后才对用户可见呢) - [ViewManagerWindowManagerWindowManagerImplWindowManagerGlobal到底都是些什么玩意](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#viewmanagerwindowmanagerwindowmanagerimplwindowmanagerglobal到底都是些什么玩意) - [ViewRootImpl是什么有什么作用ViewRootImpl如何与WMS通信](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#viewrootimpl是什么有什么作用viewrootimpl如何与wms通信) - [从什么时候开始绘制整个Activity的View树的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#从什么时候开始绘制整个activity的view树的) - [Window的类型有几种分别在什么情况下会使用到哪一种](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#window的类型有几种分别在什么情况下会使用到哪一种) - [为什么使用PopWindow的时候不设置背景就不能触发事件](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#为什么使用popwindow的时候不设置背景就不能触发事件) - [在Activity中使用Dialog的时候为什么有时候会报错Unable to add window token is not valid is your activity running](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#在activity中使用dialog的时候为什么有时候会报错unable-to-add-window-token-is-not-valid-is-your-activity-running) - [为什么Toast需要由系统统一控制在子线程中为什么不能显示Toast](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#为什么toast需要由系统统一控制在子线程中为什么不能显示toast) - [结语](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#结语) - [参考文章](http://blog.csdn.net/zhaokaiqiang1992/article/details/49681321#参考文章) ## 本期关键字 - Window - PhoneWindow - WindowManager - WindowManagerImpl - WindowManagerGlobal - RootViewImpl - DecorView - Dialog - PopWindow - Toast ## 学习目标 - 了解Android中Activity界面显示的流程,涉及到的关键类,以及关键流程 - 解决在开发中经常遇到的问题,并在源码的角度弄清楚其原因 - 了解Framework层与Window相关的一些概念和细节 ## 写作方式 老样子,咱们还是和上次一样,采用一问一答的方式进行学习,毕竟“带着问题学习”才是比较高效的学习方式。 ## 进入正题 话说,在上次的文章中,我们解析到了从手机开机第一个zygote进程开启,到App的第一个Activity的onCreate()结束,那么我们这里就接着上次留下的茬,从第一个Activity的onCreate()开始说起。 ### onCreate()中的setContentView()到底做了什么?为什么不能在setContentView()之后设置某些Window属性标志? 一个最简单的onCreate()如下: ~~~ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } ~~~ 通过上面几行简单的代码,我们的App就可以显示在activity_main.xml文件中设计的界面了,那么这一切到底是怎么做到的呢? 我们跟踪一下源码,然后就在Activity的源码中找到了3个setContentView()的重载函数: ~~~ public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } public void setContentView(View view) { getWindow().setContentView(view); initWindowDecorActionBar(); } public void setContentView(View view, ViewGroup.LayoutParams params) { getWindow().setContentView(view, params); initWindowDecorActionBar(); } ~~~ 我们上面用到的就是第一个方法。虽然setContentView()的重载函数有3种,但是我们可以发现,内部做的事情都是基本一样的。首先是调用 getWindow()获取到一个对象,然后调用了这个对象的相关方法。咱们先来看一下,getWindow()到底获取到了什么对象。 ~~~ private Window mWindow; public Window getWindow() { return mWindow; } ~~~ 喔,原来是一个Window对象,你现在可能不知道Window到底是个什么玩意,但是没关系,你只要能猜到它肯定和咱们的界面现实有关系就得了,毕 竟叫“Window”么,Windows系统的桌面不是叫“Windows”桌面么,差不多的东西,反正是用来显示界面的就得了。 那么initWindowDecorActionBar()函数是做什么的呢? 写了这么多程序,看名字也应该能猜出八九不离十了,init是初始化,Window是窗口,Decor是装饰,ActionBar就更不用说了,所以这个方法应该就是”初始化装饰在窗口上的ActionBar”,来,咱们看一下代码实现: ~~~ /** * Creates a new ActionBar, locates the inflated ActionBarView, * initializes the ActionBar with the view, and sets mActionBar. */ private void initWindowDecorActionBar() { Window window = getWindow(); // Initializing the window decor can change window feature flags. // Make sure that we have the correct set before performing the test below. window.getDecorView(); if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) { return; } mActionBar = new WindowDecorActionBar(this); mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); mWindow.setDefaultIcon(mActivityInfo.getIconResource()); mWindow.setDefaultLogo(mActivityInfo.getLogoResource()); } ~~~ 哟,没想到这里第一行代码就又调用了getWindow(),接着往下调用了window.getDecorView(),从注释中我们知道,在调用这个方法之后,Window 的特征标志就被初始化了,还记得如何让Activity全屏吗? ~~~ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FILL_PARENT, WindowManager.LayoutParams.FILL_PARENT); setContentView(R.layout.activity_main); } ~~~ 而且这两行代码必须在setContentView()之前调用,知道为啥了吧?因为在这里就把Window的相关特征标志给初始化了,在setContentView()之后调 用就不起作用了! 如果你还不确定的话,我们可以再看下window.getDecorView()的部分注释 ~~~ /** * Note that calling this function for the first time "locks in" * various window characteristics as described in * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} */ public abstract View getDecorView(); ~~~ “注意,这个方法第一次调用的时候,会锁定在setContentView()中描述的各种Window特征”  所以说,这也同样解释了为什么在setContentView()之后设置Window的一些特征标志,会不起作用。如果以后遇到类似问题,可以往这方面想一下。 ### Activity中的findViewById()本质上是在做什么? 在上一个问题里面,咱们提到了一个很重要的类——Window,下面先简单看一下这个类的几个方法: ~~~ public abstract class Window { public abstract void setContentView(int layoutResID); public abstract void setContentView(View view); public abstract void setContentView(View view, ViewGroup.LayoutParams params); public View findViewById(int id) { return getDecorView().findViewById(id); } } ~~~ 哇塞,有个好眼熟的方法,findViewById()! 是的,你每次在Activity中用的这个方法,其实间接调用了Window类里面的方法! ~~~ public View findViewById(int id) { return getWindow().findViewById(id); } ~~~ 不过,findViewById()的最终实现是在View及其子类里面的,所以getDecorView()获取到的肯定是一个View对象或者是View的子类对象: ~~~ public abstract View getDecorView(); ~~~ Activity、Window中的findViewById()最终调用的,其实是View的findViewById()。 ~~~ public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { public final View findViewById(int id) { if (id < 0) { return null; } return findViewTraversal(id); } protected View findViewTraversal(int id) { if (id == mID) { return this; } return null; } } ~~~ 但是,很显然,最终调用的肯定不是View类里面的findViewTraversal(),因为这个方法只会返回自身。  而且,findViewById()是final修饰的,不可被重写,所以说,肯定是调用的被子类重写的findViewTraversal(),再联想到,我们的界面上有很多的View,那么既能作为View的容器,又是View的子类的类是什么呢?很显然,是ViewGroup! ~~~ public abstract class ViewGroup extends View implements ViewParent, ViewManager { @Override protected View findViewTraversal(int id) { if (id == mID) { return this; } final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) { View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) { v = v.findViewById(id); if (v != null) { return v; } } } return null; } } ~~~ 所以说,在onCreate()中调用findViewById()对控件进行绑定的操作,实质上是通过在某个View中查找子View实现的,这里你先记住,这个View叫做 DecorView,而且它位于用户窗口的最下面一层。 ### Window和PhoneWindow是什么关系?WindowManager是做什么的? 话说,咱们前面介绍Window的时候,只是简单的介绍了下findViewById(),还没有详细的介绍下这个类,下面咱们一起学习一下。 前面提到过,Window是一个抽象类,抽象类肯定是不能实例化的,所以咱们需要使用的是它的实现类,Window的实现类有哪些呢?咱们从Dash中看下Window类的文档 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7cb9c48.png "") Window只有一个实现类,就是PhoneWindow!所以说这里扯出了PhoneWindow这个类。 而且文档还说,这个类的一个实例,也就是PhoneWindow,应该被添加到Window Manager中,作为顶层的View,所以,这里又扯出了一个WindowManager的概念。 除此之外,还说这个类提供了标准的UI策略,比如背景,标题区域,和默认的按键处理等等,所以说,咱们还知道了Window和PhoneWindow这两个类的作用! 所以说,看文档多重要呀! OK,现在咱们已经知道了Window和唯一的实现类PhoneWindow,以及他们的作用。而且我们还知道了WindowManager,虽然不知道干嘛的,但是从名字也可以猜出是管理Window的,而且还会把Window添加到里面去,在下面的模块中,我会详细的介绍WindowManager这个类。 ### Activity中,Window类型的成员变量mWindow是什么时候初始化的? 在每个Activity中都有一个Window类型的对象mWindow,那么是什么时候初始化的呢? 是在attach()的时候。 还记得attach()是什么时候调用的吗?是在ActivityThread.performLaunchActivity()的时候: ~~~ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); } catch (Exception e) { ...ignore some code... } try { ...ignore some code... activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.voiceInteractor); ...ignore some code... } catch (Exception e) { } return activity; } ~~~ 在attach()里面做了些什么呢? ~~~ public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback { private Window mWindow; final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, IVoiceInteractor voiceInteractor) { ...ignore some code... //就是在这里实例化了Window对象 mWindow = PolicyManager.makeNewWindow(this); //设置各种回调 mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); //这就是传说中的UI线程,也就是ActivityThread所在的,开启了消息循环机制的线程,所以在Actiivty所在线程中使用Handler不需要使用Loop开启消息循环。 mUiThread = Thread.currentThread(); ...ignore some code... //终于见到了前面提到的WindowManager,可以看到,WindowManager属于一种系统服务 mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); } } ~~~ attach()是Activity实例化之后,调用的第一个函数,在这个时候,就实例化了Window。那么这个PolicyManager是什么玩意? ~~~ mWindow = PolicyManager.makeNewWindow(this); ~~~ 来来来,咱们一起RTFSC(Read The Fucking Source Code)! ~~~ public final class PolicyManager { private static final String POLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy"; private static final IPolicy sPolicy; static { // Pull in the actual implementation of the policy at run-time try { Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME); sPolicy = (IPolicy)policyClass.newInstance(); } catch (ClassNotFoundException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be loaded", ex); } catch (InstantiationException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex); } catch (IllegalAccessException ex) { throw new RuntimeException( POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex); } } private PolicyManager() {} public static Window makeNewWindow(Context context) { return sPolicy.makeNewWindow(context); } } ~~~ “Policy”是“策略”的意思,所以就是一个策略管理器,采用了策略设计模式。而sPolicy是一个IPolicy类型,IPolicy实际上是一个接口 ~~~ public interface IPolicy {} ~~~ 所以说,sPolicy的实际类型是在静态代码块里面,利用反射进行实例化的Policy类型。静态代码块中的代码在类文件加载进类加载器之后就会执行, sPolicy就实现了实例化。 那我们看下在Policy里面实际上是做了什么 ~~~ public class Policy implements IPolicy { //看见PhoneWindow眼熟么?还有DecorView,眼熟么?这就是前面所说的那个位于最下面的View,findViewById()就是在它里面找的 private static final String[] preload_classes = { "com.android.internal.policy.impl.PhoneLayoutInflater", "com.android.internal.policy.impl.PhoneWindow", "com.android.internal.policy.impl.PhoneWindow$1", "com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback", "com.android.internal.policy.impl.PhoneWindow$DecorView", "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState", "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState", }; //由于性能方面的原因,在当前Policy类加载的时候,会预加载一些特定的类 static { for (String s : preload_classes) { try { Class.forName(s); } catch (ClassNotFoundException ex) { Log.e(TAG, "Could not preload class for phone policy: " + s); } } } //终于找到PhoneWindow了,我没骗你吧,前面咱们所说的Window终于可以换成PhoneWindow了~ public Window makeNewWindow(Context context) { return new PhoneWindow(context); } } ~~~ PhoneWindow.setContentView()到底发生了什么? 上面说了这么多,实际上只是追踪到了PhoneWindow.setContentView(),下面看一下到底在这里执行了什么: ~~~ @Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } @Override public void setContentView(View view) { setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } @Override public void setContentView(View view, ViewGroup.LayoutParams params) { if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { view.setLayoutParams(params); final Scene newScene = new Scene(mContentParent, view); transitionTo(newScene); } else { mContentParent.addView(view, params); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } ~~~ 当我们第一次调用serContentView()的时候,mContentParent是没有进行过初始化的,所以会调用installDecor()。 为什么能确定mContentParent是没有初始化的呢?因为mContentParent就是在installDecor()里面赋值的 ~~~ private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); ... } if (mContentParent == null) { mContentParent = generateLayout(mDecor); } } ~~~ 在generateDecor()做了什么?返回了一个DecorView对象。 ~~~ protected DecorView generateDecor() { return new DecorView(getContext(), -1); } ~~~ 还记得前面推断出的,DecorView是一个ViewGroup的结论吗?看下面,DecorView继承自FrameLayout,所以咱们的推论是完全正确的。而且 DecorView是PhoneWindow的私有内部类,这两个类关系紧密! ~~~ public class PhoneWindow extends Window implements MenuBuilder.Callback { private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {} } ~~~ 咱们再看一下在对mContentParent赋值的generateLayout(mDecor)做了什么 ~~~ protected ViewGroup generateLayout(DecorView decor) { ...判断并设置了一堆的标志位... //这个是我们的界面将要采用的基础布局xml文件的id int layoutResource; //根据标志位,给layoutResource赋值 if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; } ...我们设置不同的主题以及样式,会采用不同的布局文件... else { //我们在下面代码验证的时候,就会用到这个布局,记住它哦 layoutResource = R.layout.screen_simple; } //要开始更改mDecor啦~ mDecor.startChanging(); //将xml文件解析成View对象,至于LayoutInflater是如何将xml解析成View的,咱们后面再说 View in = mLayoutInflater.inflate(layoutResource, null); //decor和mDecor实际上是同一个对象,一个是形参,一个是成员变量 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; //这里的常量ID_ANDROID_CONTENT就是 public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; //而且,由于是直接执行的findViewById(),所以本质上还是调用的mDecor.findViewById()。而在上面的decor.addView()执行之前,decor里面是空白的,所以我们可以断定,layoutResource所指向的xml布局文件内部,一定存在一个叫做“content”的ViewGroup ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } ...... mDecor.finishChanging(); //最后把id为content的一个ViewGroup返回了 return contentParent; } ~~~ 当上的代码执行之后,mDecor和mContentParent就初始化了,往下就会执行下面的代码,利用LayoutInflater把咱们传进来的layoutResID转化成 View对象,然后添加到id为content的mContentParent中 ~~~ mLayoutInflater.inflate(layoutResID, mContentParent); ~~~ 所以到目前为止,咱们已经知道了以下几个事实,咱们总结一下: - DecorView是PhoneWindow的内部类,继承自FrameLayout,是最底层的界面 - PhoneWindow是Window的唯一子类,他们的作用就是提供标准UI,标题,背景和按键操作 - 在DecorView中会根据用户选择的不同标志,选择不同的xml文件,并且这些布局会被添加到DecorView中 - 在DecorView中,一定存在一个叫做“content”的ViewGroup,而且我们在xml文件中声明的布局文件,会被添加进去 既然是事实,那么怎么才能验证一下呢? ### 如何验证上一个问题 首先,说明一下运行条件: ~~~ //主题 name="AppTheme" parent="@android:style/Theme.Holo.Light.NoActionBar" //编译版本 android { compileSdkVersion 19 buildToolsVersion '19.1.0' defaultConfig { applicationId "com.socks.uitestapp" minSdkVersion 15 targetSdkVersion 19 versionCode 1 versionName "1.0" } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:appcompat-v7:19.1.0' } //Activity代码 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } //activity_main.xml <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="Hello World!" android:textSize="20sp" /> ~~~ OK,咱们的软件已经准备好了,采用的是最简单的布局,界面效果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7cd05de.png "") 下面用Hierarchy看一下树状结构: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7ce9b7a.png "") 第一层,就是上面的DecorView,里面有一个线性布局,上面的是ViewStub,下面就是id为content的ViewGroup,是一个FrameLayout。而我们通过setContentView()设置的布局,就是TextView了。 能不能在源码里面找到源文件呢?当然可以,这个布局就是screen_simple.xml frameworks/base/core/res/res/layout/screen_simple.xml ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout> ~~~ 所以,即使你不调用setContentView(),在一个空Activity上面,也是有布局的。而且肯定有一个DecorView,一个id为content的FrameLayout。 你可以采用下面的方式获取到DecorView,但是你不能获取到一个DecorView实例,只能获取到ViewGroup。 下面贴上这个图,你就可以看明白了(转自 [工匠若水](http://blog.csdn.net/yanbober)) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7959677.jpg "") ~~~ ViewGroup view = (ViewGroup) getWindow().getDecorView(); ~~~ 我们通过setContentView()设置的界面,为什么在onResume()之后才对用户可见呢? 有开发经验的朋友应该知道,我们的界面元素在onResume()之后才对用户是可见的,这是为啥呢? 那我们就追踪一下,onResume()是什么时候调用的,然后看看做了什么操作就Ok了。 这一下,我们又要从ActivityThread开始说起了,不熟悉的快去看前一篇文章《Activity启动过程全解析》]([http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287))。 话说,前文说到,我们想要开启一个Activity的时候,ActivityThread的handleLaunchActivity()会在Handler中被调用 ~~~ private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { //就是在这里调用了Activity.attach()呀,接着调用了Activity.onCreate()和Activity.onStart()生命周期,但是由于只是初始化了mDecor,添加了布局文件,还没有把 //mDecor添加到负责UI显示的PhoneWindow中,所以这时候对用户来说,是不可见的 Activity a = performLaunchActivity(r, customIntent); ...... if (a != null) { //这里面执行了Activity.onResume() handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed); if (!r.activity.mFinished && r.startsNotResumed) { try { r.activity.mCalled = false; //执行Activity.onPause() mInstrumentation.callActivityOnPause(r.activity); } } } } ~~~ 所以说,ActivityThread.handleLaunchActivity执行完之后,Activity的生命周期已经执行了4个(onCreate、onStart()、onResume、onPause())。 下面咱们重点看下handleResumeActivity()做了什么 ~~~ final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { //这个时候,Activity.onResume()已经调用了,但是现在界面还是不可见的 ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); //decor对用户不可见 decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; //这里记住这个WindowManager.LayoutParams的type为TYPE_BASE_APPLICATION,后面介绍Window的时候会见到 l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; if (a.mVisibleFromClient) { a.mWindowAdded = true; //终于被添加进WindowManager了,但是这个时候,还是不可见的 wm.addView(decor, l); } if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { //在这里,执行了重要的操作! if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } } ~~~ 从上面的分析中我们知道,其实在onResume()执行之后,界面还是不可见的,当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的 ~~~ if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); ~~~ OK,其实讲到了这里,关于Activity中的界面显示应该算是告一段落了,我们知道了Activity的生命周期方法的调用时机,还知道了一个最简单的Activity 的界面的构成,并了解了Window、PhoneWindow、DecorView、WindowManager的存在。 但是我还是感觉不过瘾,因为上面只是在流程上大体上过了一遍,对于Window、WindowManager的深入了解还不够,所以下面就开始讲解Window、WindowManager等相关类的稍微高级点的知识。 前面看累了的朋友,可以上个厕所,泡个咖啡,休息下继续往下看。 ### ViewManager、WindowManager、WindowManagerImpl、WindowManagerGlobal到底都是些什么玩意? WindowManager其实是一个接口,和Window一样,起作用的是它的实现类 ~~~ public interface WindowManager extends ViewManager { //对这个异常熟悉么?当你往已经销毁的Activity中添加Dialog的时候,就会抛这个异常 public static class BadTokenException extends RuntimeException { public BadTokenException() { } public BadTokenException(String name) { super(name); } } //其实WindowManager里面80%的代码是用来描述这个内部静态类的 public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { } } ~~~ WindowManager继承自ViewManager这个接口,从注释和方法我们可以知道,这个就是用来描述可以对Activity中的子View进行添加和移除能力的接口。 ~~~ /** Interface to let you add and remove child views to an Activity. To get an instance * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. */ public interface ViewManager { public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); } ~~~ 那么我们在使用WindowManager的时候,到底是在使用哪个类呢? 是WindowManagerImpl。 ~~~ public final class WindowManagerImpl implements WindowManager {} ~~~ 怎么知道的呢?那我们还要从Activity.attach()说起 话说,在attach()里面完成了mWindowManager的初始化 ~~~ final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, IVoiceInteractor voiceInteractor) { mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); mWindowManager = mWindow.getWindowManager(); } ~~~ 那我们只好看下(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)是什么玩意了。 这里要说明的是,context是一个ContextImpl对象,这里先记住就好,以后再细说。 ~~~ class ContextImpl extends Context { //静态代码块,完成各种系统服务的注册 static { ...... registerService(WINDOW_SERVICE, new ServiceFetcher() { Display mDefaultDisplay; public Object getService(ContextImpl ctx) { Display display = ctx.mDisplay; if (display == null) { if (mDefaultDisplay == null) { DisplayManager dm = (DisplayManager)ctx.getOuterContext(). getSystemService(Context.DISPLAY_SERVICE); mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY); } display = mDefaultDisplay; } //没骗你吧 return new WindowManagerImpl(display); }}); ...... } @Override public Object getSystemService(String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name); return fetcher == null ? null : fetcher.getService(this); } } ~~~ 要注意的是,这里返回的WindowManagerImpl对象,最终并不是和我们的Window关联的,而且这个方法是有可能返回null的,所以在 Window.setWindowManager()的时候,进行了处理 ~~~ public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); //重试一遍 if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } //设置parentWindow,创建真正关联的WindowManagerImpl对象 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); } public final class WindowManagerImpl implements WindowManager { //最终调用的这个构造 private WindowManagerImpl(Display display, Window parentWindow) { mDisplay = display; mParentWindow = parentWindow; } public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mDisplay, parentWindow); } } ~~~ 所以说,每一个Activity都有一个PhoneWindow成员变量,并且也都有一个WindowManagerImpl,而且,PhoneWindow和WindowManagerImpl 在Activity.attach()的时候进行了关联。 插一张类图(转自[工匠若水](http://blog.csdn.net/yanbober)) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da79baa0a.jpg "") 知道了这些,那下面的操作就可以直接看WindowManagerImpl了。 其实WindowManagerImpl这个类也没有什么看头,为啥这么说呢?因为他其实是代理模式中的代理。是谁的代理呢?是WindowManagerGlobal。 ~~~ public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); private final Display mDisplay; private final Window mParentWindow; @Override public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); } @Override public void updateViewLayout(View view, ViewGroup.LayoutParams params) { mGlobal.updateViewLayout(view, params); } @Override public void removeView(View view) { mGlobal.removeView(view, false); } @Override public void removeViewImmediate(View view) { mGlobal.removeView(view, true); } } ~~~ 从上面的代码中可以看出来,WindowManagerImpl里面对ViewManager接口内方法的实现,都是通过代理WindowManagerGlobal的方法实现的, 所以重点转移到了WindowManagerGlobal这个类。 还记得前面我们的DecorView被添加到了WindowManager吗? ~~~ wm.addView(decor, l); ~~~ 其实最终调用的是WindowManagerGlobal.addView(); ~~~ public final class WindowManagerGlobal { private static IWindowManager sWindowManagerService; private static IWindowSession sWindowSession; private final ArrayList<View> mViews = new ArrayList<View>(); private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); //WindowManagerGlobal是单例模式 private static WindowManagerGlobal sDefaultWindowManager; public static WindowManagerGlobal getInstance() { synchronized (WindowManagerGlobal.class) { if (sDefaultWindowManager == null) { sDefaultWindowManager = new WindowManagerGlobal(); } return sDefaultWindowManager; } } public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; ...... synchronized (mLock) { ViewRootImpl root; root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } ...... try { //注意下这个方法,因为下面介绍ViewRootImpl的时候会用到 root.setView(view, wparams, panelParentView); }catch (RuntimeException e) { } } } ~~~ 我们看到,WindowManagerGlobal是单例模式,所以在一个App里面只会有一个WindowManagerGlobal实例。在WindowManagerGlobal里面维 护了三个集合,分别存放添加进来的View(实际上就是DecorView),布局参数params,和刚刚实例化的ViewRootImpl对象, WindowManagerGlobal到底干嘛的呢? 其实,WindowManagerGlobal是和WindowManagerService(即WMS)通信的。 还记得在上一篇文章中我们介绍ActivityThread和AMS之间的IBinder通信的吗?是的,这里也是IBinder通信。 ~~~ public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { InputMethodManager imm = InputMethodManager.getInstance(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( ...... } catch (RemoteException e) { Log.e(TAG, "Failed to open window session", e); } } return sWindowSession; } } public static IWindowManager getWindowManagerService() { synchronized (WindowManagerGlobal.class) { if (sWindowManagerService == null) { //ServiceManager是用来管理系统服务的,比如AMS、WMS等,这里就获取到了WMS的客户端代理对象 sWindowManagerService = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); } return sWindowManagerService; } } ~~~ 首先通过上面的方法获取到IBinder对象,然后转化成了WMS在本地的代理对象IWindowManager,然后通过openSession()初始化了sWindowSession 对象。这个对象是干什么的呢? “Session“是会话的意思,这个类就是为了实现与WMS的会话的,谁和WMS的对话呢?WindowManagerGlobal类内部并没有用这个类呀! 是ViewRootImpl与WMS的对话。 ### ViewRootImpl是什么?有什么作用?ViewRootImpl如何与WMS通信 你还记得么?在前面将WindowManagerGlobal.addView()的时候,实例化了一个ViewRootImpl,然后添加到了一个集合里面,咱们先看下ViewRootImpl的构造函数吧 ~~~ public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { public ViewRootImpl(Context context, Display display) { mContext = context; //获取WindowSession mWindowSession = WindowManagerGlobal.getWindowSession(); mDisplay = display; ...... mWindow = new W(this); //默认不可见 mViewVisibility = View.GONE; //这个数值就是屏幕宽度的dp总数 mDensity = context.getResources().getDisplayMetrics().densityDpi; mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); } } ~~~ 在这个构造方法里面,主要是完成了各种参数的初始化,并且最关键的,获取到了前面介绍的WindowSession,那么你可能好奇了,这个ViewRootImpl 到底有什么作用呢? ViewRootImpl负责管理视图树和与WMS交互,与WMS交互是通过WindowSession。而且ViewRootImpl也负责UI界面的布局与渲染,负责把一些事件分发至Activity,以便Activity可以截获事件。大多数情况下,它管理Activity顶层视图DecorView,它相当于MVC模型中的Controller。 WindowSession是ViewRootImpl获取之后,主动和WMS通信的,但是我们在前面的文章知道,客户端和服务器需要互相持有对方的代理引用,才能实现双向通信,那么WMS是怎么得到ViewRootImpl的通信代理的呢? 是在ViewRootImpl.setView()的时候。 还记得不?在上面介绍WindowManagerGlobal.addView()的时候,我还重点说了下,在这个方法的try代码块中,调用了ViewRootImpl.setView(),下面咱们看下这个方法干嘛了: ~~~ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; int res; requestLayout(); try { res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel); }catch (RemoteException e) { throw new RuntimeException("Adding window failed", e); } finally { } } } } ~~~ 为了突出重点,我简化了很多代码,从上面可以看出来,是mWindowSession.addToDisplay()这个方法把mWindow传递给我WMS,WMS就持有了 当前ViewRootlmpl的代理,就可以调用W对象让ViewRootlmpl做一些事情了。 这样,双方都有了对方的接口,WMS中的Session注册到WindowManagerGlobal的成员WindowSession中,ViewRootImpl::W注册到WindowState中的成员mClient中。前者是为了App改变View结构时请求WMS为其更新布局。后者代表了App端的一个添加到WMS中的View,每一个像这样通过WindowManager接口中addView()添加的窗口都有一个对应的ViewRootImpl,也有一个相应的ViewRootImpl::W。它可以理解为是ViewRootImpl中暴露给WMS的接口,这样WMS可以通过这个接口和App端通信。 另外源码中很多地方采用了这种将接口隐藏为内部类的方式,这样可以实现六大设计原则之一——接口最小原则。 ### 从什么时候开始绘制整个Activity的View树的? 注意前面代码中的requestLayout();因为这个方法执行之后,我们的ViewRootImpl才开始绘制整个View树! ~~~ @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } ~~~ ~~~ void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //暂停UI线程消息队列对同步消息的处理 mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); //向Choreographer注册一个类型为CALLBACK_TRAVERSAL的回调,用于处理UI绘制 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); } } ~~~ “Choreographer就是一个消息处理器,根据vsync 信号 来计算frame“ 解释起来比较麻烦,我们暂时不展开讨论,你只要知道,当回调被触发之后,mTraversalRunnable对象的run()就会被调用 ~~~ final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } ~~~ doTraversal()中最关键的,就是调用了performTraversals(),然后就开始mesure,layout,draw了,这里面的具体逻辑本篇文章不讲,因为重点是 Activity的界面显示流程,这一块属于View的,找时间单独拿出来说 ~~~ void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals"); try { performTraversals(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } } ~~~ 来回倒腾了这么多,终于看见界面了,让我哭会 T^T ### Window的类型有几种?分别在什么情况下会使用到哪一种? Window的类型是根据WindowManager.LayoutParams的type属性相关的,根据类型可以分为三类: - 取值在FIRST_APPLICATION_WINDOW与LAST_APPLICATION_WINDOW之间(1-99),是常用的顶层应用程序窗口,须将token设置成Activity的token,比如前面开启Window的时候设置的类型即为TYPE_APPLICATION - 在FIRST_SUB_WINDOW和LAST_SUB_WINDOW(1000-1999)之间,与顶层窗口相关联,需将token设置成它所附着宿主窗口的token,比如PopupWindow就是TYPE_APPLICATION_PANEL - 取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW(2000-2999)之间,不能用于应用程序,使用时需要有特殊权限,它是特定的系统功能才能使用,比如Toast就是TYPE_TOAST=2005,所以不需要特殊权限 下面是所有的Type说明 ~~~ //WindowType:开始应用程序窗口 public static final int FIRST_APPLICATION_WINDOW = 1; //WindowType:所有程序窗口的base窗口,其他应用程序窗口都显示在它上面 public static final int TYPE_BASE_APPLICATION = 1; //WindowType:普通应用程序窗口,token必须设置为Activity的token来指定窗口属于谁 public static final int TYPE_APPLICATION = 2; //WindowType:应用程序启动时所显示的窗口,应用自己不要使用这种类型,它被系统用来显示一些信息,直到应用程序可以开启自己的窗口为止 public static final int TYPE_APPLICATION_STARTING = 3; //WindowType:结束应用程序窗口 public static final int LAST_APPLICATION_WINDOW = 99; //WindowType:SubWindows子窗口,子窗口的Z序和坐标空间都依赖于他们的宿主窗口 public static final int FIRST_SUB_WINDOW = 1000; //WindowType: 面板窗口,显示于宿主窗口的上层 public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; //WindowType:媒体窗口(例如视频),显示于宿主窗口下层 public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1; //WindowType:应用程序窗口的子面板,显示于所有面板窗口的上层 public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2; //WindowType:对话框,类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口 public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3; //WindowType:媒体信息,显示在媒体层和程序窗口之间,需要实现半透明效果 public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4; //WindowType:子窗口结束 public static final int LAST_SUB_WINDOW = 1999; //WindowType:系统窗口,非应用程序创建 public static final int FIRST_SYSTEM_WINDOW = 2000; //WindowType:状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方 public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW; //WindowType:搜索栏,只能有一个搜索栏,位于屏幕上方 public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1; //WindowType:电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下 public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2; //WindowType:系统提示,出现在应用程序窗口之上 public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3; //WindowType:锁屏窗口 public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4; //WindowType:信息窗口,用于显示Toast public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5; //WindowType:系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏 public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6; //WindowType:电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏 public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7; //WindowType:系统对话框 public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8; //WindowType:锁屏时显示的对话框 public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9; //WindowType:系统内部错误提示,显示于所有内容之上 public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10; //WindowType:内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖 public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11; //WindowType:内部输入法对话框,显示于当前输入法窗口之上 public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12; //WindowType:墙纸窗口 public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13; //WindowType:状态栏的滑动面板 public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14; //WindowType:安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘 public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15; //WindowType:拖放伪窗口,只有一个阻力层(最多),它被放置在所有其他窗口上面 public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16; //WindowType:状态栏下拉面板 public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17; //WindowType:鼠标指针 public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18; //WindowType:导航栏(有别于状态栏时) public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19; //WindowType:音量级别的覆盖对话框,显示当用户更改系统音量大小 public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20; //WindowType:起机进度框,在一切之上 public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21; //WindowType:假窗,消费导航栏隐藏时触摸事件 public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22; //WindowType:梦想(屏保)窗口,略高于键盘 public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23; //WindowType:导航栏面板(不同于状态栏的导航栏) public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24; //WindowType:universe背后真正的窗户 public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25; //WindowType:显示窗口覆盖,用于模拟辅助显示设备 public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26; //WindowType:放大窗口覆盖,用于突出显示的放大部分可访问性放大时启用 public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27; //WindowType:...... public static final int TYPE_KEYGUARD_SCRIM = FIRST_SYSTEM_WINDOW+29; public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30; public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31; public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32; //WindowType:系统窗口结束 public static final int LAST_SYSTEM_WINDOW = 2999; ~~~ 为什么使用PopWindow的时候,不设置背景就不能触发事件? 我们在使用PopupWindow的时候,会发现如果不给PopupWindow设置背景,那么就不能触发点击返回事件,有人认为这个是BUG,其实并不是的。 我们以下面的方法为例,其实所有的显示方法都有下面的流程: ~~~ public void showAtLocation(IBinder token, int gravity, int x, int y) { if (isShowing() || mContentView == null) { return; } mIsShowing = true; mIsDropdown = false; WindowManager.LayoutParams p = createPopupLayout(token); p.windowAnimations = computeAnimationResource(); //在这里会根据不同的设置,配置不同的LayoutParams属性 preparePopup(p); if (gravity == Gravity.NO_GRAVITY) { gravity = Gravity.TOP | Gravity.START; } p.gravity = gravity; p.x = x; p.y = y; if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; invokePopup(p); } ~~~ 我们重点看下preparePopup() ~~~ private void preparePopup(WindowManager.LayoutParams p) { //根据背景的设置情况进行不同的配置 if (mBackground != null) { final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); int height = ViewGroup.LayoutParams.MATCH_PARENT; //如果设置了背景,就用一个PopupViewContainer对象来包裹之前的mContentView,并设置背景后 PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height ); popupViewContainer.setBackground(mBackground); popupViewContainer.addView(mContentView, listParams); mPopupView = popupViewContainer; } else { mPopupView = mContentView; } } ~~~ 为啥包了一层PopupViewContainer,就可以处理按钮点击事件了?因为PopupWindow没有相关事件回调,也没有重写按键和触摸方法,所以接收不 到对应的信号 ~~~ public class PopupWindow {} ~~~ 而PopupViewContainer则可以,因为它重写了相关方法 ~~~ private class PopupViewContainer extends FrameLayout { @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (getKeyDispatcherState() == null) { return super.dispatchKeyEvent(event); } if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { //back键消失 KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null && state.isTracking(event) && !event.isCanceled()) { dismiss(); return true; } } return super.dispatchKeyEvent(event); } else { return super.dispatchKeyEvent(event); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { return true; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { final int x = (int) event.getX(); final int y = (int) event.getY(); //触摸在外面就消失 if ((event.getAction() == MotionEvent.ACTION_DOWN) && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) { dismiss(); return true; } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { dismiss(); return true; } else { return super.onTouchEvent(event); } } } ~~~ 在Activity中使用Dialog的时候,为什么有时候会报错“Unable to add window – token is not valid; is your activity running?”? 这种情况一般发生在什么时候?一般发生在Activity进入后台,Dialog没有主动Dismiss掉,然后从后台再次进入App的时候。 为什么会这样呢? 还记得前面说过吧,子窗口类型的Window,比如Dialog,想要显示的话,比如保证appToken与Activity保持一致,而当Activity销毁,再次回来的时候,Dialog试图重新创建,调用ViewRootImp的setView()的时候就会出问题,所以记得在Activity不可见的时候,主动Dismiss掉Dialog。 ~~~ if (res < WindowManagerGlobal.ADD_OKAY) { switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- another window of this type already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- permission denied for this window type"); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException( "Unable to add window " + mWindow + " -- the specified display can not be found"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); } } ~~~ 为什么Toast需要由系统统一控制,在子线程中为什么不能显示Toast? 首先Toast也属于窗口系统,但是并不是属于App的,是由系统同一控制的。  关于这一块不想说太多,具体实现机制请参考后面的文章。 为了看下面的内容,你需要知道以下几件事情: 1. Toast的显示是由系统Toast服务控制的,与系统之间的通信方式是Binder 1. 整个Toast系统会维持最多50个Toast的队列,依次显示 1. 负责现实工作的是Toast的内部类TN,它负责最终的显示与隐藏操作 1. 负责给系统Toast服务发送内容的是INotificationManager的实现类,它负责在Toast.show()里面把TN对象传递给系统消息服务,service.enqueueToast(pkg, tn, mDuration);这样Toast服务就持有客户端的代理,可以通过TN来控制每个Toast的显示与隐藏。 再来张图(转自[工匠若水](http://blog.csdn.net/yanbober)) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7abdb0e.jpg "") ok,现在假如你知道上面这些啦,那么我们下面就看为什么在子线程使用Toast.show()会提示 ~~~ "No Looper; Looper.prepare() wasn't called on this thread." ~~~ 原因很简单,因为TN在操作Toast的时候,是通过Handler做的 ~~~ @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } ~~~ 所以说,TN初始化的线程必须为主线程,在子线程中使用Handler,由于没有消息队列,就会造成这个问题。 ## 结语 上面写了这么多,你可能看了前面忘了后面,下面,凯子哥给你总结一下,这篇文章到底讲了什么东西: - 每个Activity,都至少有一个Window,这个Window实际类型为PhoneWindow,当Activity中有子窗口,比如Dialog的时候,就会出现多个Window。Activity的Window是我们控制的,状态栏和导航栏的Window由系统控制。 - 在DecorView的里面,一定有一个id为content的FraneLayout的布局容器,咱们自己定义的xml布局都放在这里面。 - Activity的Window里面有一个DecorView,它使继承自FrameLayout的一个自定义控件,作为整个View层的容器,及View树的根节点。 - Window是虚拟的概念,DecorView才是看得见,摸得着的东西,Activity.setContentView()实际调用的是PhoneWindow.setContentView(),在这里面实现了DecorView的初始化和id为content的FraneLayout的布局容器的初始化,并且会根据主题等配置,选择不同的xml文件。而且在Activity.setContentView()之后,Window的一些特征位将被锁定。 - Activity.findViewById()实际上调用的是DecorView的findviewById(),这个方法在View中定义,但是是final的,实际起作用的是在ViewGroup中被重写的findViewTraversal()方法。 - Activity的mWindow成员变量是在attach()的时候被初始化的,attach()是Activity被通过反射手段实例化之后调用的第一个方法,在这之后生命周期方法才会依次调用 - 在onResume()刚执行之后,界面还是不可见的,只有执行完Activity.makeVisible(),DecorView才对用户可见 - ViewManager这个接口里面就三个接口,添加、移除和更新,实现这个接口的有WindowManager和ViewGroup,但是他们两个面向的对象是不一样的,WindowManager实现的是对Window的操作,而ViewGroup则是对View的增、删、更新操作。 - WindowManagerImpl是WindowManager的实现类,但是他就是一个代理类,代理的是WindowManagerGlobal,WindowManagerGlobal一个App里面就有一个,因为它是单例的,它里面管理了App中所有打开的DecorView,ContentView和PhoneWindow的布局参数WindowManager.LayoutParams,而且WindowManagerGlobal这个类是和WMS通信用的,是通过IWindowSession对象完成这个工作的,而IWindowSession一个App只有一个,但是每个ViewRootImpl都持有对IWindowSession的引用,所以ViewRootImpl可以和WMS喊话,但是WMS怎么和ViewRootImpl喊话呢?是通过ViewRootImpl::W这个内部类实现的,而且源码中很多地方采用了这种将接口隐藏为内部类的方式,这样可以实现六大设计原则之一——接口最小原则,这样ViewRootImpl和WMS就互相持有对方的代理,就可以互相交流了 - ViewRootImpl这个类每个Activity都有一个,它负责和WMS通信,同时相应WMS的指挥,还负责View界面的测量、布局和绘制工作,所以当你调用View.invalidate()和View.requestLayout()的时候,都会把事件传递到ViewRootImpl,然后ViewRootImpl计算出需要重绘的区域,告诉WMS,WMS再通知其他服务完成绘制和动画等效果,当然,这是后话,咱们以后再说。 - Window分为三种,子窗口,应用窗口和系统窗口,子窗口必须依附于一个上下文,就是Activity,因为它需要Activity的appToken,子窗口和Activity的WindowManager是一个的,都是根据appToken获取的,描述一个Window属于哪种类型,是根据LayoutParam.type决定的,不同类型有不同的取值范围,系统类的的Window需要特殊权限,当然Toast比较特殊,不需要权限 - PopupWindow使用的时候,如果想触发按键和触摸事件,需要添加一个背景,代码中会根据是否设置背景进行不同的逻辑判断 - Dialog在Activity不可见的时候,要主动dismiss掉,否则会因为appToken为空crash - Toast属于系统窗口,由系统服务NotificationManagerService统一调度,NotificationManagerService中维持着一个集合ArrayList,最多存放50个Toast,但是NotificationManagerService只负责管理Toast,具体的现实工作由Toast::TN来实现 最后来一张Android的窗口管理框架(转自[ariesjzj](http://blog.csdn.net/jinzhuojun)) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7d2d3a7.jpg "") OK,关于Activity的界面显示就说到这里吧,本篇文章大部分的内容来自于阅读下面参考文章之后的总结和思考,想了解更详细的可以研究下。 下次再见,拜拜~ ## 参考文章 - [Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析](http://blog.csdn.net/yanbober/article/details/46361191) - [Android应用setContentView与LayoutInflater加载解析机制源码分析](http://blog.csdn.net/yanbober/article/details/45970721) - [Android 4.4(KitKat)窗口管理子系统 - 体系框架](http://blog.csdn.net/jinzhuojun/article/details/37737439) - [Android 之 Window、WindowManager 与窗口管理](http://blog.csdn.net/xieqibao/article/details/6567814) - [图解Android - Android GUI 系统 (1) - 概论](http://www.cnblogs.com/samchen2009/p/3364327.html) 尊重原创,转载请注明:From 凯子哥([](http://blog.csdn.net/zhaokaiqiang1992)[http://blog.csdn.net/zhaokaiqiang1992](http://blog.csdn.net/zhaokaiqiang1992)) 侵权必究! 关注我的微博,可以获得更多精彩内容 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7b5de49.png)](http://weibo.com/u/1783932377?s=6uyXnP)
';

Android Activity.startActivity流程简介

最后更新于:2022-04-01 09:53:30

## 1. 基本概念 ### 1.1 Instrumentation是什么? 顾名思义,仪器仪表,用于在应用程序中进行“测量”和“管理”工作。一个应用程序中只有一个Instrumentation实例对象,且每个Activity都有此对象的引用。Instrumentation将在任何应用程序运行前初始化,可以通过它监测系统与应用程序之间的所有交互,即类似于在系统与应用程序之间安装了个“窃听器”。       当ActivityThread 创建(callActivityOnCreate)、暂停、恢复某个Activity时,通过调用此对象的方法来实现,如:          1) 创建: callActivityOnCreate           2) 暂停: callActivityOnPause          3) 恢复: callActivityOnResume Instrumentation和ActivityThread的关系,类似于老板与经理的关系,老板负责对外交流(如与Activity Manager Service<AMS>),Instrumentation负责管理并完成老板交待的任务。 它通过以下两个成员变量来对当前应用进程中的Activity进行管理: ~~~ private List<ActivityWaiter> mWaitingActivities;   private List<ActivityMonitor> mActivityMonitors;   ~~~ 其功能函数下表所示: <table border="1" cellspacing="1" cellpadding="1" style="color:rgb(51,51,51); font-family:Arial; font-size:14px; line-height:26px"><tbody><tr><td><span style="font-size:18px">功能</span></td><td><span style="font-size:18px">函数</span></td></tr><tr><td><span style="font-size:18px">增加删除Monitor</span></td><td><span style="font-size:18px">addMonitor(ActivityMonitor monitor)<br/>removeMonitor(ActivityMonitor monitor)</span></td></tr><tr><td><span style="font-size:18px">Application与Activity生命周期控制</span></td><td><span style="font-size:18px">newApplication(Class&lt;?&gt; clazz, Context context)<br/>newActivity(ClassLoader cl, String className,Intent intent)<br/>callActivityOnCreate(Activity activity, Bundle icicle)<br/>callActivityOnDestroy(Activity activity)<br/>callActivityOnStart(Activity activity)<br/>callActivityOnRestart(Activity activity)<br/>callActivityOnResume(Activity activity)<br/>callActivityOnStop(Activity activity)<br/>callActivityOnPause(Activity activity)<br/></span></td></tr><tr><td><span style="font-size:18px">Instrumentation生命周期控制</span></td><td><span style="font-size:18px">onCreate(Bundle arguments)<br/>start()<br/>onStart()<br/>finish(int resultCode, Bundle results)<br/>onDestroy()<br/></span></td></tr><tr><td><span style="font-size:18px">发送用户操控信息到当前窗口</span></td><td><span style="font-size:18px">sendCharacterSync(int keyCode)<br/>sendPointerSync(MotionEvent event)<br/>sendTrackballEventSync(MotionEvent event)<br/>sendTrackballEventSync(MotionEvent event)<br/></span></td></tr><tr><td><span style="font-size:18px">同步操作</span></td><td><span style="font-size:18px">startActivitySync(Intent intent) //它调用Context.startActivity<br/>runOnMainSync(Runnable runner)<br/>waitForIdle()<br/></span></td></tr></tbody></table> ## 2. Android应用程序启动过程(MainActivity) 即MainActivity的启动过程,在此过程中,将创建一个新的进程来执行此MainActivity。 Android应用程序从Launcher启动流程如下所示: ~~~ /*****************************************************************   * Launcher通过Binder告诉ActivityManagerService,   * 它将要启动一个新的Activity;   ****************************************************************/   Launcher.startActivitySafely->     Launcher.startActivity->      //要求在新的Task中启动此Activity      //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)      Activity.startActivity->      Activity.startActivityForResult->      Instrumentation.execStartActivity->       // ActivityManagerNative.getDefault()返回AMS Proxy接口       ActivityManagerNative.getDefault().startActivity->       ActivityManagerProxy.startActivity->             ActivityManagerService.startActivity-> (AMS)        ActivityManagerService.startActivityAsUser->               ActivityStack.startActivityMayWait->         ActivityStack.resolveActivity(获取ActivityInfo)           //aInfo.name为main Activity,如:com.my.test.MainActivity           //aInfo.applicationInfo.packageName为包名,如com.my.test         ActivityStack.startActivityLocked->           //ProcessRecord callerApp; 调用者即Launcher信息           //ActivityRecord sourceRecord; Launcher Activity相关信息           //ActivityRecord r=new ActivityRecord(...),将要创建的Activity相关信息           ActivityStack.startActivityUncheckedLocked->          //Activity启动方式:ActivityInfo.LAUNCH_MULTIPLE/LAUNCH_SINGLE_INSTANCE/          //             ActivityInfo.LAUNCH_SINGLE_TASK/LAUNCH_SINGLE_TOP)          // 创建一个新的task,即TaskRecord,并保存在ActivityRecord.task中          //r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true)          // 把新创建的Activity放在栈顶             ActivityStack.startActivityLocked->          ActivityStack.resumeTopActivityLocked->          ActivityStack.startPausingLocked (使Launcher进入Paused状态)->               /*****************************************************************        * AMS通过Binder通知Launcher进入Paused状态        ****************************************************************/         ApplicationThreadProxy.schedulePauseActivity->            //private class ApplicationThread extends ApplicationThreadNative           ApplicationThread.schedulePauseActivity->                 ActivityThread.queueOrSendMessage->                  // 调用Activity.onUserLeaveHint            // 调用Activity.onPause            // 通知activity manager我进入了pause状态            ActivityThread.handlePauseActivity->               /*****************************************************************          * Launcher通过Binder告诉AMS,它已经进入Paused状态          ****************************************************************/          ActivityManagerProxy.activityPaused->            ActivityManagerService.activityPaused->            ActivityStack.activityPaused->(把Activity状态修改为PAUSED)            ActivityStack.completePauseLocked->                   // 参数为代表Launcher这个Activity的ActivityRecord            // 使用栈顶的Activity进入RESUME状态            ActivityStack.resumeTopActivityLokced->              //topRunningActivityLocked将刚创建的放于栈顶的activity取回来              // 即在ActivityStack.startActivityUncheckedLocked中创建的               /*****************************************************************          * AMS创建一个新的进程,用来启动一个ActivityThread实例,          * 即将要启动的Activity就是在这个ActivityThread实例中运行          ****************************************************************/          ActivityStack.startSpecificActivityLocked->                  // 创建对应的ProcessRecord             ActivityManagerService.startProcessLocked->                           // 启动一个新的进程              // 新的进程会导入android.app.ActivityThread类,并且执行它的main函数,              // 即实例化ActivityThread, 每个应用有且仅有一个ActivityThread实例              Process.start("android.app.ActivityThread",...)->                 // 通过zygote机制创建一个新的进程              Process.startViaZygote->                     // 这个函数在进程中创建一个ActivityThread实例,然后调用              // 它的attach函数,接着就进入消息循环              ActivityThread.main->                 /*****************************************************************            * ActivityThread通过Binder将一个ApplicationThread类的Binder对象            * 传递给AMS,以便AMS通过此Binder对象来控制Activity整个生命周期            ****************************************************************/            ActivityThread.attach->              IActivityManager.attachApplication(mAppThread)->              ActivityManagerProxy.attachApplication->              ActivityManagerService.attachApplication->                   // 把在ActivityManagerService.startProcessLocked中创建的ProcessRecord取出来              ActivityManagerService.attachApplicationLocked->                 /*****************************************************************            * AMS通过Binder通知ActivityThread一切准备OK,它可以真正启动新的Activity了            ****************************************************************/                        // 真正启动Activity              ActivityStack.realStartActivityLocked->              ApplicationThreadProxy.scheduleLaunchActivity->              ApplicationThread.scheduleLaunchActivity->              ActivityThread.handleLaunchActivity->                // 加载新的Activity类,并执行它的onCreate                ActivityThread.performLaunchActivity                 /*1) Instrumentation.newActivity: 加载新类,即创建Activity对象;                 2) ActivityClientRecord.packageInfo.makeApplication:创建Application对象;                    <LoadedApk.makeApplication>                 3) Activity.attach(Context context, ActivityThread aThread,                       Instrumentation instr, IBinder token, int ident,                       Application application, Intent intent, ActivityInfo info,                       CharSequence title, Activity parent, String id,                       NonConfigurationInstances lastNonConfigurationInstances,                       Configuration config):把Application attach到Activity, 即把Activtiy                                              相关信息设置到新创建的Activity中                 4) Instrumentation.callActivityOnCreate:调用onCreate;*/                       // 使用Activity进入RESUMED状态,并调用onResume                ActivityThread.handleResumeActivity     ~~~ ## 3. ActivityManagerService ### 3.1 类中关键信息 ~~~ public final class ActivityManagerService extends ActivityManagerNative           implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {       ...       // Maximum number of recent tasks that we can remember.       static final int MAX_RECENT_TASKS = 20;          public ActivityStack mMainStack; // 管理Activity堆栈          // Whether we should show our dialogs (ANR, crash, etc) or just perform their       // default actuion automatically.  Important for devices without direct input       // devices.       private boolean mShowDialogs = true;          /**       * Description of a request to start a new activity, which has been held       * due to app switches being disabled.       */       static class PendingActivityLaunch {           ActivityRecord r;           ActivityRecord sourceRecord;           int startFlags;       }             /**       * Activity we have told the window manager to have key focus.       */       ActivityRecord mFocusedActivity = null;          /**       * List of intents that were used to start the most recent tasks.       */       final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();          /**       * Process management.       */       final ProcessList mProcessList = new ProcessList();          /**       * All of the applications we currently have running organized by name.       * The keys are strings of the application package name (as       * returned by the package manager), and the keys are ApplicationRecord       * objects.       */       final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>();          /**       * The currently running isolated processes.       */       final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<ProcessRecord>();       ...          public static final Context main(int factoryTest) { //main入口函数           AThread thr = new AThread();           thr.start();              synchronized (thr) {               while (thr.mService == null) {                   try {                       thr.wait();                   } catch (InterruptedException e) {                   }               }           }              ActivityManagerService m = thr.mService;           mSelf = m;           ActivityThread at = ActivityThread.systemMain();           mSystemThread = at;           Context context = at.getSystemContext();           context.setTheme(android.R.style.Theme_Holo);           m.mContext = context;           m.mFactoryTest = factoryTest;           m.mMainStack = new ActivityStack(m, context, true); // 创建ActivityStack                      m.mBatteryStatsService.publish(context);           m.mUsageStatsService.publish(context);                      synchronized (thr) {               thr.mReady = true;               thr.notifyAll();           }              m.startRunning(null, null, null, null);                      return context;       }   }   ~~~ ### 3.2 家族图谱 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7bdbe4e.jpg) ## 4. ActivityStack-真正做事的家伙 ActivityManagerService使用它来管理系统中所有的Activities的状态,Activities使用stack的方式进行管理。它是真正负责做事的家伙,很勤快的,但外界无人知道! ### 4.1 类中关键信息     ~~~ /**   * State and management of a single stack of activities.   */   final class ActivityStack {       final ActivityManagerService mService;       final boolean mMainStack;       final Context mContext;          enum ActivityState {           INITIALIZING,           RESUMED,           PAUSING,           PAUSED,           STOPPING,           STOPPED,           FINISHING,           DESTROYING,           DESTROYED       }          /**       * The back history of all previous (and possibly still       * running) activities.  It contains HistoryRecord objects.       */       final ArrayList<ActivityRecord> mHistory = new ArrayList<ActivityRecord>();          /**       * Used for validating app tokens with window manager.       */       final ArrayList<IBinder> mValidateAppTokens = new ArrayList<IBinder>();          /**       * List of running activities, sorted by recent usage.       * The first entry in the list is the least recently used.       * It contains HistoryRecord objects.       */       final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>();          /**       * List of activities that are waiting for a new activity       * to become visible before completing whatever operation they are       * supposed to do.       */       final ArrayList<ActivityRecord> mWaitingVisibleActivities               = new ArrayList<ActivityRecord>();          /**       * List of activities that are ready to be stopped, but waiting       * for the next activity to settle down before doing so.  It contains       * HistoryRecord objects.       */       final ArrayList<ActivityRecord> mStoppingActivities               = new ArrayList<ActivityRecord>();          /**       * List of activities that are in the process of going to sleep.       */       final ArrayList<ActivityRecord> mGoingToSleepActivities               = new ArrayList<ActivityRecord>();       /**       * When we are in the process of pausing an activity, before starting the       * next one, this variable holds the activity that is currently being paused.       */       ActivityRecord mPausingActivity = null;          /**       * This is the last activity that we put into the paused state.  This is       * used to determine if we need to do an activity transition while sleeping,       * when we normally hold the top activity paused.       */       ActivityRecord mLastPausedActivity = null;          /**       * Current activity that is resumed, or null if there is none.       */       ActivityRecord mResumedActivity = null;              /**       * This is the last activity that has been started.  It is only used to       * identify when multiple activities are started at once so that the user       * can be warned they may not be in the activity they think they are.       */       ActivityRecord mLastStartedActivity = null;          /**       * Set to indicate whether to issue an onUserLeaving callback when a       * newly launched activity is being brought in front of us.       */       boolean mUserLeaving = false;          ActivityStack(ActivityManagerService service, Context context, boolean mainStack) {           mService = service;           mContext = context;           mMainStack = mainStack;           ...       }       ...   }   ~~~ ### 4.2 家族图谱 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7c2bc73.jpg) ## 5. ProcessRecord 记录了一个进程的相关信息。 ### 5.1 类中关键信息 ~~~ /**   * Full information about a particular process that   * is currently running.   */   class ProcessRecord {       final ApplicationInfo info; // all about the first app in the process       final boolean isolated;     // true if this is a special isolated process       final int uid;              // uid of process; may be different from 'info' if isolated       final int userId;           // user of process.       final String processName;   // name of the process          IApplicationThread thread;  // the actual proc...  may be null only if                                   // 'persistent' is true (in which case we                                   // are in the process of launching the app)                                   // 是ApplicationThread对象的远程接口,                                   // 通过此接口通知Activity进入对应的状态                                          int pid;                    // The process of this application; 0 if none       ApplicationInfo instrumentationInfo; // the application being instrumented          BroadcastRecord curReceiver;// receiver currently running in the app          // contains HistoryRecord objects       final ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();          // all ServiceRecord running in this process       final HashSet<ServiceRecord> services = new HashSet<ServiceRecord>();          // services that are currently executing code (need to remain foreground).       final HashSet<ServiceRecord> executingServices                = new HashSet<ServiceRecord>();          // All ConnectionRecord this process holds       final HashSet<ConnectionRecord> connections               = new HashSet<ConnectionRecord>();            // all IIntentReceivers that are registered from this process.       final HashSet<ReceiverList> receivers = new HashSet<ReceiverList>();          // class (String) -> ContentProviderRecord       final HashMap<String, ContentProviderRecord> pubProviders               = new HashMap<String, ContentProviderRecord>();           // All ContentProviderRecord process is using       final ArrayList<ContentProviderConnection> conProviders               = new ArrayList<ContentProviderConnection>();              boolean persistent;         // always keep this application running?       boolean crashing;           // are we in the process of crashing?       Dialog crashDialog;         // dialog being displayed due to crash.       boolean notResponding;      // does the app have a not responding dialog?       Dialog anrDialog;           // dialog being displayed due to app not resp.       boolean removed;            // has app package been removed from device?       boolean debugging;          // was app launched for debugging?       boolean waitedForDebugger;  // has process show wait for debugger dialog?       Dialog waitDialog;          // current wait for debugger dialog          ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, IApplicationThread _thread,               ApplicationInfo _info, String _processName, int _uid) {           batteryStats = _batteryStats;           info = _info;           isolated = _info.uid != _uid;           uid = _uid;           userId = UserHandle.getUserId(_uid);           processName = _processName;           pkgList.add(_info.packageName);           thread = _thread;           maxAdj = ProcessList.HIDDEN_APP_MAX_ADJ;           hiddenAdj = clientHiddenAdj = emptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ;           curRawAdj = setRawAdj = -100;           curAdj = setAdj = -100;           persistent = false;           removed = false;       }       ...   }   ~~~ ### 5. 2 家族图谱 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7c62c0a.jpg) ## 6. IApplicationThread接口AMS->Application IApplicationThread为AMS作为客户端访问Application服务器端的Binder接口。当创建Application时,将把此Binder对象传递给AMS,然后AMS把它保存在mProcessNames.ProcessRecord.thread中。当需要通知Application工作时,则调用IApplicationThread中对应的接口函数。 其相互关系如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7c8eaa0.jpg)
';

说说 PendingIntent 的内部机制

最后更新于:2022-04-01 09:53:28

http://www.tuicool.com/articles/22iMZj 说说 PendingIntent 的内部机制 侯 亮 ### 1 概述 在Android中,我们常常使用PendingIntent来表达一种“留待日后处理”的意思。从这个角度来说,PendingIntent可以被理解为一种特殊的异步处理机制。不过,单就命名而言,PendingIntent其实具有一定误导性,因为它既不继承于Intent,也不包含Intent,它的核心可以粗略地汇总成四个字——“异步激发”。  很明显,这种异步激发常常是要跨进程执行的。比如说A进程作为发起端,它可以从系统“获取”一个PendingIntent,然后A进程可以将PendingIntent对象通过binder机制“传递”给B进程,再由B进程在未来某个合适时机,“回调”PendingIntent对象的send()动作,完成激发。 在Android系统中,最适合做集中性管理的组件就是AMS(Activity Manager Service)啦,所以它义不容辞地承担起管理所有PendingIntent的职责。这样我们就可以画出如下示意图: ![image001](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7b90c01.png "image001") 注意其中的第4步“递送相应的intent”。这一步递送的intent是从何而来的呢?简单地说,当发起端获取PendingIntent时,其实是需要同时提供若干intent的。这些intent和PendingIntent只是配套的关系,而不是聚合的关系,它们会被缓存在AMS中。日后,一旦处理端将PendingIntent的“激发”语义传递到AMS,AMS就会尝试找到与这个PendingIntent对应的若干intent,并递送出去。 当然,以上说的只是大概情况,实际的技术细节会更复杂一点儿。下面我们就来谈谈细节。 ### 2 PendingIntent的技术细节 #### 2.1 发起端获取PendingIntent 我们先要理解,所谓的“发起端获取PendingIntent”到底指的是什么。难道只是简单new一个PendingIntent对象吗?当然不是。此处的“获取”动作其实还含有向AMS“注册”intent的语义。 在PendingIntent.java文件中,我们可以看到有如下几个比较常见的静态函数: - public static PendingIntent **getActivity** (Context context, int requestCode, Intent intent, int flags) - public static PendingIntent **getBroadcast** (Context context, int requestCode, Intent intent, int flags) - public static PendingIntent **getService** (Context context, int requestCode, Intent intent, int flags) - public static PendingIntent **getActivities** (Context context, int requestCode, Intent[] intents, int flags) - public static PendingIntent **getActivities** (Context context, int requestCode, Intent[] intents, int flags, Bundle options) 它们就是我们常用的获取PendingIntent的动作了。 坦白说,这几个函数的命名可真不怎么样,所以我们简单解释一下。上面的getActivity()的意思其实是,获取一个PendingIntent对象,而且该对象日后激发时所做的事情是启动一个新activity。也就是说,当它异步激发时,会执行类似Context.startActivity()那样的动作。相应地,getBroadcast()和getService()所获取的PendingIntent对象在激发时,会分别执行类似Context..sendBroadcast()和Context.startService()这样的动作。至于最后两个getActivities(),用得比较少,激发时可以启动几个activity。 我们以getActivity()的代码来说明问题: ~~~ public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags, Bundle options) { String packageName = context.getPackageName(); String resolvedType = intent != null ? intent.resolveTypeIfNeeded(context.getContentResolver()) : null; try { intent.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags, options); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) {} return null; } ~~~ 其中那句new PendingIntent(target)创建了PendingIntent对象,其重要性自不待言。然而,这个对象的内部核心其实是由上面那个getIntentSender()函数得来的。而这个IIntentSender核心才是我们真正需要关心的东西。 说穿了,此处的IIntentSender对象是个binder代理,它对应的binder实体是AMS中的PendingIntentRecord对象。PendingIntent对象构造之时,IIntentSender代理作为参数传进来,并记录在PendingIntent的mTarget域。日后,当PendingIntent执行异步激发时,其内部就是靠这个mTarget域向AMS传递语义的。 我们前文说过,PendingIntent常常会经由binder机制,传递到另一个进程去。而binder机制可以保证,目标进程得到的PendingIntent的mTarget域也是合法的IIntentSender代理,而且和发起端的IIntentSender代理对应着同一个PendingIntentRecord实体。示意图如下: ![image002](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7ba7d7d.png "image002") #### 2.2 AMS里的PendingIntentRecord 那么PendingIntentRecord里又有什么信息呢?它的定义截选如下: ~~~ class PendingIntentRecord extends IIntentSender.Stub { final ActivityManagerService owner; final Key key; // 最关键的key域 final int uid; final WeakReference<PendingIntentRecord> ref; boolean sent = false; boolean canceled = false; String stringName; . . . . . . } ~~~ 请注意其中那个key域。这里的Key是个PendingIntentRecord的内嵌类,其定义截选如下: ~~~ final static class Key { final int type; final String packageName; final ActivityRecord activity; final String who; final int requestCode; final Intent requestIntent; // 注意! final String requestResolvedType; final Bundle options; Intent[] allIntents; // 注意!记录了当初获取PendingIntent时,用户所指定的所有intent String[] allResolvedTypes; final int flags; final int hashCode; . . . . . . . . . . . . } ~~~ 请注意其中的allIntents[]数组域以及requestIntent域。前者记录了当初获取PendingIntent时,用户所指定的所有intent(虽然一般情况下只会指定一个intent,但类似getActivities()这样的函数还是可以指定多个intent的),而后者可以粗浅地理解为用户所指定的那个intent数组中的最后一个intent。现在大家应该清楚异步激发时用到的intent都存在哪里了吧。 Key的构造函数截选如下: ~~~ Key(int _t, String _p, ActivityRecord _a, String _w, int _r, Intent[] _i, String[] _it, int _f, Bundle _o) { type = _t; packageName = _p; activity = _a; who = _w; requestCode = _r; requestIntent = _i != null ? _i[_i.length-1] : null; // intent数组中的最后一个 requestResolvedType = _it != null ? _it[_it.length-1] : null; allIntents = _i; // 所有intent allResolvedTypes = _it; flags = _f; options = _o; . . . . . . } ~~~ Key不光承担着记录信息的作用,它还承担“键值”的作用。 #### 2.3 AMS中的PendingIntentRecord总表 在AMS中,管理着系统中所有的PendingIntentRecord节点,所以需要把这些节点组织成一张表: ~~~ final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords ~~~ 这张哈希映射表的键值类型就是刚才所说的PendingIntentRecord.Key。 以后每当我们要获取PendingIntent对象时,PendingIntent里的mTarget是这样得到的:AMS会先查mIntentSenderRecords表,如果能找到符合的PendingIntentRecord节点,则返回之。如果找不到,就创建一个新的PendingIntentRecord节点。因为PendingIntentRecord是个binder实体,所以经过binder机制传递后,客户进程拿到的就是个合法的binder代理。如此一来,前文的示意图可以进一步修改成下图: ![image003](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7bbf9f8.png "image003") #### 2.4 AMS里的getIntentSender()函数 现在,我们回过头继续说前文的getActivity(),以及其调用的getIntentSender()。我们先列一遍getActivity()的原型: ~~~ public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags, Bundle options) ~~~ - context参数是调用方的上下文。 - requestCode是个简单的整数,起区分作用。 - intent是异步激发时将发出的intent。 - flags可以包含一些既有的标识,比如FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT等等。不少同学对这个域不是很清楚,我们后文会细说。 - options可以携带一些额外的数据。 getActivity()的代码很简单,其参数基本上都传给了getIntentSender()。 ~~~ IIntentSender target = ActivityManagerNative.getDefault().getIntentSender(. . . . . .) ~~~ getIntentSender()的原型大体是这样的: ~~~ public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) throws RemoteException; ~~~ 其参数比getActivity()要多一些,我们逐个说明。 type参数表明PendingIntent的类型。getActivity()和getActivities()动作里指定的类型值是INTENT_SENDER_ACTIVITY,getBroadcast()和getService()和动作里指定的类型值分别是INTENT_SENDER_BROADCAST和INTENT_SENDER_SERVICE。另外,在Activity.java文件中,我们还看到一个createPendingResult()函数,这个函数表达了发起方的activity日后希望得到result回馈的意思,所以其内部调用getIntentSender()时指定的类型值为INTENT_SENDER_ACTIVITY_RESULT。 packageName参数表示发起端所属的包名。 token参数是个指代回馈目标方的代理。这是什么意思呢?我们常用的getActivity()、getBroadcast()和getService()中,只是把这个参数简单地指定为null,表示这个PendingIntent激发时,是不需要发回什么回馈的。不过当我们希望获取类型为INTENT_SENDER_ACTIVITY_RESULT的PendingIntent时,就需要指定token参数了。具体可参考createPendingResult()的代码: ~~~ public PendingIntent createPendingResult(int requestCode, Intent data, int flags) { String packageName = getPackageName(); try { data.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, mParent == null ? mToken : mParent.mToken, mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { // Empty } return null; } ~~~ 看到了吗?传入的token为Activity的mToken或者其mParent.mToken。说得简单点儿,AMS内部可以根据这个token找到其对应的ActivityRecord,日后当PendingIntent激发时,AMS可以根据这个ActivityRecord确定出该向哪个目标进程的哪个Activity发出result语义。 resultWho参数和token参数息息相关,一般也是null啦。在createPendingResult()中,其值为Activity的mEmbeddedID字符串。 requestCode参数是个简单的整数,可以在获取PendingIntent时由用户指定,它可以起区分的作用。 intents数组参数是异步激发时希望发出的intent。对于getActivity()、getBroadcast()和getService()来说,都只会指定一个intent而已。只有getActivities()会尝试一次传入若干intent。 resolvedTypes参数基本上和intent是相关的。一般是这样得到的: ~~~ String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; ~~~ 这个值常常和intent内部的mData URI有关系,比如最终的值可能是URI对应的MIME类型。 flags参数可以指定PendingIntent的一些行为特点。它的取值是一些既有的比特标识的组合。目前可用的标识有:FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT等等。有时候,flags中还可以附带若干FILL_IN_XXX标识。我们把常见的标识定义列举如下: 【PendingIntent中】 ~~~ public static final int FLAG_ONE_SHOT = 1<<30; public static final int FLAG_NO_CREATE = 1<<29; public static final int FLAG_CANCEL_CURRENT = 1<<28; public static final int FLAG_UPDATE_CURRENT = 1<<27; ~~~ 【Intent中】 ~~~ public static final int FILL_IN_ACTION = 1<<0; public static final int FILL_IN_DATA = 1<<1; public static final int FILL_IN_CATEGORIES = 1<<2; public static final int FILL_IN_COMPONENT = 1<<3; public static final int FILL_IN_PACKAGE = 1<<4; public static final int FILL_IN_SOURCE_BOUNDS = 1<<5; public static final int FILL_IN_SELECTOR = 1<<6; public static final int FILL_IN_CLIP_DATA = 1<<7; ~~~ 这些以FILL_IN_打头的标志位,主要是在intent对象的fillIn()函数里起作用: ~~~ public int fillIn(Intent other, int flags) ~~~ 我们以FILL_IN_ACTION为例来说明,当一个当我们执行类似srcIntent.fillIn(otherIntent, ...)的句子时,如果otherIntent的mAction域不是null值,那么fillIn()会在以下两种情况下,用otherIntent的mAction域值为srcIntent的mAction域赋值: 1) 当srcIntent的mAction域值为null时;  2) 如果fillIn的flags参数里携带了FILL_IN_ACTION标志位,那么即便srcIntent的mAction已经有值了,此时也会用otherIntent的mAction域值强行替换掉srcIntent的mAction域值。 其他FILL_IN_标志位和FILL_IN_ACTION的处理方式类似,我们不再赘述。         options参数可以携带一些额外数据。 ### 2.4.1 getIntentSender()函数         getIntentSender()函数摘录如下: ~~~ public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) { . . . . . . // 先判断intents数组,可以用伪代码checkIntents(intents)来表示 // checkIntents(intents); . . . . . . int callingUid = Binder.getCallingUid(); . . . . . . if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { int uid = AppGlobals.getPackageManager().getPackageUid(packageName, UserId.getUserId(callingUid)); if (!UserId.isSameApp(callingUid, uid)) { . . . . . . throw new SecurityException(msg); } } . . . . . . return getIntentSenderLocked(type, packageName, Binder.getOrigCallingUid(), token, resultWho, requestCode, intents, resolvedTypes, flags, options); . . . . . . } ~~~ getIntentSender()函数中有一段逐条判断intents[]的代码,我用伪代码checkIntents(intents)来表示,这部分对应的实际代码如下: ~~~ for (int i=0; i<intents.length; i++) { Intent intent = intents[i]; if (intent != null) { if (intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } if (type == ActivityManager.INTENT_SENDER_BROADCAST && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { throw new IllegalArgumentException("Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); } intents[i] = new Intent(intent); } } ~~~ 这段代码说明在获取PendingIntent对象时,intent中是不能携带文件描述符的。而且如果这个PendingIntent是那种要发出广播的PendingIntent,那么intent中也不能携带FLAG_RECEIVER_BOOT_UPGRADE标识符。“BOOT_UPGRADE”应该是“启动并升级”的意思,它不能使用PendingIntent。         getIntentSender()中最核心的一句应该是调用getIntentSenderLocked()的那句。 ### 2.4.2 getIntentSenderLocked()函数         getIntentSenderLocked()的代码截选如下: 【frameworks/base/services/java/com/android/server/am/ActivityManagerService.java】 ~~~ IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) { . . . . . . // 如果是INTENT_SENDER_ACTIVITY_RESULT类型,那么要判断token所 // 代表的activity是否还在activity栈中 . . . . . . // 整理flags中的信息 . . . . . . PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, activity, resultWho, requestCode, intents, resolvedTypes, flags, options); // 尽力从哈希映射表中查找key对应的PendingIntentRecord,如果找不到就创建一个新的节点。 WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; if (rec != null) { // 找到了匹配的PendingIntent,现在考虑要不要更新它,或者取消它。 if (!cancelCurrent) { if (updateCurrent) { // 如果明确指定了FLAG_UPDATE_CURRENT,那么更新找到的节点 if (rec.key.requestIntent != null) { rec.key.requestIntent.replaceExtras(intents != null ? intents[intents.length - 1] : null); } if (intents != null) { intents[intents.length-1] = rec.key.requestIntent; rec.key.allIntents = intents; rec.key.allResolvedTypes = resolvedTypes; } else { rec.key.allIntents = null; rec.key.allResolvedTypes = null; } } // 凡是能找到对应的节点,而且又不取消该节点的,那么就return这个节点 return rec; } // 如果PendingIntent的标志中带有FLAG_CANCEL_CURRENT,则从哈希映射表中删除之 rec.canceled = true; mIntentSenderRecords.remove(key); } if (noCreate) { // 如果明确表示了不创建新节点,也就是说标志中带有FLAG_NO_CREATE, // 那么不管是不是Cancel了PendingIntent,此时一概直接返回。 return rec; } // 从哈希映射表中找不到,而且又没有写明FLAG_NO_CREATE,此时创建一个新节点 rec = new PendingIntentRecord(this, key, callingUid); mIntentSenderRecords.put(key, rec.ref); if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { // 如果intent需要返回结果,那么修改token对应的ActivityRecord // 的pendingResults域。 if (activity.pendingResults == null) { activity.pendingResults = new HashSet<WeakReference<PendingIntentRecord>>(); } activity.pendingResults.add(rec.ref); } return rec; } ~~~ 上面这段代码主要做的事情有: 1) 将传进来的多个参数信息整理成一个PendingIntentRecord.Key对象(key);  2) 尝试从mIntentSenderRecords总表中查找和key相符的PendingIntentRecord节点;  3) 根据flags参数所含有的意义,对得到的PendingIntentRecord进行加工。有时候修改之,有时候删除之。  4) 如果在总表中没有找到对应的PendingIntentRecord节点,或者根据flags的语义删除了刚找到的节点,那么此时的默认行为是创建一个新的PendingIntentRecord节点,并插入总表。除非flags中明确指定了FLAG_NO_CREATE,此时不会创建新节点。 ### 2.4.3 说说flags 从getIntentSenderLocked()的代码中,我们终于搞明白了flags中那些特定比特值的意义了。我们现在总结一下。 应该说这些flags比特值基本上都是在围绕着mIntentSenderRecords总表说事的。其中,FLAG_CANCEL_CURRENT的意思是,当我们获取PendingIntent时,如果可以从总表中查到一个相符的已存在的PendingIntentRecord节点的话,那么需要把这个节点从总表中清理出去。而在没有指定FLAG_CANCEL_CURRENT的大前提下,如果用户指定了FLAG_UPDATE_CURRENT标识,那么会用新的intents参数替掉刚查到的PendingIntentRecord中的旧intents。 而不管是刚清理了已存在的PendingIntentRecord,还是压根儿就没有找到符合的PendingIntentRecord,只要用户没有明确指定FLAG_NO_CREATE标识,系统就会尽力创建一个新的PendingIntentRecord节点,并插入总表。 至于FLAG_ONE_SHOT标识嘛,它并没有在getIntentSenderLocked()中露脸儿。它的名字是“FLAG_ONE_SHOT”,也就是“只打一枪”的意思,那么很明显,这个标识起作用的地方应该是在“激发”函数里。在最终的激发函数(sendInner())里,我们可以看到下面的代码: 【frameworks/base/services/java/com/android/server/am/PendingIntentRecord.java】 ~~~ int sendInner(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) { synchronized(owner) { if (!canceled) { sent = true; if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) { owner.cancelIntentSenderLocked(this, true); canceled = true; } . . . . . . . . . . . . } } return ActivityManager.START_CANCELED; } ~~~ 意思很简单,一进行激发就把相应的PendingIntentRecord节点从总表中清理出去,而且把PendingIntentRecord的canceled域设为true。这样,以后即便外界再调用send()动作都没用了,因为再也无法进入if (!canceled)判断了。 ### 2.4.4 将PendingIntentRecord节点插入总表 接下来getIntentSenderLocked()函数new了一个PendingIntentRecord节点,并将之插入mIntentSenderRecords总表中。 ### 2.5 PendingIntent的激发动作 下面我们来看PendingIntent的激发动作。在前文我们已经说过,当需要激发PendingIntent之时,主要是通过调用PendingIntent的send()函数来完成激发动作的。PendingIntent提供了多个形式的send()函数,然而这些函数的内部其实调用的是同一个send(),其函数原型如下: ~~~ public void send(Context context, int code, Intent intent, OnFinished onFinished, Handler handler, String requiredPermission) throws CanceledException ~~~ 该函数内部最关键的一句是: ~~~ int res = mTarget.send(code, intent, resolvedType, onFinished != null ? new FinishedDispatcher(this, onFinished, handler) : null, requiredPermission); ~~~ 我们前文已经介绍过这个mTarget域了,它对应着AMS中的某个PendingIntentRecord。 所以我们要看一下PendingIntentRecord一侧的send()函数,其代码如下: ~~~ public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission) { return sendInner(code, intent, resolvedType, finishedReceiver, requiredPermission, null, null, 0, 0, 0, null); } ~~~ 其中sendInner()才是真正做激发动作的函数。 sendInner()完成的主要逻辑动作有: 1) 如果当前PendingIntentRecord节点已经处于canceled域为true的状态,那么说明这个节点已经被取消掉了,此时sendInner()不会做任何实质上的激发动作,只是简单地return ActivityManager.START_CANCELED而已。  2) 如果当初在创建这个节点时,使用者已经指定了FLAG_ONE_SHOT标志位的话,那么此时sendInner()会把这个PendingIntentRecord节点从AMS中的总表中摘除,并且把canceled域设为true。而后的操作和普通激发时的动作是一致的,也就是说也会走下面的第3)步。  3) 关于普通激发时应执行的逻辑动作是,根据当初创建PendingIntentRecord节点时,用户指定的type类型,进行不同的处理。这个type其实就是我们前文所说的INTENT_SENDER_ACTIVITY、INTENT_SENDER_BROADCAST、INTENT_SENDER_SERVICE等类型啦,大家如有兴趣,可自己参考本文一开始所说的getActivity()、getBroadcast()、getService()等函数的实现代码。 现在还有一个问题是,既然我们在当初获取PendingIntent时,已经指定了日后激发时需要递送的intent(或intent数组),那么为什么send()动作里还有一个intent参数呢?它们的关系又是什么呢?我猜想,PendingIntent机制的设计者是希望给激发端一个修改“待激发的intent”的机会。比如当初我们获取PendingIntent对象时,如果在flags里设置了FILL_IN_ACTION标志位,那么就说明我们允许日后在某个激发点,用新的intent的mAction域值,替换掉我们最初给的intent的mAction域值。如果一开始没有设置FILL_IN_ACTION标志位,而且在最初的intent里已经有了非空的mAction域值的话,那么即使在激发端又传入了新intent,它也不可能修改用新intent的mAction域值替换旧intent的mAction域值。 细心的读者一定记得,当初获取PendingIntent对象时,我们可是向AMS端传递了一个intent数组噢,虽然一般情况下这个数组里只有一个intent元素,但有时候我们也是有可能一次性传递多个intent的。比如getActivities()函数就可以一次传递多个intent。可是现在激发动作send()却只能传递一个intent参数,这该如何处理呢?答案很简单,所传入的intent只能影响已有的intent数组的最后一个intent元素。大家可以看看sendInner里allIntents[allIntents.length-1] = finalIntent;一句。 Ok,intent说完了,下面就该做具体的激发了。我们以简单的INTENT_SENDER_BROADCAST型PendingIntentRecord来说明,此时的激发动作就是发送一个广播: ~~~ owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType, finishedReceiver, code, null, null, requiredPermission, (finishedReceiver != null), false, UserId.getUserId(uid)); ~~~ 至于其他类型的PendingIntentRecord的激发动作,大家可以自行查阅代码,它们的基本代码格局都是差不多的。 ### 3 小结 本文是基于我早先的一点儿笔记整理而成的。当时为了搞清楚PendingIntent的机理,也查阅了一些网上的相关文章,只是都不大满足我的要求,后来只好自己看代码,终于得了些自己的浅见。现在把我过去的一点儿认识整理出来,希望能对学习PendingIntent的同学有点儿帮助。
';

Android应用AsyncTask处理机制详解及源码分析

最后更新于:2022-04-01 09:53:25

【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点。前面我们分析了Handler异步机制原理(不了解的可以阅读我的[《Android异步消息处理机制详解及源码分析》](http://blog.csdn.net/yanbober/article/details/45936145)文章),这里继续分析Android的另一个异步机制AsyncTask的原理。 当使用线程和Handler组合实现异步处理时,当每次执行耗时操作都创建一条新线程进行处理,性能开销会比较大。为了提高性能我们使用AsyncTask实现异步处理(其实也是线程和handler组合实现),因为其内部使用了java提供的线程池技术,有效的降低了线程创建数量及限定了同时运行的线程数,还有一些针对性的对池的优化操作。所以说AsyncTask是Android为我们提供的方便编写异步任务的工具类。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 2 实例演示 先看下使用AsyncTask模拟下载的效果图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7b75e7f.jpg "") 看下代码,如下: ~~~ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new TestAsyncTask(this).execute(); } static final class TestAsyncTask extends AsyncTask<Void, Integer, Boolean> { //如上三个泛型参数从左到右含义依次为: //1. 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。 //2. 后台任务执行时,如果需要在界面上显示当前的进度,则使用这个。 //3. 当任务执行完毕后,如果需要对结果进行返回,则使用这个。 private Context mContext = null; private ProgressDialog mDialog = null; private int mCount = 0; public TestAsyncTask(Context context) { mContext = context; } //在后台任务开始执行之间调用,用于进行一些界面上的初始化操作 protected void onPreExecute() { super.onPreExecute(); mDialog = new ProgressDialog(mContext); mDialog.setMax(100); mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mDialog.show(); } //这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务 protected Boolean doInBackground(Void... params) { while (mCount < 100) { publishProgress(mCount); mCount += 20; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return true; } //当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用 protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); mDialog.setProgress(values[0]); } //当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用 protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); if (aBoolean && mDialog != null && mDialog.isShowing()) { mDialog.dismiss(); } } } } ~~~ 可以看见Android帮我们封装好的AsyncTask还是很方便使用的,咱们不做过多说明。接下来直接分析源码。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 3 Android5.1.1(API 22)AsyncTask源码分析 通过源码可以发现AsyncTask是一个抽象类,所以我们在在上面使用时需要实现它。 那怎么下手分析呢?很简单,我们就依据上面示例的流程来分析源码,具体如下。 #### 3-1 AsyncTask实例化源码分析 ~~~ /** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */ public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } }; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; } ~~~ 看见注释没有,AsyncTask的实例化只能在UI线程中。然后整个构造函数就只初始化了两个AsyncTask类的成员变量(mWorker和mFuture)。mWorker 为匿名内部类的实例对象WorkerRunnable(实现了Callable接口),mFuture为匿名内部类的实例对象FutureTask,传入了mWorker作为形参(重写了 FutureTask类的done方法)。 #### 3-2 AsyncTask的execute方法源码分析 正如上面实例一样,得到AsyncTask实例化对象之后就执行了execute方法,所以看下execute方法的源码,如下: ~~~ public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } ~~~ 可以看见,execute调运了executeOnExecutor方法,executeOnExecutor方法除过传入了params形参以外,还传入了一个static的SerialExecutor对象 (SerialExecutor实现了Executor接口)。继续看下executeOnExecutor源码,如下: ~~~ public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; } ~~~ 首先判断AsyncTask异步任务的状态,当处于RUNNING和FINISHED时就报IllegalStateException非法状态异常。由此可以看见一个AsyncTask的 execute方法只能被调运一次。接着看见17行onPreExecute();没有?看下这个方法源码,如下: ~~~ /** * Runs on the UI thread before {@link #doInBackground}. * * @see #onPostExecute * @see #doInBackground */ protected void onPreExecute() { } ~~~ 空方法,而且通过注释也能看见,这不就是我们AsyncTask中第一个执行的方法吗?是的。 回过头继续往下看,看见20行exec.execute(mFuture);代码没?exec就是形参出入的上面定义的static SerialExecutor对象(SerialExecutor实现了Executor接口),所以execute就是SerialExecutor静态内部类的方法喽,在执行execute方法时还传入了AsyncTask构造函数中实例化的第二个成员变量mFuture。我们来看下SerialExecutor静态内部类的代码,如下: ~~~ private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } } ~~~ 在源码中可以看见,SerialExecutor在AsyncTask中是以常量的形式被使用的,所以在整个应用程序中的所有AsyncTask实例都会共用同一个 SerialExecutor对象。接着可以看见,SerialExecutor是使用ArrayDeque这个队列来管理Runnable对象的,如果我们一次性启动了很多个任务,首先在第一次运行execute()方法的时候会调用ArrayDeque的offer()方法将传入的Runnable对象添加到队列的最后,然后判断mActive对象是不是等于null,第一次运行是null,然后调用scheduleNext()方法,在这个方法中会从队列的头部取值,并赋值给mActive对象,然后调用THREAD_POOL_EXECUTOR去执行取出的取出的Runnable对象。之后如果再有新的任务被执行时就等待上一个任务执行完毕后才会得到执行,所以说同一时刻只会有一个线程正在执行,其余的均处于等待状态,这就是SerialExecutor类的核心作用。 我们再来看看上面用到的THREAD_POOL_EXECUTOR与execute,如下: ~~~ public abstract class AsyncTask<Params, Progress, Result> { ...... private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); /** * An {@link Executor} that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); ...... } ~~~ 看见没有,实质就是在一个线程池中执行,这个THREAD_POOL_EXECUTOR线程池是一个常量,也就是说整个App中不论有多少AsyncTask都只有这 一个线程池。也就是说上面SerialExecutor类中execute()方法的所有逻辑就是在子线程中执行,注意SerialExecutor的execute方法有一个Runnable参数 ,这个参数就是mFuture对象,所以我们看下FutureTask类的run()方法,如下源码: ~~~ public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } } ~~~ 看见没有?第7行的c = callable;其实就是AsyncTask构造函数中实例化FutureTask对象时传入的参数mWorker。12行看见`result = c.call();`没有? 其实就是调运WorkerRunnable类的call方法,所以我们回到AsyncTask构造函数的WorkerRunnable匿名内部内中可以看见如下: ~~~ mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } }; ~~~ 看见没有?在postResult()方法的参数里面,我们可以看见doInBackground()方法。所以这验证了我们上面例子中使用的AsyncTask,首先在主线程执 行onPreExecute方法,接着在子线程执行doInBackground方法,所以这也就是为什么我们可以在doInBackground()方法中去处理耗时操作的原因了 ,接着等待doInBackground方法耗时操作执行完毕以后将返回值传递给了postResult()方法。所以我们来看下postResult这个方法的源码,如下: ~~~ private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; } ~~~ 先看下这个getHandler拿到的是哪个Handler吧,如下: ~~~ private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } } } ~~~ 看见没有,拿到的是MainLooper,也就是说在在UI线程中的Handler(不清楚的请阅读[《Android异步消息处理机制详解及源码分析》](http://blog.csdn.net/yanbober/article/details/45936145)文章)。所以 上面的方法其实就是将子线程的数据发送到了UI来处理,也就是通过MESSAGE_POST_RESULT在handleMessage来处理。所以我们继续看 handleMessage中的result.mTask.finish(result.mData[0]);就会发现finish的代码如下: ~~~ private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; } ~~~ 看见没有?依据返回值true与false回调AsyncTask的onPostExecute或者onCancelled方法。 到此是不是会好奇onProgressUpdate方法啥时候调运的呢?继续往下看可以发现handleMessage方法中的MESSAGE_POST_PROGRESS不就是回调我们UI Thread中的onProgressUpdate方法吗?那怎么样才能让他回调呢?追踪MESSAGE_POST_PROGRESS消息你会发现如下: ~~~ protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } } ~~~ 额,没意思了。这不就是我们上面例子中的在子线程的doInBackground耗时操作中调运通知回调onProgressUpdate的方法么。 看见没有,AsyncTask的实质就是Handler异步消息处理机制(不清楚的请阅读[《Android异步消息处理机制详解及源码分析》](http://blog.csdn.net/yanbober/article/details/45936145)文章),只是对线程做了优化处理和封装而已。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 4 为当年低版本AsyncTask的臭名正身 接触Android比较久的可能都知道,在Android 3.0之前是并没有SerialExecutor这个类的(上面有分析)。那些版本的代码是直接创建了指定大小的线程池常量来执行task的。其中MAXIMUM_POOL_SIZE = 128;,所以那时候如果我们应用中一个界面需要同时创建的AsyncTask线程大于128(批量获取数据,譬如照片浏览瀑布流一次加载)程序直接就挂了。所以当时的AsyncTask因为这个原因臭名昭著。 回过头来看看现在高版本的AsyncTask,是不是没有这个问题了吧?因为现在是顺序执行的。而且更劲爆的是现在的AsyncTask还直接提供了客户化实现Executor接口功能,使用如下方法执行AsyncTask即可使用自定义Executor,如下: ~~~ public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { ...... return this; } ~~~ 可以看出,在3.0以上版中AsyncTask已经不存在那个臭名昭著的Bug了,所以可以放心使用了,妈妈再也不用担心我的AsyncTask出Bug了。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 5 AsyncTask与Handler异步机制对比 前面文章也分析过Handler了,这里也分析了AsyncTask,现在把他们两拽一起来比较比较。具体如下: 1. AsyncTask是对Handler与Thread的封装。 1. AsyncTask在代码上比Handler要轻量级别,但实际上比Handler更耗资源,因为AsyncTask底层是一个线程池,而Handler仅仅就是发送了一个消息队列。但是,如果异步任务的数据特别庞大,AsyncTask线程池比Handler节省开销,因为Handler需要不停的new Thread执行。 1. AsyncTask的实例化只能在主线程,Handler可以随意,只和Looper有关系。 ### 6 AsyncTask总结 到此整个Android的AsyncTask已经分析完毕,相信你现在对于AsyncTask会有一个很深入的理解与认识了。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 版权声明:本文为博主原创文章,未经博主允许不得转载。
';

Activity启动过程全解析

最后更新于:2022-04-01 09:53:23

http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287 > It’s right time to learn Android’s Framework ! - [前言](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#前言) - [学习目标](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#学习目标) - [写作方式](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#写作方式) - [主要对象功能介绍](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#主要对象功能介绍) - [主要流程介绍](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#主要流程介绍) - [zygote是什么有什么作用](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#zygote是什么有什么作用) - [SystemServer是什么有什么作用它与zygote的关系是什么](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#systemserver是什么有什么作用它与zygote的关系是什么) - [ActivityManagerService是什么什么时候初始化的有什么作用](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#activitymanagerservice是什么什么时候初始化的有什么作用) - [Launcher是什么什么时候启动的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#launcher是什么什么时候启动的) - [Instrumentation是什么和ActivityThread是什么关系](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#instrumentation是什么和activitythread是什么关系) - [如何理解AMS和ActivityThread之间的Binder通信](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#如何理解ams和activitythread之间的binder通信) - [AMS接收到客户端的请求之后会如何开启一个Activity](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#ams接收到客户端的请求之后会如何开启一个activity) - [送给你们的彩蛋](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#送给你们的彩蛋) - [不要使用 startActivityForResultintentRESULT_OK](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#不要使用-startactivityforresultintentresultok) - [一个App的程序入口到底是什么](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#一个app的程序入口到底是什么) - [整个App的主线程的消息循环是在哪里创建的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#整个app的主线程的消息循环是在哪里创建的) - [Application是在什么时候创建的onCreate什么时候调用的](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#application是在什么时候创建的oncreate什么时候调用的) - [参考文章](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#参考文章) - [Binder](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#binder) - [zygote](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#zygote) - [ActivityThreadInstrumentationAMS](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#activitythreadinstrumentationams) - [Launcher](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#launcher) - [结语](http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287#结语) # 前言 - 一个App是怎么启动起来的? - App的程序入口到底是哪里? - Launcher到底是什么神奇的东西? - 听说还有个AMS的东西,它是做什么的? - Binder是什么?他是如何进行IPC通信的? - Activity生命周期到底是什么时候调用的?被谁调用的? - 等等… 你是不是还有很多类似的疑问一直没有解决?没关系,这篇文章将结合源码以及大量的优秀文章,站在巨人的肩膀上,更加通俗的来试着解释一些问题。但是毕竟源码繁多、经验有限,文中不免会出现一些纰漏甚至是错误,还恳请大家指出,互相学习。 # 学习目标 1. 了解从手机开机第一个zygote进程创建,到点击桌面上的图标,进入一个App的完整流程,并且从源码的角度了解到一个Activity的生命周期是怎么回事 1. 了解到ActivityManagerServices(即AMS)、ActivityStack、ActivityThread、Instrumentation等Android framework中非常重要的基础类的作用,及相互间的关系 1. 了解AMS与ActivityThread之间利用Binder进行IPC通信的过程,了解AMS和ActivityThread在控制Activity生命周期起到的作用和相互之间的配合 1. 了解与Activity相关的framework层的其他琐碎问题 # 写作方式 这篇文章我决定采用一问一答的方式进行。 其实在这之前,我试过把每个流程的代码调用过程,用粘贴源代码的方式写在文章里,但是写完一部分之后,发现由于代码量太大,整篇文章和老太太的裹脚布一样——又臭又长,虽然每个重要的操作可以显示出详细调用过程,但是太关注于细节反而导致从整体上不能很好的把握。所以在原来的基础之上进行了修改,对关键的几个步骤进行重点介绍,力求语言简洁,重点突出,从而让大家在更高的层次上对framework层有个认识,然后结合后面我给出的参考资料,大家就可以更加快速,更加高效的了解这一块的整体架构。 # 主要对象功能介绍 我们下面的文章将围绕着这几个类进行介绍。可能你第一次看的时候,印象不深,不过没关系,当你跟随者我读完这篇文章的时候,我会在最后再次列出这些对象的功能,相信那时候你会对这些类更加的熟悉和深刻。 - ActivityManagerServices,简称AMS,服务端对象,负责系统中所有Activity的生命周期 - ActivityThread,App的真正入口。当开启App之后,会调用main()开始运行,开启消息循环队列,这就是传说中的UI线程或者叫主线程。与ActivityManagerServices配合,一起完成Activity的管理工作 - ApplicationThread,用来实现ActivityManagerService与ActivityThread之间的交互。在ActivityManagerService需要管理相关Application中的Activity的生命周期时,通过ApplicationThread的代理对象与ActivityThread通讯。 - ApplicationThreadProxy,是ApplicationThread在服务器端的代理,负责和客户端的ApplicationThread通讯。AMS就是通过该代理与ActivityThread进行通信的。 - Instrumentation,每一个应用程序只有一个Instrumentation对象,每个Activity内都有一个对该对象的引用。Instrumentation可以理解为应用进程的管家,ActivityThread要创建或暂停某个Activity时,都需要通过Instrumentation来进行具体的操作。 - ActivityStack,Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。 - ActivityRecord,ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。 - TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这个概念应该不陌生。 # 主要流程介绍 下面将按照App启动过程的先后顺序,一问一答,来解释一些事情。 让我们开始吧! ### zygote是什么?有什么作用? 首先,你觉得这个单词眼熟不?当你的程序Crash的时候,打印的红色log下面通常带有这一个单词。 zygote意为“受精卵“。Android是基于Linux系统的,而在Linux中,所有的进程都是由init进程直接或者是间接fork出来的,zygote进程也不例外。 在Android系统里面,zygote是一个进程的名字。Android是基于Linux System的,当你的手机开机的时候,Linux的内核加载完成之后就会启动一个叫“init“的进程。在Linux System里面,所有的进程都是由init进程fork出来的,我们的zygote进程也不例外。 我们都知道,每一个App其实都是 - 一个单独的dalvik虚拟机 - 一个单独的进程 所以当系统里面的第一个zygote进程运行之后,在这之后再开启App,就相当于开启一个新的进程。而为了实现资源共用和更快的启动速度,Android系统开启新进程的方式,是通过fork第一个zygote进程实现的。所以说,除了第一个zygote进程,其他应用所在的进程都是zygote的子进程,这下你明白为什么这个进程叫“受精卵”了吧?因为就像是一个受精卵一样,它能快速的分裂,并且产生遗传物质一样的细胞! ### SystemServer是什么?有什么作用?它与zygote的关系是什么? 首先我要告诉你的是,SystemServer也是一个进程,而且是由zygote进程fork出来的。 知道了SystemServer的本质,我们对它就不算太陌生了,这个进程是Android Framework里面两大非常重要的进程之一——另外一个进程就是上面的zygote进程。 为什么说SystemServer非常重要呢?因为系统里面重要的服务都是在这个进程里面开启的,比如  ActivityManagerService、PackageManagerService、WindowManagerService等等,看着是不是都挺眼熟的? 那么这些系统服务是怎么开启起来的呢? 在zygote开启的时候,会调用ZygoteInit.main()进行初始化 ~~~ public static void main(String argv[]) { ...ignore some code... //在加载首个zygote的时候,会传入初始化参数,使得startSystemServer = true boolean startSystemServer = false; for (int i = 1; i < argv.length; i++) { if ("start-system-server".equals(argv[i])) { startSystemServer = true; } else if (argv[i].startsWith(ABI_LIST_ARG)) { abiList = argv[i].substring(ABI_LIST_ARG.length()); } else if (argv[i].startsWith(SOCKET_NAME_ARG)) { socketName = argv[i].substring(SOCKET_NAME_ARG.length()); } else { throw new RuntimeException("Unknown command line argument: " + argv[i]); } } ...ignore some code... //开始fork我们的SystemServer进程 if (startSystemServer) { startSystemServer(abiList, socketName); } ...ignore some code... } ~~~ 我们看下startSystemServer()做了些什么 ~~~ /**留着这个注释,就是为了说明SystemServer确实是被fork出来的 * Prepare the arguments and fork for the system server process. */ private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller, RuntimeException { ...ignore some code... //留着这段注释,就是为了说明上面ZygoteInit.main(String argv[])里面的argv就是通过这种方式传递进来的 /* Hardcoded command line to start the system server */ String args[] = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1032,3001,3002,3003,3006,3007", "--capabilities=" + capabilities + "," + capabilities, "--runtime-init", "--nice-name=system_server", "com.android.server.SystemServer", }; int pid; try { parsedArgs = new ZygoteConnection.Arguments(args); ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); //确实是fuck出来的吧,我没骗你吧~不对,是fork出来的 -_-||| /* Request to fork the system server process */ pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */ if (pid == 0) { if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } handleSystemServerProcess(parsedArgs); } return true; } ~~~ ActivityManagerService是什么?什么时候初始化的?有什么作用? ActivityManagerService,简称AMS,服务端对象,负责系统中所有Activity的生命周期。 ActivityManagerService进行初始化的时机很明确,就是在SystemServer进程开启的时候,就会初始化ActivityManagerService。从下面的代码中可以看到 ~~~ public final class SystemServer { //zygote的主入口 public static void main(String[] args) { new SystemServer().run(); } public SystemServer() { // Check for factory test mode. mFactoryTestMode = FactoryTest.getMode(); } private void run() { ...ignore some code... //加载本地系统服务库,并进行初始化 System.loadLibrary("android_servers"); nativeInit(); // 创建系统上下文 createSystemContext(); //初始化SystemServiceManager对象,下面的系统服务开启都需要调用SystemServiceManager.startService(Class<T>),这个方法通过反射来启动对应的服务 mSystemServiceManager = new SystemServiceManager(mSystemContext); //开启服务 try { startBootstrapServices(); startCoreServices(); startOtherServices(); } catch (Throwable ex) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting system services", ex); throw ex; } ...ignore some code... } //初始化系统上下文对象mSystemContext,并设置默认的主题,mSystemContext实际上是一个ContextImpl对象。调用ActivityThread.systemMain()的时候,会调用ActivityThread.attach(true),而在attach()里面,则创建了Application对象,并调用了Application.onCreate()。 private void createSystemContext() { ActivityThread activityThread = ActivityThread.systemMain(); mSystemContext = activityThread.getSystemContext(); mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar); } //在这里开启了几个核心的服务,因为这些服务之间相互依赖,所以都放在了这个方法里面。 private void startBootstrapServices() { ...ignore some code... //初始化ActivityManagerService mActivityManagerService = mSystemServiceManager.startService( ActivityManagerService.Lifecycle.class).getService(); mActivityManagerService.setSystemServiceManager(mSystemServiceManager); //初始化PowerManagerService,因为其他服务需要依赖这个Service,因此需要尽快的初始化 mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class); // 现在电源管理已经开启,ActivityManagerService负责电源管理功能 mActivityManagerService.initPowerManagement(); // 初始化DisplayManagerService mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class); //初始化PackageManagerService mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); ...ignore some code... } } ~~~ 经过上面这些步骤,我们的ActivityManagerService对象已经创建好了,并且完成了成员变量初始化。而且在这之前,调用createSystemContext()创 建系统上下文的时候,也已经完成了mSystemContext和ActivityThread的创建。注意,这是系统进程开启时的流程,在这之后,会开启系统的 Launcher程序,完成系统界面的加载与显示。你是否会好奇,我为什么说AMS是服务端对象?下面我给你介绍下Android系统里面的服务器和客户端的概念。 其实服务器客户端的概念不仅仅存在于Web开发中,在Android的框架设计中,使用的也是这一种模式。服务器端指的就是所有App共用的系统服务,比如我们这里提到的ActivityManagerService,和前面提到的PackageManagerService、WindowManagerService等等,这些基础的系统服务是被所有的App公用的,当某个App想实现某个操作的时候,要告诉这些系统服务,比如你想打开一个App,那么我们知道了包名和MainActivity类名之后就可以打开 ~~~ Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); ComponentName cn = new ComponentName(packageName, className); intent.setComponent(cn); startActivity(intent); ~~~ 但是,我们的App通过调用startActivity()并不能直接打开另外一个App,这个方法会通过一系列的调用,最后还是告诉AMS说:“我要打开这个App, 我知道他的住址和名字,你帮我打开吧!”所以是AMS来通知zygote进程来fork一个新进程,来开启我们的目标App的。这就像是浏览器想要打开一 个超链接一样,浏览器把网页地址发送给服务器,然后还是服务器把需要的资源文件发送给客户端的。知道了Android Framework的客户端服务器架构之后,我们还需要了解一件事情,那就是我们的App和AMS(SystemServer进程)还有zygote进程分属于三个独立的进程,他们之间如何通信呢? App与AMS通过Binder进行IPC通信,AMS(SystemServer进程)与zygote通过Socket进行IPC通信。 那么AMS有什么用呢?在前面我们知道了,如果想打开一个App的话,需要AMS去通知zygote进程,除此之外,其实所有的Activity的开启、暂停、关闭都需要AMS来控制,所以我们说,AMS负责系统中所有Activity的生命周期。 在Android系统中,任何一个Activity的启动都是由AMS和应用程序进程(主要是ActivityThread)相互配合来完成的。AMS服务统一调度系统中所有进程的Activity启动,而每个Activity的启动过程则由其所属的进程具体来完成。 这样说你可能还是觉得比较抽象,没关系,下面有一部分是专门来介绍AMS与ActivityThread如何一起合作控制Activity的生命周期的。 ### Launcher是什么?什么时候启动的? 当我们点击手机桌面上的图标的时候,App就由Launcher开始启动了。但是,你有没有思考过Launcher到底是一个什么东西? Launcher本质上也是一个应用程序,和我们的App一样,也是继承自Activity packages/apps/Launcher2/src/com/android/launcher2/Launcher.java ~~~ public final class Launcher extends Activity implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, View.OnTouchListener { } ~~~ Launcher实现了点击、长按等回调接口,来接收用户的输入。既然是普通的App,那么我们的开发经验在这里就仍然适用,比如,我们点击图标的时候 ,是怎么开启的应用呢?如果让你,你怎么做这个功能呢?捕捉图标点击事件,然后startActivity()发送对应的Intent请求呗!是的,Launcher也是这么 做的,就是这么easy! 那么到底是处理的哪个对象的点击事件呢?既然Launcher是App,并且有界面,那么肯定有布局文件呀,是的,我找到了布局文件launcher.xml ~~~ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" android:id="@+id/launcher"> <com.android.launcher2.DragLayer android:id="@+id/drag_layer" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!-- Keep these behind the workspace so that they are not visible when we go into AllApps --> <include android:id="@+id/dock_divider" layout="@layout/workspace_divider" android:layout_marginBottom="@dimen/button_bar_height" android:layout_gravity="bottom" /> <include android:id="@+id/paged_view_indicator" layout="@layout/scroll_indicator" android:layout_gravity="bottom" android:layout_marginBottom="@dimen/button_bar_height" /> <!-- The workspace contains 5 screens of cells --> <com.android.launcher2.Workspace android:id="@+id/workspace" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingStart="@dimen/workspace_left_padding" android:paddingEnd="@dimen/workspace_right_padding" android:paddingTop="@dimen/workspace_top_padding" android:paddingBottom="@dimen/workspace_bottom_padding" launcher:defaultScreen="2" launcher:cellCountX="@integer/cell_count_x" launcher:cellCountY="@integer/cell_count_y" launcher:pageSpacing="@dimen/workspace_page_spacing" launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left" launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right"> <include android:id="@+id/cell1" layout="@layout/workspace_screen" /> <include android:id="@+id/cell2" layout="@layout/workspace_screen" /> <include android:id="@+id/cell3" layout="@layout/workspace_screen" /> <include android:id="@+id/cell4" layout="@layout/workspace_screen" /> <include android:id="@+id/cell5" layout="@layout/workspace_screen" /> </com.android.launcher2.Workspace> ...ignore some code... </com.android.launcher2.DragLayer> </FrameLayout> ~~~ 为了方便查看,我删除了很多代码,从上面这些我们应该可以看出一些东西来:Launcher大量使用标签来实现界面的复用,而且定义了很多的自定义控 件实现界面效果,dock_divider从布局的参数声明上可以猜出,是底部操作栏和上面图标布局的分割线,而paged_view_indicator则是页面指示器, 和App首次进入的引导页下面的界面引导是一样的道理。当然,我们最关心的是Workspace这个布局,因为注释里面说在这里面包含了5个屏幕的单元 格,想必你也猜到了,这个就是在首页存放我们图标的那五个界面(不同的ROM会做不同的DIY,数量不固定)。 接下来,我们应该打开workspace_screen布局,看看里面有什么东东。 workspace_screen.xml ~~~ <com.android.launcher2.CellLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingStart="@dimen/cell_layout_left_padding" android:paddingEnd="@dimen/cell_layout_right_padding" android:paddingTop="@dimen/cell_layout_top_padding" android:paddingBottom="@dimen/cell_layout_bottom_padding" android:hapticFeedbackEnabled="false" launcher:cellWidth="@dimen/workspace_cell_width" launcher:cellHeight="@dimen/workspace_cell_height" launcher:widthGap="@dimen/workspace_width_gap" launcher:heightGap="@dimen/workspace_height_gap" launcher:maxGap="@dimen/workspace_max_gap" /> ~~~ 里面就一个CellLayout,也是一个自定义布局,那么我们就可以猜到了,既然可以存放图标,那么这个自定义的布局很有可能是继承自ViewGroup或者 是其子类,实际上,CellLayout确实是继承自ViewGroup。在CellLayout里面,只放了一个子View,那就是ShortcutAndWidgetContainer。从名字也 可以看出来,ShortcutAndWidgetContainer这个类就是用来存放快捷图标和Widget小部件的,那么里面放的是什么对象呢? 在桌面上的图标,使用的是BubbleTextView对象,这个对象在TextView的基础之上,添加了一些特效,比如你长按移动图标的时候,图标位置会出现一个背景(不同版本的效果不同),所以我们找到BubbleTextView对象的点击事件,就可以找到Launcher如何开启一个App了。 除了在桌面上有图标之外,在程序列表中点击图标,也可以开启对应的程序。这里的图标使用的不是BubbleTextView对象,而是PagedViewIcon对象,我们如果找到它的点击事件,就也可以找到Launcher如何开启一个App。 其实说这么多,和今天的主题隔着十万八千里,上面这些东西,你有兴趣就看,没兴趣就直接跳过,不知道不影响这篇文章阅读。 BubbleTextView的点击事件在哪里呢?我来告诉你:在Launcher.onClick(View v)里面。 ~~~ /** * Launches the intent referred by the clicked shortcut */ public void onClick(View v) { ...ignore some code... Object tag = v.getTag(); if (tag instanceof ShortcutInfo) { // Open shortcut final Intent intent = ((ShortcutInfo) tag).intent; int[] pos = new int[2]; v.getLocationOnScreen(pos); intent.setSourceBounds(new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight())); //开始开启Activity咯~ boolean success = startActivitySafely(v, intent, tag); if (success && v instanceof BubbleTextView) { mWaitingForResume = (BubbleTextView) v; mWaitingForResume.setStayPressed(true); } } else if (tag instanceof FolderInfo) { //如果点击的是图标文件夹,就打开文件夹 if (v instanceof FolderIcon) { FolderIcon fi = (FolderIcon) v; handleFolderClick(fi); } } else if (v == mAllAppsButton) { ...ignore some code... } } ~~~ 从上面的代码我们可以看到,在桌面上点击快捷图标的时候,会调用 ~~~ startActivitySafely(v, intent, tag); ~~~ 那么从程序列表界面,点击图标的时候会发生什么呢?实际上,程序列表界面使用的是AppsCustomizePagedView对象,所以我在这个类里面找到了 onClick(View v)。 com.android.launcher2.AppsCustomizePagedView.java ~~~ /** * The Apps/Customize page that displays all the applications, widgets, and shortcuts. */ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements View.OnClickListener, View.OnKeyListener, DragSource, PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener, LauncherTransitionable { @Override public void onClick(View v) { ...ignore some code... if (v instanceof PagedViewIcon) { mLauncher.updateWallpaperVisibility(true); mLauncher.startActivitySafely(v, appInfo.intent, appInfo); } else if (v instanceof PagedViewWidget) { ...ignore some code.. } } } ~~~ 可以看到,调用的是 ~~~ mLauncher.startActivitySafely(v, appInfo.intent, appInfo); ~~~ 和上面一样!这叫什么?这叫殊途同归! 所以咱们现在又明白了一件事情:不管从哪里点击图标,调用的都是Launcher.startActivitySafely()。 - 下面我们就可以一步步的来看一下Launcher.startActivitySafely()到底做了什么事情。 ~~~ boolean startActivitySafely(View v, Intent intent, Object tag) { boolean success = false; try { success = startActivity(v, intent, tag); } catch (ActivityNotFoundException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e); } return success; } ~~~ - 调用了startActivity(v, intent, tag) ~~~ boolean startActivity(View v, Intent intent, Object tag) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { boolean useLaunchAnimation = (v != null) && !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); if (useLaunchAnimation) { if (user == null || user.equals(android.os.Process.myUserHandle())) { startActivity(intent, opts.toBundle()); } else { launcherApps.startMainActivity(intent.getComponent(), user, intent.getSourceBounds(), opts.toBundle()); } } else { if (user == null || user.equals(android.os.Process.myUserHandle())) { startActivity(intent); } else { launcherApps.startMainActivity(intent.getComponent(), user, intent.getSourceBounds(), null); } } return true; } catch (SecurityException e) { ... } return false; } ~~~ - 这里会调用Activity.startActivity(intent, opts.toBundle()),这个方法熟悉吗?这就是我们经常用到的Activity.startActivity(Intent)的重载函数。 - 而且由于设置了 ~~~ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ~~~ - 所以这个Activity会添加到一个新的Task栈中,而且,startActivity()调用的其实是startActivityForResult()这个方法。 ~~~ @Override public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } ~~~ - 所以我们现在明确了,Launcher中开启一个App,其实和我们在Activity中直接startActivity()基本一样,都是调用了Activity.startActivityForResult()。 ### Instrumentation是什么?和ActivityThread是什么关系? 还记得前面说过的Instrumentation对象吗?每个Activity都持有Instrumentation对象的一个引用,但是整个进程只会存在一个Instrumentation对象。当startActivityForResult()调用之后,实际上还是调用了mInstrumentation.execStartActivity() ~~~ public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } ...ignore some code... } else { if (options != null) { //当现在的Activity有父Activity的时候会调用,但是在startActivityFromChild()内部实际还是调用的mInstrumentation.execStartActivity() mParent.startActivityFromChild(this, intent, requestCode, options); } else { mParent.startActivityFromChild(this, intent, requestCode); } } ...ignore some code... } ~~~ 下面是mInstrumentation.execStartActivity()的实现 ~~~ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; ...ignore some code... try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { } return null; } ~~~ 所以当我们在程序中调用startActivity()的 时候,实际上调用的是Instrumentation的相关的方法。 Instrumentation意为“仪器”,我们先看一下这个类里面包含哪些方法吧 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7b31985.png "") 我们可以看到,这个类里面的方法大多数和Application和Activity有关,是的,这个类就是完成对Application和Activity初始化和生命周期的工具类。比如说,我单独挑一个callActivityOnCreate()让你看看 ~~~ public void callActivityOnCreate(Activity activity, Bundle icicle) { prePerformCreate(activity); activity.performCreate(icicle); postPerformCreate(activity); } ~~~ 对activity.performCreate(icicle);这一行代码熟悉吗?这一行里面就调用了传说中的Activity的入口函数onCreate(),不信?接着往下看 Activity.performCreate() ~~~ final void performCreate(Bundle icicle) { onCreate(icicle); mActivityTransitionState.readState(icicle); performCreateCommon(); } ~~~ 没骗你吧,onCreate在这里调用了吧。但是有一件事情必须说清楚,那就是这个Instrumentation类这么重要,为啥我在开发的过程中,没有发现他的踪迹呢? 是的,Instrumentation这个类很重要,对Activity生命周期方法的调用根本就离不开他,他可以说是一个大管家,但是,这个大管家比较害羞,是一个女的,管内不管外,是老板娘~ 那么你可能要问了,老板是谁呀?  老板当然是大名鼎鼎的ActivityThread了! ActivityThread你都没听说过?那你肯定听说过传说中的UI线程吧?是的,这就是UI线程。我们前面说过,App和AMS是通过Binder传递信息的,那么ActivityThread就是专门与AMS的外交工作的。 AMS说:“ActivityThread,你给我暂停一个Activity!”  ActivityThread就说:“没问题!”然后转身和Instrumentation说:“老婆,AMS让暂停一个Activity,我这里忙着呢,你快去帮我把这事办了把~”  于是,Instrumentation就去把事儿搞定了。 所以说,AMS是董事会,负责指挥和调度的,ActivityThread是老板,虽然说家里的事自己说了算,但是需要听从AMS的指挥,而Instrumentation则是老板娘,负责家里的大事小事,但是一般不抛头露面,听一家之主ActivityThread的安排。 ### 如何理解AMS和ActivityThread之间的Binder通信? 前面我们说到,在调用startActivity()的时候,实际上调用的是 ~~~ mInstrumentation.execStartActivity() ~~~ 但是到这里还没完呢!里面又调用了下面的方法 ~~~ ActivityManagerNative.getDefault() .startActivity ~~~ 这里的ActivityManagerNative.getDefault返回的就是ActivityManagerService的远程接口,即ActivityManagerProxy。 怎么知道的呢?往下看 ~~~ public abstract class ActivityManagerNative extends Binder implements IActivityManager { //从类声明上,我们可以看到ActivityManagerNative是Binder的一个子类,而且实现了IActivityManager接口 static public IActivityManager getDefault() { return gDefault.get(); } //通过单例模式获取一个IActivityManager对象,这个对象通过asInterface(b)获得 private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity"); if (false) { Log.v("ActivityManager", "default service binder = " + b); } IActivityManager am = asInterface(b); if (false) { Log.v("ActivityManager", "default service = " + am); } return am; } }; } //最终返回的还是一个ActivityManagerProxy对象 static public IActivityManager asInterface(IBinder obj) { if (obj == null) { return null; } IActivityManager in = (IActivityManager)obj.queryLocalInterface(descriptor); if (in != null) { return in; } //这里面的Binder类型的obj参数会作为ActivityManagerProxy的成员变量保存为mRemote成员变量,负责进行IPC通信 return new ActivityManagerProxy(obj); } } ~~~ 再看ActivityManagerProxy.startActivity(),在这里面做的事情就是IPC通信,利用Binder对象,调用transact(),把所有需要的参数封装成Parcel对象 ,向AMS发送数据进行通信。 ~~~ public int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); data.writeString(callingPackage); intent.writeToParcel(data, 0); data.writeString(resolvedType); data.writeStrongBinder(resultTo); data.writeString(resultWho); data.writeInt(requestCode); data.writeInt(startFlags); if (profilerInfo != null) { data.writeInt(1); profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { data.writeInt(0); } if (options != null) { data.writeInt(1); options.writeToParcel(data, 0); } else { data.writeInt(0); } mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); reply.recycle(); data.recycle(); return result; } ~~~ Binder本质上只是一种底层通信方式,和具体服务没有关系。为了提供具体服务,Server必须提供一套接口函数以便Client通过远程访问使用各种服务。 这时通常采用Proxy设计模式:将接口函数定义在一个抽象类中,Server和Client都会以该抽象类为基类实现所有接口函数,所不同的是Server端是真正 的功能实现,而Client端是对这些函数远程调用请求的包装。 为了更方便的说明客户端和服务器之间的Binder通信,下面以ActivityManagerServices和他在客户端的代理类ActivityManagerProxy为例。 ActivityManagerServices和ActivityManagerProxy都实现了同一个接口——IActivityManager。 ~~~ class ActivityManagerProxy implements IActivityManager{} public final class ActivityManagerService extends ActivityManagerNative{} public abstract class ActivityManagerNative extends Binder implements IActivityManager{} ~~~ 虽然都实现了同一个接口,但是代理对象ActivityManagerProxy并不会对这些方法进行真正地实现,ActivityManagerProxy只是通过这种方式对方法 的参数进行打包(因为都实现了相同接口,所以可以保证同一个方法有相同的参数,即对要传输给服务器的数据进行打包),真正实现的是ActivityManagerService。 但是这个地方并不是直接由客户端传递给服务器,而是通过Binder驱动进行中转。其实我对Binder驱动并不熟悉,我们就把他当做一个中转站就OK,客户端调用ActivityManagerProxy接口里面的方法,把数据传送给Binder驱动,然后Binder驱动就会把这些东西转发给服务器的ActivityManagerServices,由ActivityManagerServices去真正的实施具体的操作。 但是Binder只能传递数据,并不知道是要调用ActivityManagerServices的哪个方法,所以在数据中会添加方法的唯一标识码,比如前面的startActivity()方法: ~~~ public int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); ...ignore some code... mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); reply.recycle(); data.recycle(); return result; } ~~~ 上面的START_ACTIVITY_TRANSACTION就是方法标示,data是要传输给Binder驱动的数据,reply则接受操作的返回值。 即 客户端:ActivityManagerProxy =====>Binder驱动=====> ActivityManagerService:服务器 而且由于继承了同样的公共接口类,ActivityManagerProxy提供了与ActivityManagerService一样的函数原型,使用户感觉不出Server是运行在本地还是远端,从而可以更加方便的调用这些重要的系统服务。 但是!这里Binder通信是单方向的,即从ActivityManagerProxy指向ActivityManagerService的,如果AMS想要通知ActivityThread做一些事情,应该咋办呢? 还是通过Binder通信,不过是换了另外一对,换成了ApplicationThread和ApplicationThreadProxy。 客户端:ApplicationThread <=====Binder驱动<===== ApplicationThreadProxy:服务器 他们也都实现了相同的接口IApplicationThread ~~~ private class ApplicationThread extends ApplicationThreadNative {} public abstract class ApplicationThreadNative extends Binder implements IApplicationThread{} class ApplicationThreadProxy implements IApplicationThread {} ~~~ 剩下的就不必多说了吧,和前面一样。 ### AMS接收到客户端的请求之后,会如何开启一个Activity? OK,至此,点击桌面图标调用startActivity(),终于把数据和要开启Activity的请求发送到了AMS了。说了这么多,其实这些都在一瞬间完成了,下面咱们研究下AMS到底做了什么。 注:前方有高能的方法调用链,如果你现在累了,请先喝杯咖啡或者是上趟厕所休息下 AMS收到startActivity的请求之后,会按照如下的方法链进行调用 调用startActivity() ~~~ @Override public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle options) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, options, UserHandle.getCallingUserId()); } ~~~ 调用startActivityAsUser() ~~~ @Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) { ...ignore some code... return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, null, null, options, userId, null, null); } ~~~ 在这里又出现了一个新对象ActivityStackSupervisor,通过这个类可以实现对ActivityStack的部分操作。 ~~~ final int startActivityMayWait(IApplicationThread caller, int callingUid, String callingPackage, Intent intent, String resolvedType, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, WaitResult outResult, Configuration config, Bundle options, int userId, IActivityContainer iContainer, TaskRecord inTask) { ...ignore some code... int res = startActivityLocked(caller, intent, resolvedType, aInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, componentSpecified, null, container, inTask); ...ignore some code... } ~~~ 继续调用startActivityLocked() ~~~ final int startActivityLocked(IApplicationThread caller, Intent intent, String resolvedType, ActivityInfo aInfo, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int realCallingPid, int realCallingUid, int startFlags, Bundle options, boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container, TaskRecord inTask) { err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true, options, inTask); if (err < 0) { notifyActivityDrawnForKeyguard(); } return err; } ~~~ 调用startActivityUncheckedLocked(),此时要启动的Activity已经通过检验,被认为是一个正当的启动请求。 终于,在这里调用到了ActivityStack的startActivityLocked(ActivityRecord r, boolean newTask,boolean doResume, boolean keepCurTransition, Bundle options)。 ActivityRecord代表的就是要开启的Activity对象,里面分装了很多信息,比如所在的ActivityTask等,如果这是首次打开应用,那么这个Activity会被放到ActivityTask的栈顶, ~~~ final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, Bundle options, TaskRecord inTask) { ...ignore some code... targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options); ...ignore some code... return ActivityManager.START_SUCCESS; } ~~~ 调用的是ActivityStack.startActivityLocked() ~~~ final void startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume, boolean keepCurTransition, Bundle options) { //ActivityRecord中存储的TaskRecord信息 TaskRecord rTask = r.task; ...ignore some code... //如果不是在新的ActivityTask(也就是TaskRecord)中的话,就找出要运行在的TaskRecord对象 TaskRecord task = null; if (!newTask) { boolean startIt = true; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { task = mTaskHistory.get(taskNdx); if (task.getTopActivity() == null) { // task中的所有Activity都结束了 continue; } if (task == r.task) { // 找到了 if (!startIt) { task.addActivityToTop(r); r.putInHistory(); mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); if (VALIDATE_TOKENS) { validateAppTokensLocked(); } ActivityOptions.abort(options); return; } break; } else if (task.numFullscreen > 0) { startIt = false; } } } ...ignore some code... // Place a new activity at top of stack, so it is next to interact // with the user. task = r.task; task.addActivityToTop(r); task.setFrontOfTask(); ...ignore some code... if (doResume) { mStackSupervisor.resumeTopActivitiesLocked(this, r, options); } } ~~~ 靠!这来回折腾什么呢!从ActivityStackSupervisor到ActivityStack,又调回ActivityStackSupervisor,这到底是在折腾什么玩意啊!!! 淡定…淡定…我知道你也在心里骂娘,世界如此美妙,你却如此暴躁,这样不好,不好… 来来来,咱们继续哈,刚才说到哪里了?哦,对,咱们一起看下StackSupervisor.resumeTopActivitiesLocked(this, r, options) ~~~ boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target, Bundle targetOptions) { if (targetStack == null) { targetStack = getFocusedStack(); } // Do targetStack first. boolean result = false; if (isFrontStack(targetStack)) { result = targetStack.resumeTopActivityLocked(target, targetOptions); } ...ignore some code... return result; } ~~~ 我…已无力吐槽了,又调回ActivityStack去了… ActivityStack.resumeTopActivityLocked() ~~~ final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { if (inResumeTopActivity) { // Don't even start recursing. return false; } boolean result = false; try { // Protect against recursion. inResumeTopActivity = true; result = resumeTopActivityInnerLocked(prev, options); } finally { inResumeTopActivity = false; } return result; } ~~~ 咱们坚持住,看一下ActivityStack.resumeTopActivityInnerLocked()到底进行了什么操作 ~~~ final boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) { ...ignore some code... //找出还没结束的首个ActivityRecord ActivityRecord next = topRunningActivityLocked(null); //如果一个没结束的Activity都没有,就开启Launcher程序 if (next == null) { ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home"); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); // Only resume home if on home display final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ? HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo(); return isOnHomeDisplay() && mStackSupervisor.resumeHomeStackTask(returnTaskType, prev); } //先需要暂停当前的Activity。因为我们是在Lancher中启动mainActivity,所以当前mResumedActivity!=null,调用startPausingLocked()使得Launcher进入Pausing状态 if (mResumedActivity != null) { pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause); if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing " + mResumedActivity); } } ~~~ 在这个方法里,prev.app为记录启动Lancher进程的ProcessRecord,prev.app.thread为Lancher进程的远程调用接口IApplicationThead,所以可以 调用prev.app.thread.schedulePauseActivity,到Lancher进程暂停指定Activity。 ~~~ final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, boolean resuming, boolean dontWait) { if (mPausingActivity != null) { completePauseLocked(false); } ...ignore some code... if (prev.app != null && prev.app.thread != null) try { mService.updateUsageStats(prev, false); prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing, userLeaving, prev.configChangeFlags, dontWait); } catch (Exception e) { mPausingActivity = null; mLastPausedActivity = null; mLastNoHistoryActivity = null; } } else { mPausingActivity = null; mLastPausedActivity = null; mLastNoHistoryActivity = null; } ...ignore some code... } ~~~ 在Lancher进程中消息传递,调用ActivityThread.handlePauseActivity(),最终调用ActivityThread.performPauseActivity()暂停指定Activity。接着通 过前面所说的Binder通信,通知AMS已经完成暂停的操作。 ~~~ ActivityManagerNative.getDefault().activityPaused(token). ~~~ 上面这些调用过程非常复杂,源码中各种条件判断让人眼花缭乱,所以说如果你没记住也没关系,你只要记住这个流程,理解了Android在控制Activity 生命周期时是如何操作,以及是通过哪几个关键的类进行操作的就可以了,以后遇到相关的问题之道从哪块下手即可,这些过程我虽然也是撸了一遍,但 还是记不清。最后来一张高清无码大图,方便大家记忆: [请戳这里(图片3.3M,请用电脑观看)](http://i11.tietuku.com/0582844414810f38.png) # 送给你们的彩蛋 ### 不要使用 startActivityForResult(intent,RESULT_OK) 这是因为startActivity()是这样实现的 ~~~ public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } ~~~ 而 ~~~ public static final int RESULT_OK = -1; ~~~ 所以 ~~~ startActivityForResult(intent,RESULT_OK) = startActivity() ~~~ 你不可能从onActivityResult()里面收到任何回调。而这个问题是相当难以被发现的,就是因为这个坑,我工作一年多来第一次加班到9点 (ˇˍˇ) ### 一个App的程序入口到底是什么? 是ActivityThread.main()。 ### 整个App的主线程的消息循环是在哪里创建的? 是在ActivityThread初始化的时候,就已经创建消息循环了,所以在主线程里面创建Handler不需要指定Looper,而如果在其他线程使用Handler,则需要单独使用Looper.prepare()和Looper.loop()创建消息循环。 ~~~ public static void main(String[] args) { ...ignore some code... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); ...ignore some code... } ~~~ Application是在什么时候创建的?onCreate()什么时候调用的? 也是在ActivityThread.main()的时候,再具体点呢,就是在thread.attach(false)的时候。 看你的表情,不信是吧!凯子哥带你溜溜~ 我们先看一下ActivityThread.attach() ~~~ private void attach(boolean system) { sCurrentActivityThread = this; mSystemThread = system; //普通App进这里 if (!system) { ...ignore some code... RuntimeInit.setApplicationObject(mAppThread.asBinder()); final IActivityManager mgr = ActivityManagerNative.getDefault(); try { mgr.attachApplication(mAppThread); } catch (RemoteException ex) { // Ignore } } else { //这个分支在SystemServer加载的时候会进入,通过调用 // private void createSystemContext() { // ActivityThread activityThread = ActivityThread.systemMain(); //} // public static ActivityThread systemMain() { // if (!ActivityManager.isHighEndGfx()) { // HardwareRenderer.disable(true); // } else { // HardwareRenderer.enableForegroundTrimming(); // } // ActivityThread thread = new ActivityThread(); // thread.attach(true); // return thread; // } } } ~~~ 这里需要关注的就是mgr.attachApplication(mAppThread),这个就会通过Binder调用到AMS里面对应的方法 ~~~ @Override public final void attachApplication(IApplicationThread thread) { synchronized (this) { int callingPid = Binder.getCallingPid(); final long origId = Binder.clearCallingIdentity(); attachApplicationLocked(thread, callingPid); Binder.restoreCallingIdentity(origId); } } ~~~ 然后就是 ~~~ private final boolean attachApplicationLocked(IApplicationThread thread, int pid) { thread.bindApplication(processName, appInfo, providers, app.instrumentationClass, profilerInfo, app.instrumentationArguments, app.instrumentationWatcher, app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent, new Configuration(mConfiguration), app.compat, getCommonServicesLocked(), mCoreSettingsObserver.getCoreSettingsLocked()); } ~~~ thread是IApplicationThread,实际上就是ApplicationThread在服务端的代理类ApplicationThreadProxy,然后又通过IPC就会调用到 ApplicationThread的对应方法 ~~~ private class ApplicationThread extends ApplicationThreadNative { public final void bindApplication(String processName, ApplicationInfo appInfo, List<ProviderInfo> providers, ComponentName instrumentationName, ProfilerInfo profilerInfo, Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, IUiAutomationConnection instrumentationUiConnection, int debugMode, boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent, Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) { ...ignore some code... AppBindData data = new AppBindData(); data.processName = processName; data.appInfo = appInfo; data.providers = providers; data.instrumentationName = instrumentationName; data.instrumentationArgs = instrumentationArgs; data.instrumentationWatcher = instrumentationWatcher; data.instrumentationUiAutomationConnection = instrumentationUiConnection; data.debugMode = debugMode; data.enableOpenGlTrace = enableOpenGlTrace; data.restrictedBackupMode = isRestrictedBackupMode; data.persistent = persistent; data.config = config; data.compatInfo = compatInfo; data.initProfilerInfo = profilerInfo; sendMessage(H.BIND_APPLICATION, data); } } ~~~ 我们需要关注的其实就是最后的sendMessage(),里面有函数的编号H.BIND_APPLICATION,然后这个Messge会被H这个Handler处理 ~~~ private class H extends Handler { ...ignore some code... public static final int BIND_APPLICATION = 110; ...ignore some code... public void handleMessage(Message msg) { switch (msg.what) { ...ignore some code... case BIND_APPLICATION: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); AppBindData data = (AppBindData)msg.obj; handleBindApplication(data); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; ...ignore some code... } } ~~~ 最后就在下面这个方法中,完成了实例化,拨那个企鹅通过mInstrumentation.callApplicationOnCreate实现了onCreate()的调用。 ~~~ private void handleBindApplication(AppBindData data) { try { ...ignore some code... Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; ...ignore some code... try { mInstrumentation.onCreate(data.instrumentationArgs); } catch (Exception e) { } try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { } } finally { StrictMode.setThreadPolicy(savedPolicy); } } ~~~ data.info是一个LoadeApk对象。  LoadeApk.data.info.makeApplication() ~~~ public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } Application app = null; String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } try { java.lang.ClassLoader cl = getClassLoader(); if (!mPackageName.equals("android")) { initializeJavaContextClassLoader(); } ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { } mActivityThread.mAllApplications.add(app); mApplication = app; //传进来的是null,所以这里不会执行,onCreate在上一层执行 if (instrumentation != null) { try { instrumentation.callApplicationOnCreate(app); } catch (Exception e) { } } ...ignore some code... } return app; } ~~~ 所以最后还是通过Instrumentation.makeApplication()实例化的,这个老板娘真的很厉害呀! ~~~ static public Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); return app; } ~~~ 而且通过反射拿到Application对象之后,直接调用attach(),所以attach()调用是在onCreate()之前的。 # 参考文章 下面的这些文章都是这方面比较精品的,希望你抽出时间研究,这可能需要花费很长时间,但是如果你想进阶为中高级开发者,这一步是必须的。 再次感谢下面这些文章的作者的分享精神。 ### Binder - [Android Bander设计与实现 - 设计篇](http://blog.csdn.net/universus/article/details/6211589) ### zygote - [Android系统进程Zygote启动过程的源代码分析](http://blog.csdn.net/luoshengyang/article/details/6768304) - [Android 之 zygote 与进程创建](http://blog.csdn.net/xieqibao/article/details/6581975) - [Zygote浅谈](http://www.th7.cn/Program/Android/201404/187670.shtml) ### ActivityThread、Instrumentation、AMS - [Android Activity.startActivity流程简介](http://blog.csdn.net/myarrow/article/details/14224273) - [Android应用程序进程启动过程的源代码分析](http://blog.csdn.net/luoshengyang/article/details/6747696#comments) - [框架层理解Activity生命周期(APP启动过程)](http://laokaddk.blog.51cto.com/368606/1206840) - [Android应用程序窗口设计框架介绍](http://blog.csdn.net/yangwen123/article/details/35987609) - [ActivityManagerService分析一:AMS的启动](http://www.xuebuyuan.com/2172927.html) - [Android应用程序窗口设计框架介绍](http://blog.csdn.net/yangwen123/article/details/35987609) ### Launcher - [Android 4.0 Launcher源码分析系列(一)](http://mobile.51cto.com/hot-312129.htm) - [Android Launcher分析和修改9——Launcher启动APP流程](http://www.cnblogs.com/mythou/p/3187881.html) # 结语 OK,到这里,这篇文章算是告一段落了,我们再回头看看一开始的几个问题,你还困惑吗? - 一个App是怎么启动起来的? - App的程序入口到底是哪里? - Launcher到底是什么神奇的东西? - 听说还有个AMS的东西,它是做什么的? - Binder是什么?他是如何进行IPC通信的? - Activity生命周期到底是什么时候调用的?被谁调用的? 再回过头来看看这些类,你还迷惑吗? - ActivityManagerServices,简称AMS,服务端对象,负责系统中所有Activity的生命周期 - ActivityThread,App的真正入口。当开启App之后,会调用main()开始运行,开启消息循环队列,这就是传说中的UI线程或者叫主线程。与ActivityManagerServices配合,一起完成Activity的管理工作 - ApplicationThread,用来实现ActivityManagerService与ActivityThread之间的交互。在ActivityManagerService需要管理相关Application中的Activity的生命周期时,通过ApplicationThread的代理对象与ActivityThread通讯。 - ApplicationThreadProxy,是ApplicationThread在服务器端的代理,负责和客户端的ApplicationThread通讯。AMS就是通过该代理与ActivityThread进行通信的。 - Instrumentation,每一个应用程序只有一个Instrumentation对象,每个Activity内都有一个对该对象的引用。Instrumentation可以理解为应用进程的管家,ActivityThread要创建或暂停某个Activity时,都需要通过Instrumentation来进行具体的操作。 - ActivityStack,Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。 - ActivityRecord,ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。 - TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这个概念应该不陌生。 如果你还感到迷惑的话,就把这篇文章多读几遍吧,信息量可能比较多,需要慢慢消化~ 尊重原创,转载请注明:From 凯子哥([http://blog.csdn.net/zhaokaiqiang1992](http://blog.csdn.net/zhaokaiqiang1992)) 侵权必究! 关注我的微博,可以获得更多精彩内容 [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7b5de49.png)](http://weibo.com/u/1783932377?s=6uyXnP)
';

Android ListView工作原理完全解析,带你从源码的角度彻底理解

最后更新于:2022-04-01 09:53:21

转载请注明出处:[http://blog.csdn.net/guolin_blog/article/details/44996879](http://blog.csdn.net/guolin_blog/article/details/44996879) 在Android所有常用的原生控件当中,用法最复杂的应该就是ListView了,它专门用于处理那种内容元素很多,手机屏幕无法展示出所有内容的情况。ListView可以使用列表的形式来展示内容,超出屏幕部分的内容只需要通过手指滑动就可以移动到屏幕内了。 另外ListView还有一个非常神奇的功能,我相信大家应该都体验过,即使在ListView中加载非常非常多的数据,比如达到成百上千条甚至更多,ListView都不会发生OOM或者崩溃,而且随着我们手指滑动来浏览更多数据时,程序所占用的内存竟然都不会跟着增长。那么ListView是怎么实现这么神奇的功能的呢?当初我就抱着学习的心态花了很长时间把ListView的源码通读了一遍,基本了解了它的工作原理,在感叹Google大神能够写出如此精妙代码的同时我也有所敬畏,因为ListView的代码量比较大,复杂度也很高,很难用文字表达清楚,于是我就放弃了把它写成一篇博客的想法。那么现在回想起来这件事我已经肠子都悔青了,因为没过几个月时间我就把当初梳理清晰的源码又忘的一干二净。于是现在我又重新定下心来再次把ListView的源码重读了一遍,那么这次我一定要把它写成一篇博客,分享给大家的同时也当成我自己的笔记吧。 首先我们先来看一下ListView的继承结构,如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7ad856c.jpg) 可以看到,ListView的继承结构还是相当复杂的,它是直接继承自的AbsListView,而AbsListView有两个子实现类,一个是ListView,另一个就是GridView,因此我们从这一点就可以猜出来,ListView和GridView在工作原理和实现上都是有很多共同点的。然后AbsListView又继承自AdapterView,AdapterView继承自ViewGroup,后面就是我们所熟知的了。先把ListView的继承结构了解一下,待会儿有助于我们更加清晰地分析代码。 ### Adapter的作用 Adapter相信大家都不会陌生,我们平时使用ListView的时候一定都会用到它。那么话说回来大家有没有仔细想过,为什么需要Adapter这个东西呢?总感觉正因为有了Adapter,ListView的使用变得要比其它控件复杂得多。那么这里我们就先来学习一下Adapter到底起到了什么样的一个作用。 其实说到底,控件就是为了交互和展示数据用的,只不过ListView更加特殊,它是为了展示很多很多数据用的,但是ListView只承担交互和展示工作而已,至于这些数据来自哪里,ListView是不关心的。因此,我们能设想到的最基本的ListView工作模式就是要有一个ListView控件和一个数据源。 不过如果真的让ListView和数据源直接打交道的话,那ListView所要做的适配工作就非常繁杂了。因为数据源这个概念太模糊了,我们只知道它包含了很多数据而已,至于这个数据源到底是什么样类型,并没有严格的定义,有可能是数组,也有可能是集合,甚至有可能是数据库表中查询出来的游标。所以说如果ListView真的去为每一种数据源都进行适配操作的话,一是扩展性会比较差,内置了几种适配就只有几种适配,不能动态进行添加。二是超出了它本身应该负责的工作范围,不再是仅仅承担交互和展示工作就可以了,这样ListView就会变得比较臃肿。 那么显然Android开发团队是不会允许这种事情发生的,于是就有了Adapter这样一个机制的出现。顾名思义,Adapter是适配器的意思,它在ListView和数据源之间起到了一个桥梁的作用,ListView并不会直接和数据源打交道,而是会借助Adapter这个桥梁来去访问真正的数据源,与之前不同的是,Adapter的接口都是统一的,因此ListView不用再去担心任何适配方面的问题。而Adapter又是一个接口(interface),它可以去实现各种各样的子类,每个子类都能通过自己的逻辑来去完成特定的功能,以及与特定数据源的适配操作,比如说ArrayAdapter可以用于数组和List类型的数据源适配,SimpleCursorAdapter可以用于游标类型的数据源适配,这样就非常巧妙地把数据源适配困难的问题解决掉了,并且还拥有相当不错的扩展性。简单的原理示意图如下所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7aeca8a.jpg) 当然Adapter的作用不仅仅只有数据源适配这一点,还有一个非常非常重要的方法也需要我们在Adapter当中去重写,就是getView()方法,这个在下面的文章中还会详细讲到。 ### RecycleBin机制 那么在开始分析ListView的源码之前,还有一个东西是我们提前需要了解的,就是RecycleBin机制,这个机制也是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。其实RecycleBin的代码并不多,只有300行左右,它是写在AbsListView中的一个内部类,所以所有继承自AbsListView的子类,也就是ListView和GridView,都可以使用这个机制。那我们来看一下RecycleBin中的主要代码,如下所示: ~~~ /** * The RecycleBin facilitates reuse of views across layouts. The RecycleBin * has two levels of storage: ActiveViews and ScrapViews. ActiveViews are * those views which were onscreen at the start of a layout. By * construction, they are displaying current information. At the end of * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews * are old views that could potentially be used by the adapter to avoid * allocating views unnecessarily. * * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) * @see android.widget.AbsListView.RecyclerListener */ class RecycleBin { private RecyclerListener mRecyclerListener; /** * The position of the first view stored in mActiveViews. */ private int mFirstActivePosition; /** * Views that were on screen at the start of layout. This array is * populated at the start of layout, and at the end of layout all view * in mActiveViews are moved to mScrapViews. Views in mActiveViews * represent a contiguous range of Views, with position of the first * view store in mFirstActivePosition. */ private View[] mActiveViews = new View[0]; /** * Unsorted views that can be used by the adapter as a convert view. */ private ArrayList<View>[] mScrapViews; private int mViewTypeCount; private ArrayList<View> mCurrentScrap; /** * Fill ActiveViews with all of the children of the AbsListView. * * @param childCount * The minimum number of views mActiveViews should hold * @param firstActivePosition * The position of the first view that will be stored in * mActiveViews */ void fillActiveViews(int childCount, int firstActivePosition) { if (mActiveViews.length < childCount) { mActiveViews = new View[childCount]; } mFirstActivePosition = firstActivePosition; final View[] activeViews = mActiveViews; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); // Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in // active views. // However, we will NOT place them into scrap views. activeViews[i] = child; } } } /** * Get the view corresponding to the specified position. The view will * be removed from mActiveViews if it is found. * * @param position * The position to look up in mActiveViews * @return The view if it is found, null otherwise */ View getActiveView(int position) { int index = position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >= 0 && index < activeViews.length) { final View match = activeViews[index]; activeViews[index] = null; return match; } return null; } /** * Put a view into the ScapViews list. These views are unordered. * * @param scrap * The view to add */ void addScrapView(View scrap) { AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { return; } // Don't put header or footer views or views that should be ignored // into the scrap heap int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { removeDetachedView(scrap, false); } return; } if (mViewTypeCount == 1) { dispatchFinishTemporaryDetach(scrap); mCurrentScrap.add(scrap); } else { dispatchFinishTemporaryDetach(scrap); mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } /** * @return A view from the ScrapViews collection. These are unordered. */ View getScrapView(int position) { ArrayList<View> scrapViews; if (mViewTypeCount == 1) { scrapViews = mCurrentScrap; int size = scrapViews.size(); if (size > 0) { return scrapViews.remove(size - 1); } else { return null; } } else { int whichScrap = mAdapter.getItemViewType(position); if (whichScrap >= 0 && whichScrap < mScrapViews.length) { scrapViews = mScrapViews[whichScrap]; int size = scrapViews.size(); if (size > 0) { return scrapViews.remove(size - 1); } } } return null; } public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); } // noinspection unchecked ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new ArrayList<View>(); } mViewTypeCount = viewTypeCount; mCurrentScrap = scrapViews[0]; mScrapViews = scrapViews; } } ~~~ 这里的RecycleBin代码并不全,我只是把最主要的几个方法提了出来。那么我们先来对这几个方法进行简单解读,这对后面分析ListView的工作原理将会有很大的帮助。 - **fillActiveViews()** 这个方法接收两个参数,第一个参数表示要存储的view的数量,第二个参数表示ListView中第一个可见元素的position值。RecycleBin当中使用mActiveViews这个数组来存储View,调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews数组当中。 - **getActiveView()** 这个方法和fillActiveViews()是对应的,用于从mActiveViews数组当中获取数据。该方法接收一个position参数,表示元素在ListView当中的位置,方法内部会自动将position值转换成mActiveViews数组对应的下标值。需要注意的是,mActiveViews当中所存储的View,一旦被获取了之后就会从mActiveViews当中移除,下次获取同样位置的View将会返回null,也就是说mActiveViews不能被重复利用。 - **addScrapView()** 用于将一个废弃的View进行缓存,该方法接收一个View参数,当有某个View确定要废弃掉的时候(比如滚动出了屏幕),就应该调用这个方法来对View进行缓存,RecycleBin当中使用mScrapViews和mCurrentScrap这两个List来存储废弃View。 - **getScrapView** 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,因此getScrapView()方法中的算法也非常简单,就是直接从mCurrentScrap当中获取尾部的一个scrap view进行返回。 - **setViewTypeCount()** 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。实际上,getViewTypeCount()方法通常情况下使用的并不是很多,所以我们只要知道RecycleBin当中有这样一个功能就行了。 了解了RecycleBin中的主要方法以及它们的用处之后,下面就可以开始来分析ListView的工作原理了,这里我将还是按照以前分析源码的方式来进行,即跟着主线执行流程来逐步阅读并点到即止,不然的话要是把ListView所有的代码都贴出来,那么本篇文章将会很长很长了。 ### 第一次Layout 不管怎么说,ListView即使再特殊最终还是继承自View的,因此它的执行流程还将会按照View的规则来执行,对于这方面不太熟悉的朋友可以参考我之前写的 [**Android视图绘制流程完全解析,带你一步步深入了解View(二)**](http://blog.csdn.net/guolin_blog/article/details/16330267) 。 View的执行流程无非就分为三步,onMeasure()用于测量View的大小,onLayout()用于确定View的布局,onDraw()用于将View绘制到界面上。而在ListView当中,onMeasure()并没有什么特殊的地方,因为它终归是一个View,占用的空间最多并且通常也就是整个屏幕。onDraw()在ListView当中也没有什么意义,因为ListView本身并不负责绘制,而是由ListView当中的子元素来进行绘制的。那么ListView大部分的神奇功能其实都是在onLayout()方法中进行的了,因此我们本篇文章也是主要分析的这个方法里的内容。 如果你到ListView源码中去找一找,你会发现ListView中是没有onLayout()这个方法的,这是因为这个方法是在ListView的父类AbsListView中实现的,代码如下所示: ~~~ /** * Subclasses should NOT override this method but {@link #layoutChildren()} * instead. */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mInLayout = true; if (changed) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } layoutChildren(); mInLayout = false; } ~~~ 可以看到,onLayout()方法中并没有做什么复杂的逻辑操作,主要就是一个判断,如果ListView的大小或者位置发生了变化,那么changed变量就会变成true,此时会要求所有的子布局都强制进行重绘。除此之外倒没有什么难理解的地方了,不过我们注意到,在第16行调用了layoutChildren()这个方法,从方法名上我们就可以猜出这个方法是用来进行子元素布局的,不过进入到这个方法当中你会发现这是个空方法,没有一行代码。这当然是可以理解的了,因为子元素的布局应该是由具体的实现类来负责完成的,而不是由父类完成。那么进入ListView的layoutChildren()方法,代码如下所示: ~~~ @Override protected void layoutChildren() { final boolean blockLayoutRequests = mBlockLayoutRequests; if (!blockLayoutRequests) { mBlockLayoutRequests = true; } else { return; } try { super.layoutChildren(); invalidate(); if (mAdapter == null) { resetList(); invokeOnItemScrollListener(); return; } int childrenTop = mListPadding.top; int childrenBottom = getBottom() - getTop() - mListPadding.bottom; int childCount = getChildCount(); int index = 0; int delta = 0; View sel; View oldSel = null; View oldFirst = null; View newSel = null; View focusLayoutRestoreView = null; // Remember stuff we will need down below switch (mLayoutMode) { case LAYOUT_SET_SELECTION: index = mNextSelectedPosition - mFirstPosition; if (index >= 0 && index < childCount) { newSel = getChildAt(index); } break; case LAYOUT_FORCE_TOP: case LAYOUT_FORCE_BOTTOM: case LAYOUT_SPECIFIC: case LAYOUT_SYNC: break; case LAYOUT_MOVE_SELECTION: default: // Remember the previously selected view index = mSelectedPosition - mFirstPosition; if (index >= 0 && index < childCount) { oldSel = getChildAt(index); } // Remember the previous first child oldFirst = getChildAt(0); if (mNextSelectedPosition >= 0) { delta = mNextSelectedPosition - mSelectedPosition; } // Caution: newSel might be null newSel = getChildAt(index + delta); } boolean dataChanged = mDataChanged; if (dataChanged) { handleDataChanged(); } // Handle the empty set by removing all views that are visible // and calling it a day if (mItemCount == 0) { resetList(); invokeOnItemScrollListener(); return; } else if (mItemCount != mAdapter.getCount()) { throw new IllegalStateException("The content of the adapter has changed but " + "ListView did not receive a notification. Make sure the content of " + "your adapter is not modified from a background thread, but only " + "from the UI thread. [in ListView(" + getId() + ", " + getClass() + ") with Adapter(" + mAdapter.getClass() + ")]"); } setSelectedPositionInt(mNextSelectedPosition); // Pull all children into the RecycleBin. // These views will be reused if possible final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; // reset the focus restoration View focusLayoutRestoreDirectChild = null; // Don't put header or footer views into the Recycler. Those are // already cached in mHeaderViews; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i)); if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(getChildAt(i), ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i); } } } else { recycleBin.fillActiveViews(childCount, firstPosition); } // take focus back to us temporarily to avoid the eventual // call to clear focus when removing the focused child below // from messing things up when ViewRoot assigns focus back // to someone else final View focusedChild = getFocusedChild(); if (focusedChild != null) { // TODO: in some cases focusedChild.getParent() == null // we can remember the focused view to restore after relayout if the // data hasn't changed, or if the focused position is a header or footer if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) { focusLayoutRestoreDirectChild = focusedChild; // remember the specific view that had focus focusLayoutRestoreView = findFocus(); if (focusLayoutRestoreView != null) { // tell it we are going to mess with it focusLayoutRestoreView.onStartTemporaryDetach(); } } requestFocus(); } // Clear out old views detachAllViewsFromParent(); switch (mLayoutMode) { case LAYOUT_SET_SELECTION: if (newSel != null) { sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom); } else { sel = fillFromMiddle(childrenTop, childrenBottom); } break; case LAYOUT_SYNC: sel = fillSpecific(mSyncPosition, mSpecificTop); break; case LAYOUT_FORCE_BOTTOM: sel = fillUp(mItemCount - 1, childrenBottom); adjustViewsUpOrDown(); break; case LAYOUT_FORCE_TOP: mFirstPosition = 0; sel = fillFromTop(childrenTop); adjustViewsUpOrDown(); break; case LAYOUT_SPECIFIC: sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop); break; case LAYOUT_MOVE_SELECTION: sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom); break; default: if (childCount == 0) { if (!mStackFromBottom) { final int position = lookForSelectablePosition(0, true); setSelectedPositionInt(position); sel = fillFromTop(childrenTop); } else { final int position = lookForSelectablePosition(mItemCount - 1, false); setSelectedPositionInt(position); sel = fillUp(mItemCount - 1, childrenBottom); } } else { if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop()); } else if (mFirstPosition < mItemCount) { sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop()); } else { sel = fillSpecific(0, childrenTop); } } break; } // Flush any cached views that did not get reused above recycleBin.scrapActiveViews(); if (sel != null) { // the current selected item should get focus if items // are focusable if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) { final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild && focusLayoutRestoreView.requestFocus()) || sel.requestFocus(); if (!focusWasTaken) { // selected item didn't take focus, fine, but still want // to make sure something else outside of the selected view // has focus final View focused = getFocusedChild(); if (focused != null) { focused.clearFocus(); } positionSelector(sel); } else { sel.setSelected(false); mSelectorRect.setEmpty(); } } else { positionSelector(sel); } mSelectedTop = sel.getTop(); } else { if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) { View child = getChildAt(mMotionPosition - mFirstPosition); if (child != null) positionSelector(child); } else { mSelectedTop = 0; mSelectorRect.setEmpty(); } // even if there is not selected position, we may need to restore // focus (i.e. something focusable in touch mode) if (hasFocus() && focusLayoutRestoreView != null) { focusLayoutRestoreView.requestFocus(); } } // tell focus view we are done mucking with it, if it is still in // our view hierarchy. if (focusLayoutRestoreView != null && focusLayoutRestoreView.getWindowToken() != null) { focusLayoutRestoreView.onFinishTemporaryDetach(); } mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; mNeedSync = false; setNextSelectedPositionInt(mSelectedPosition); updateScrollIndicators(); if (mItemCount > 0) { checkSelectionChanged(); } invokeOnItemScrollListener(); } finally { if (!blockLayoutRequests) { mBlockLayoutRequests = false; } } } ~~~ 这段代码比较长,我们挑重点的看。首先可以确定的是,ListView当中目前还没有任何子View,数据都还是由Adapter管理的,并没有展示到界面上,因此第19行getChildCount()方法得到的值肯定是0。接着在第81行会根据dataChanged这个布尔型的值来判断执行逻辑,dataChanged只有在数据源发生改变的情况下才会变成true,其它情况都是false,因此这里会进入到第90行的执行逻辑,调用RecycleBin的fillActiveViews()方法。按理来说,调用fillActiveViews()方法是为了将ListView的子View进行缓存的,可是目前ListView中还没有任何的子View,因此这一行暂时还起不了任何作用。 接下来在第114行会根据mLayoutMode的值来决定布局模式,默认情况下都是普通模式LAYOUT_NORMAL,因此会进入到第140行的default语句当中。而下面又会紧接着进行两次if判断,childCount目前是等于0的,并且默认的布局顺序是从上往下,因此会进入到第145行的fillFromTop()方法,我们跟进去瞧一瞧: ~~~ /** * Fills the list from top to bottom, starting with mFirstPosition * * @param nextTop The location where the top of the first item should be * drawn * * @return The view that is currently selected */ private View fillFromTop(int nextTop) { mFirstPosition = Math.min(mFirstPosition, mSelectedPosition); mFirstPosition = Math.min(mFirstPosition, mItemCount - 1); if (mFirstPosition < 0) { mFirstPosition = 0; } return fillDown(mFirstPosition, nextTop); } ~~~ 从这个方法的注释中可以看出,它所负责的主要任务就是从mFirstPosition开始,自顶至底去填充ListView。而这个方法本身并没有什么逻辑,就是判断了一下mFirstPosition值的合法性,然后调用fillDown()方法,那么我们就有理由可以猜测,填充ListView的操作是在fillDown()方法中完成的。进入fillDown()方法,代码如下所示: ~~~ /** * Fills the list from pos down to the end of the list view. * * @param pos The first position to put in the list * * @param nextTop The location where the top of the item associated with pos * should be drawn * * @return The view that is currently selected, if it happens to be in the * range that we draw. */ private View fillDown(int pos, int nextTop) { View selectedView = null; int end = (getBottom() - getTop()) - mListPadding.bottom; while (nextTop < end && pos < mItemCount) { // is this the selected item? boolean selected = pos == mSelectedPosition; View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } pos++; } return selectedView; } ~~~ 可以看到,这里使用了一个while循环来执行重复逻辑,一开始nextTop的值是第一个子元素顶部距离整个ListView顶部的像素值,pos则是刚刚传入的mFirstPosition的值,而end是ListView底部减去顶部所得的像素值,mItemCount则是Adapter中的元素数量。因此一开始的情况下nextTop必定是小于end值的,并且pos也是小于mItemCount值的。那么每执行一次while循环,pos的值都会加1,并且nextTop也会增加,当nextTop大于等于end时,也就是子元素已经超出当前屏幕了,或者pos大于等于mItemCount时,也就是所有Adapter中的元素都被遍历结束了,就会跳出while循环。 那么while循环当中又做了什么事情呢?值得让人留意的就是第18行调用的makeAndAddView()方法,进入到这个方法当中,代码如下所示: ~~~ /** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the * recycle bin. * * @param position Logical position in the list * @param y Top or bottom edge of the view to add * @param flow If flow is true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */ private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an exsiting view for this position child = mRecycler.getActiveView(position); if (child != null) { // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; } ~~~ 这里在第19行尝试从RecycleBin当中快速获取一个active view,不过很遗憾的是目前RecycleBin当中还没有缓存任何的View,所以这里得到的值肯定是null。那么取得了null之后就会继续向下运行,到第28行会调用obtainView()方法来再次尝试获取一个View,这次的obtainView()方法是可以保证一定返回一个View的,于是下面立刻将获取到的View传入到了setupChild()方法当中。那么obtainView()内部到底是怎么工作的呢?我们先进入到这个方法里面看一下: ~~~ /** * Get a view and have it show the data associated with the specified * position. This is called when we have already discovered that the view is * not available for reuse in the recycle bin. The only choices left are * converting an old view or making a new one. * * @param position * The position to display * @param isScrap * Array of at least 1 boolean, the first entry will become true * if the returned view was taken from the scrap heap, false if * otherwise. * * @return A view displaying the data associated with the specified position */ View obtainView(int position, boolean[] isScrap) { isScrap[0] = false; View scrapView; scrapView = mRecycler.getScrapView(position); View child; if (scrapView != null) { child = mAdapter.getView(position, scrapView, this); if (child != scrapView) { mRecycler.addScrapView(scrapView); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } } else { isScrap[0] = true; dispatchFinishTemporaryDetach(child); } } else { child = mAdapter.getView(position, null, this); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } } return child; } ~~~ obtainView()方法中的代码并不多,但却包含了非常非常重要的逻辑,不夸张的说,整个ListView中最重要的内容可能就在这个方法里了。那么我们还是按照执行流程来看,在第19行代码中调用了RecycleBin的getScrapView()方法来尝试获取一个废弃缓存中的View,同样的道理,这里肯定是获取不到的,getScrapView()方法会返回一个null。这时该怎么办呢?没有关系,代码会执行到第33行,调用mAdapter的getView()方法来去获取一个View。那么mAdapter是什么呢?当然就是当前ListView关联的适配器了。而getView()方法又是什么呢?还用说吗,这个就是我们平时使用ListView时最最经常重写的一个方法了,这里getView()方法中传入了三个参数,分别是position,null和this。 那么我们平时写ListView的Adapter时,getView()方法通常会怎么写呢?这里我举个简单的例子: ~~~ @Override public View getView(int position, View convertView, ViewGroup parent) { Fruit fruit = getItem(position); View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(resourceId, null); } else { view = convertView; } ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image); TextView fruitName = (TextView) view.findViewById(R.id.fruit_name); fruitImage.setImageResource(fruit.getImageId()); fruitName.setText(fruit.getName()); return view; } ~~~ getView()方法接受的三个参数,第一个参数position代表当前子元素的的位置,我们可以通过具体的位置来获取与其相关的数据。第二个参数convertView,刚才传入的是null,说明没有convertView可以利用,因此我们会调用LayoutInflater的inflate()方法来去加载一个布局。接下来会对这个view进行一些属性和值的设定,最后将view返回。 那么这个View也会作为obtainView()的结果进行返回,并最终传入到setupChild()方法当中。其实也就是说,第一次layout过程当中,所有的子View都是调用LayoutInflater的inflate()方法加载出来的,这样就会相对比较耗时,但是不用担心,后面就不会再有这种情况了,那么我们继续往下看: ~~~ /** * Add a view as a child and make sure it is measured (if necessary) and * positioned properly. * * @param child The view to add * @param position The position of this child * @param y The y position relative to which this view will be positioned * @param flowDown If true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @param recycled Has this view been pulled from the recycle bin? If so it * does not need to be remeasured. */ private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { final boolean isSelected = selected && shouldShowSelector(); final boolean updateChildSelected = isSelected != child.isSelected(); final int mode = mTouchMode; final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL && mMotionPosition == position; final boolean updateChildPressed = isPressed != child.isPressed(); final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested(); // Respect layout params that are already in the view. Otherwise make some up... // noinspection unchecked AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams(); if (p == null) { p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0); } p.viewType = mAdapter.getItemViewType(position); if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) { attachViewToParent(child, flowDown ? -1 : 0, p); } else { p.forceAdd = false; if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { p.recycledHeaderFooter = true; } addViewInLayout(child, flowDown ? -1 : 0, p, true); } if (updateChildSelected) { child.setSelected(isSelected); } if (updateChildPressed) { child.setPressed(isPressed); } if (needToMeasure) { int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, mListPadding.left + mListPadding.right, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } else { cleanupLayoutState(child); } final int w = child.getMeasuredWidth(); final int h = child.getMeasuredHeight(); final int childTop = flowDown ? y : y - h; if (needToMeasure) { final int childRight = childrenLeft + w; final int childBottom = childTop + h; child.layout(childrenLeft, childTop, childRight, childBottom); } else { child.offsetLeftAndRight(childrenLeft - child.getLeft()); child.offsetTopAndBottom(childTop - child.getTop()); } if (mCachingStarted && !child.isDrawingCacheEnabled()) { child.setDrawingCacheEnabled(true); } } ~~~ setupChild()方法当中的代码虽然比较多,但是我们只看核心代码的话就非常简单了,刚才调用obtainView()方法获取到的子元素View,这里在第40行调用了addViewInLayout()方法将它添加到了ListView当中。那么根据fillDown()方法中的while循环,会让子元素View将整个ListView控件填满然后就跳出,也就是说即使我们的Adapter中有一千条数据,ListView也只会加载第一屏的数据,剩下的数据反正目前在屏幕上也看不到,所以不会去做多余的加载工作,这样就可以保证ListView中的内容能够迅速展示到屏幕上。 那么到此为止,第一次Layout过程结束。 ### 第二次Layout 虽然我在源码中并没有找出具体的原因,但如果你自己做一下实验的话就会发现,即使是一个再简单的View,在展示到界面上之前都会经历至少两次onMeasure()和两次onLayout()的过程。其实这只是一个很小的细节,平时对我们影响并不大,因为不管是onMeasure()或者onLayout()几次,反正都是执行的相同的逻辑,我们并不需要进行过多关心。但是在ListView中情况就不一样了,因为这就意味着layoutChildren()过程会执行两次,而这个过程当中涉及到向ListView中添加子元素,如果相同的逻辑执行两遍的话,那么ListView中就会存在一份重复的数据了。因此ListView在layoutChildren()过程当中做了第二次Layout的逻辑处理,非常巧妙地解决了这个问题,下面我们就来分析一下第二次Layout的过程。 其实第二次Layout和第一次Layout的基本流程是差不多的,那么我们还是从layoutChildren()方法开始看起: ~~~ @Override protected void layoutChildren() { final boolean blockLayoutRequests = mBlockLayoutRequests; if (!blockLayoutRequests) { mBlockLayoutRequests = true; } else { return; } try { super.layoutChildren(); invalidate(); if (mAdapter == null) { resetList(); invokeOnItemScrollListener(); return; } int childrenTop = mListPadding.top; int childrenBottom = getBottom() - getTop() - mListPadding.bottom; int childCount = getChildCount(); int index = 0; int delta = 0; View sel; View oldSel = null; View oldFirst = null; View newSel = null; View focusLayoutRestoreView = null; // Remember stuff we will need down below switch (mLayoutMode) { case LAYOUT_SET_SELECTION: index = mNextSelectedPosition - mFirstPosition; if (index >= 0 && index < childCount) { newSel = getChildAt(index); } break; case LAYOUT_FORCE_TOP: case LAYOUT_FORCE_BOTTOM: case LAYOUT_SPECIFIC: case LAYOUT_SYNC: break; case LAYOUT_MOVE_SELECTION: default: // Remember the previously selected view index = mSelectedPosition - mFirstPosition; if (index >= 0 && index < childCount) { oldSel = getChildAt(index); } // Remember the previous first child oldFirst = getChildAt(0); if (mNextSelectedPosition >= 0) { delta = mNextSelectedPosition - mSelectedPosition; } // Caution: newSel might be null newSel = getChildAt(index + delta); } boolean dataChanged = mDataChanged; if (dataChanged) { handleDataChanged(); } // Handle the empty set by removing all views that are visible // and calling it a day if (mItemCount == 0) { resetList(); invokeOnItemScrollListener(); return; } else if (mItemCount != mAdapter.getCount()) { throw new IllegalStateException("The content of the adapter has changed but " + "ListView did not receive a notification. Make sure the content of " + "your adapter is not modified from a background thread, but only " + "from the UI thread. [in ListView(" + getId() + ", " + getClass() + ") with Adapter(" + mAdapter.getClass() + ")]"); } setSelectedPositionInt(mNextSelectedPosition); // Pull all children into the RecycleBin. // These views will be reused if possible final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; // reset the focus restoration View focusLayoutRestoreDirectChild = null; // Don't put header or footer views into the Recycler. Those are // already cached in mHeaderViews; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i)); if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(getChildAt(i), ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i); } } } else { recycleBin.fillActiveViews(childCount, firstPosition); } // take focus back to us temporarily to avoid the eventual // call to clear focus when removing the focused child below // from messing things up when ViewRoot assigns focus back // to someone else final View focusedChild = getFocusedChild(); if (focusedChild != null) { // TODO: in some cases focusedChild.getParent() == null // we can remember the focused view to restore after relayout if the // data hasn't changed, or if the focused position is a header or footer if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) { focusLayoutRestoreDirectChild = focusedChild; // remember the specific view that had focus focusLayoutRestoreView = findFocus(); if (focusLayoutRestoreView != null) { // tell it we are going to mess with it focusLayoutRestoreView.onStartTemporaryDetach(); } } requestFocus(); } // Clear out old views detachAllViewsFromParent(); switch (mLayoutMode) { case LAYOUT_SET_SELECTION: if (newSel != null) { sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom); } else { sel = fillFromMiddle(childrenTop, childrenBottom); } break; case LAYOUT_SYNC: sel = fillSpecific(mSyncPosition, mSpecificTop); break; case LAYOUT_FORCE_BOTTOM: sel = fillUp(mItemCount - 1, childrenBottom); adjustViewsUpOrDown(); break; case LAYOUT_FORCE_TOP: mFirstPosition = 0; sel = fillFromTop(childrenTop); adjustViewsUpOrDown(); break; case LAYOUT_SPECIFIC: sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop); break; case LAYOUT_MOVE_SELECTION: sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom); break; default: if (childCount == 0) { if (!mStackFromBottom) { final int position = lookForSelectablePosition(0, true); setSelectedPositionInt(position); sel = fillFromTop(childrenTop); } else { final int position = lookForSelectablePosition(mItemCount - 1, false); setSelectedPositionInt(position); sel = fillUp(mItemCount - 1, childrenBottom); } } else { if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop()); } else if (mFirstPosition < mItemCount) { sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop()); } else { sel = fillSpecific(0, childrenTop); } } break; } // Flush any cached views that did not get reused above recycleBin.scrapActiveViews(); if (sel != null) { // the current selected item should get focus if items // are focusable if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) { final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild && focusLayoutRestoreView.requestFocus()) || sel.requestFocus(); if (!focusWasTaken) { // selected item didn't take focus, fine, but still want // to make sure something else outside of the selected view // has focus final View focused = getFocusedChild(); if (focused != null) { focused.clearFocus(); } positionSelector(sel); } else { sel.setSelected(false); mSelectorRect.setEmpty(); } } else { positionSelector(sel); } mSelectedTop = sel.getTop(); } else { if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) { View child = getChildAt(mMotionPosition - mFirstPosition); if (child != null) positionSelector(child); } else { mSelectedTop = 0; mSelectorRect.setEmpty(); } // even if there is not selected position, we may need to restore // focus (i.e. something focusable in touch mode) if (hasFocus() && focusLayoutRestoreView != null) { focusLayoutRestoreView.requestFocus(); } } // tell focus view we are done mucking with it, if it is still in // our view hierarchy. if (focusLayoutRestoreView != null && focusLayoutRestoreView.getWindowToken() != null) { focusLayoutRestoreView.onFinishTemporaryDetach(); } mLayoutMode = LAYOUT_NORMAL; mDataChanged = false; mNeedSync = false; setNextSelectedPositionInt(mSelectedPosition); updateScrollIndicators(); if (mItemCount > 0) { checkSelectionChanged(); } invokeOnItemScrollListener(); } finally { if (!blockLayoutRequests) { mBlockLayoutRequests = false; } } } ~~~ 同样还是在第19行,调用getChildCount()方法来获取子View的数量,只不过现在得到的值不会再是0了,而是ListView中一屏可以显示的子View数量,因为我们刚刚在第一次Layout过程当中向ListView添加了这么多的子View。下面在第90行调用了RecycleBin的fillActiveViews()方法,这次效果可就不一样了,因为目前ListView中已经有子View了,这样所有的子View都会被缓存到RecycleBin的mActiveViews数组当中,后面将会用到它们。 接下来将会是非常非常重要的一个操作,在第113行调用了detachAllViewsFromParent()方法。这个方法会将所有ListView当中的子View全部清除掉,从而保证第二次Layout过程不会产生一份重复的数据。那有的朋友可能会问了,这样把已经加载好的View又清除掉,待会还要再重新加载一遍,这不是严重影响效率吗?不用担心,还记得我们刚刚调用了RecycleBin的fillActiveViews()方法来缓存子View吗,待会儿将会直接使用这些缓存好的View来进行加载,而并不会重新执行一遍inflate过程,因此效率方面并不会有什么明显的影响。 那么我们接着看,在第141行的判断逻辑当中,由于不再等于0了,因此会进入到else语句当中。而else语句中又有三个逻辑判断,第一个逻辑判断不成立,因为默认情况下我们没有选中任何子元素,mSelectedPosition应该等于-1。第二个逻辑判断通常是成立的,因为mFirstPosition的值一开始是等于0的,只要adapter中的数据大于0条件就成立。那么进入到fillSpecific()方法当中,代码如下所示: ~~~ /** * Put a specific item at a specific location on the screen and then build * up and down from there. * * @param position The reference view to use as the starting point * @param top Pixel offset from the top of this view to the top of the * reference view. * * @return The selected view, or null if the selected view is outside the * visible area. */ private View fillSpecific(int position, int top) { boolean tempIsSelected = position == mSelectedPosition; View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected); // Possibly changed again in fillUp if we add rows above this one. mFirstPosition = position; View above; View below; final int dividerHeight = mDividerHeight; if (!mStackFromBottom) { above = fillUp(position - 1, temp.getTop() - dividerHeight); // This will correct for the top of the first view not touching the top of the list adjustViewsUpOrDown(); below = fillDown(position + 1, temp.getBottom() + dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooHigh(childCount); } } else { below = fillDown(position + 1, temp.getBottom() + dividerHeight); // This will correct for the bottom of the last view not touching the bottom of the list adjustViewsUpOrDown(); above = fillUp(position - 1, temp.getTop() - dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooLow(childCount); } } if (tempIsSelected) { return temp; } else if (above != null) { return above; } else { return below; } } ~~~ fillSpecific()这算是一个新方法了,不过其实它和fillUp()、fillDown()方法功能也是差不多的,主要的区别在于,fillSpecific()方法会优先将指定位置的子View先加载到屏幕上,然后再加载该子View往上以及往下的其它子View。那么由于这里我们传入的position就是第一个子View的位置,于是fillSpecific()方法的作用就基本上和fillDown()方法是差不多的了,这里我们就不去关注太多它的细节,而是将精力放在makeAndAddView()方法上面。再次回到makeAndAddView()方法,代码如下所示: ~~~ /** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the * recycle bin. * * @param position Logical position in the list * @param y Top or bottom edge of the view to add * @param flow If flow is true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */ private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an exsiting view for this position child = mRecycler.getActiveView(position); if (child != null) { // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; } ~~~ 仍然还是在第19行尝试从RecycleBin当中获取Active View,然而这次就一定可以获取到了,因为前面我们调用了RecycleBin的fillActiveViews()方法来缓存子View。那么既然如此,就不会再进入到第28行的obtainView()方法,而是会直接进入setupChild()方法当中,这样也省去了很多时间,因为如果在obtainView()方法中又要去infalte布局的话,那么ListView的初始加载效率就大大降低了。 注意在第23行,setupChild()方法的最后一个参数传入的是true,这个参数表明当前的View是之前被回收过的,那么我们再次回到setupChild()方法当中: ~~~ /** * Add a view as a child and make sure it is measured (if necessary) and * positioned properly. * * @param child The view to add * @param position The position of this child * @param y The y position relative to which this view will be positioned * @param flowDown If true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @param recycled Has this view been pulled from the recycle bin? If so it * does not need to be remeasured. */ private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { final boolean isSelected = selected && shouldShowSelector(); final boolean updateChildSelected = isSelected != child.isSelected(); final int mode = mTouchMode; final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL && mMotionPosition == position; final boolean updateChildPressed = isPressed != child.isPressed(); final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested(); // Respect layout params that are already in the view. Otherwise make some up... // noinspection unchecked AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams(); if (p == null) { p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0); } p.viewType = mAdapter.getItemViewType(position); if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) { attachViewToParent(child, flowDown ? -1 : 0, p); } else { p.forceAdd = false; if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { p.recycledHeaderFooter = true; } addViewInLayout(child, flowDown ? -1 : 0, p, true); } if (updateChildSelected) { child.setSelected(isSelected); } if (updateChildPressed) { child.setPressed(isPressed); } if (needToMeasure) { int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, mListPadding.left + mListPadding.right, p.width); int lpHeight = p.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } else { cleanupLayoutState(child); } final int w = child.getMeasuredWidth(); final int h = child.getMeasuredHeight(); final int childTop = flowDown ? y : y - h; if (needToMeasure) { final int childRight = childrenLeft + w; final int childBottom = childTop + h; child.layout(childrenLeft, childTop, childRight, childBottom); } else { child.offsetLeftAndRight(childrenLeft - child.getLeft()); child.offsetTopAndBottom(childTop - child.getTop()); } if (mCachingStarted && !child.isDrawingCacheEnabled()) { child.setDrawingCacheEnabled(true); } } ~~~ 可以看到,setupChild()方法的最后一个参数是recycled,然后在第32行会对这个变量进行判断,由于recycled现在是true,所以会执行attachViewToParent()方法,而第一次Layout过程则是执行的else语句中的addViewInLayout()方法。这两个方法最大的区别在于,如果我们需要向ViewGroup中添加一个新的子View,应该调用addViewInLayout()方法,而如果是想要将一个之前detach的View重新attach到ViewGroup上,就应该调用attachViewToParent()方法。那么由于前面在layoutChildren()方法当中调用了detachAllViewsFromParent()方法,这样ListView中所有的子View都是处于detach状态的,所以这里attachViewToParent()方法是正确的选择。 经历了这样一个detach又attach的过程,ListView中所有的子View又都可以正常显示出来了,那么第二次Layout过程结束。 ### 滑动加载更多数据 经历了两次Layout过程,虽说我们已经可以在ListView中看到内容了,然而关于ListView最神奇的部分我们却还没有接触到,因为目前ListView中只是加载并显示了第一屏的数据而已。比如说我们的Adapter当中有1000条数据,但是第一屏只显示了10条,ListView中也只有10个子View而已,那么剩下的990是怎样工作并显示到界面上的呢?这就要看一下ListView滑动部分的源码了,因为我们是通过手指滑动来显示更多数据的。 由于滑动部分的机制是属于通用型的,即ListView和GridView都会使用同样的机制,因此这部分代码就肯定是写在AbsListView当中的了。那么监听触控事件是在onTouchEvent()方法当中进行的,我们就来看一下AbsListView中的这个方法: ~~~ @Override public boolean onTouchEvent(MotionEvent ev) { if (!isEnabled()) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return isClickable() || isLongClickable(); } final int action = ev.getAction(); View v; int deltaY; if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { mActivePointerId = ev.getPointerId(0); final int x = (int) ev.getX(); final int y = (int) ev.getY(); int motionPosition = pointToPosition(x, y); if (!mDataChanged) { if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0) && (getAdapter().isEnabled(motionPosition))) { // User clicked on an actual view (and was not stopping a // fling). It might be a // click or a scroll. Assume it is a click until proven // otherwise mTouchMode = TOUCH_MODE_DOWN; // FIXME Debounce if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { if (ev.getEdgeFlags() != 0 && motionPosition < 0) { // If we couldn't find a view to click on, but the down // event was touching // the edge, we will bail out and try again. This allows // the edge correcting // code in ViewRoot to try to find a nearby view to // select return false; } if (mTouchMode == TOUCH_MODE_FLING) { // Stopped a fling. It is a scroll. createScrollingCache(); mTouchMode = TOUCH_MODE_SCROLL; mMotionCorrection = 0; motionPosition = findMotionRow(y); reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } } } if (motionPosition >= 0) { // Remember where the motion event started v = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = v.getTop(); } mMotionX = x; mMotionY = y; mMotionPosition = motionPosition; mLastY = Integer.MIN_VALUE; break; } case MotionEvent.ACTION_MOVE: { final int pointerIndex = ev.findPointerIndex(mActivePointerId); final int y = (int) ev.getY(pointerIndex); deltaY = y - mMotionY; switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: // Check if we have moved far enough that it looks more like a // scroll than a tap startScrollIfNeeded(deltaY); break; case TOUCH_MODE_SCROLL: if (PROFILE_SCROLLING) { if (!mScrollProfilingStarted) { Debug.startMethodTracing("AbsListViewScroll"); mScrollProfilingStarted = true; } } if (y != mLastY) { deltaY -= mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; // No need to do all this work if we're not going to move // anyway boolean atEdge = false; if (incrementalDeltaY != 0) { atEdge = trackMotionScroll(deltaY, incrementalDeltaY); } // Check to see if we have bumped into the scroll limit if (atEdge && getChildCount() > 0) { // Treat this like we're starting a new scroll from the // current // position. This will let the user start scrolling back // into // content immediately rather than needing to scroll // back to the // point where they hit the limit first. int motionPosition = findMotionRow(y); if (motionPosition >= 0) { final View motionView = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = motionView.getTop(); } mMotionY = y; mMotionPosition = motionPosition; invalidate(); } mLastY = y; } break; } break; } case MotionEvent.ACTION_UP: { switch (mTouchMode) { case TOUCH_MODE_DOWN: case TOUCH_MODE_TAP: case TOUCH_MODE_DONE_WAITING: final int motionPosition = mMotionPosition; final View child = getChildAt(motionPosition - mFirstPosition); if (child != null && !child.hasFocusable()) { if (mTouchMode != TOUCH_MODE_DOWN) { child.setPressed(false); } if (mPerformClick == null) { mPerformClick = new PerformClick(); } final AbsListView.PerformClick performClick = mPerformClick; performClick.mChild = child; performClick.mClickMotionPosition = motionPosition; performClick.rememberWindowAttachCount(); mResurrectToPosition = motionPosition; if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { final Handler handler = getHandler(); if (handler != null) { handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap : mPendingCheckForLongPress); } mLayoutMode = LAYOUT_NORMAL; if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { mTouchMode = TOUCH_MODE_TAP; setSelectedPositionInt(mMotionPosition); layoutChildren(); child.setPressed(true); positionSelector(child); setPressed(true); if (mSelector != null) { Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { ((TransitionDrawable) d).resetTransition(); } } postDelayed(new Runnable() { public void run() { child.setPressed(false); setPressed(false); if (!mDataChanged) { post(performClick); } mTouchMode = TOUCH_MODE_REST; } }, ViewConfiguration.getPressedStateDuration()); } else { mTouchMode = TOUCH_MODE_REST; } return true; } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { post(performClick); } } mTouchMode = TOUCH_MODE_REST; break; case TOUCH_MODE_SCROLL: final int childCount = getChildCount(); if (childCount > 0) { if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top && mFirstPosition + childCount < mItemCount && getChildAt(childCount - 1).getBottom() <= getHeight() - mListPadding.bottom) { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } else { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); final int initialVelocity = (int) velocityTracker .getYVelocity(mActivePointerId); if (Math.abs(initialVelocity) > mMinimumVelocity) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); mFlingRunnable.start(-initialVelocity); } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } } } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } break; } setPressed(false); // Need to redraw since we probably aren't drawing the selector // anymore invalidate(); final Handler handler = getHandler(); if (handler != null) { handler.removeCallbacks(mPendingCheckForLongPress); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mActivePointerId = INVALID_POINTER; if (PROFILE_SCROLLING) { if (mScrollProfilingStarted) { Debug.stopMethodTracing(); mScrollProfilingStarted = false; } } break; } case MotionEvent.ACTION_CANCEL: { mTouchMode = TOUCH_MODE_REST; setPressed(false); View motionView = this.getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } clearScrollingCache(); final Handler handler = getHandler(); if (handler != null) { handler.removeCallbacks(mPendingCheckForLongPress); } if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } mActivePointerId = INVALID_POINTER; break; } case MotionEvent.ACTION_POINTER_UP: { onSecondaryPointerUp(ev); final int x = mMotionX; final int y = mMotionY; final int motionPosition = pointToPosition(x, y); if (motionPosition >= 0) { // Remember where the motion event started v = getChildAt(motionPosition - mFirstPosition); mMotionViewOriginalTop = v.getTop(); mMotionPosition = motionPosition; } mLastY = y; break; } } return true; } ~~~ 这个方法中的代码就非常多了,因为它所处理的逻辑也非常多,要监听各种各样的触屏事件。但是我们目前所关心的就只有手指在屏幕上滑动这一个事件而已,对应的是ACTION_MOVE这个动作,那么我们就只看这部分代码就可以了。 可以看到,ACTION_MOVE这个case里面又嵌套了一个switch语句,是根据当前的TouchMode来选择的。那这里我可以直接告诉大家,当手指在屏幕上滑动时,TouchMode是等于TOUCH_MODE_SCROLL这个值的,至于为什么那又要牵扯到另外的好几个方法,这里限于篇幅原因就不再展开讲解了,喜欢寻根究底的朋友们可以自己去源码里找一找原因。 这样的话,代码就应该会走到第78行的这个case里面去了,在这个case当中并没有什么太多需要注意的东西,唯一一点非常重要的就是第92行调用的trackMotionScroll()方法,相当于我们手指只要在屏幕上稍微有一点点移动,这个方法就会被调用,而如果是正常在屏幕上滑动的话,那么这个方法就会被调用很多次。那么我们进入到这个方法中瞧一瞧,代码如下所示: ~~~ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { final int childCount = getChildCount(); if (childCount == 0) { return true; } final int firstTop = getChildAt(0).getTop(); final int lastBottom = getChildAt(childCount - 1).getBottom(); final Rect listPadding = mListPadding; final int spaceAbove = listPadding.top - firstTop; final int end = getHeight() - listPadding.bottom; final int spaceBelow = lastBottom - end; final int height = getHeight() - getPaddingBottom() - getPaddingTop(); if (deltaY < 0) { deltaY = Math.max(-(height - 1), deltaY); } else { deltaY = Math.min(height - 1, deltaY); } if (incrementalDeltaY < 0) { incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY); } else { incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); } final int firstPosition = mFirstPosition; if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) { // Don't need to move views down if the top of the first position // is already visible return true; } if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) { // Don't need to move views up if the bottom of the last position // is already visible return true; } final boolean down = incrementalDeltaY < 0; final boolean inTouchMode = isInTouchMode(); if (inTouchMode) { hideSelector(); } final int headerViewsCount = getHeaderViewsCount(); final int footerViewsStart = mItemCount - getFooterViewsCount(); int start = 0; int count = 0; if (down) { final int top = listPadding.top - incrementalDeltaY; for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else { count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { mRecycler.addScrapView(child); } } } } else { final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY; for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { mRecycler.addScrapView(child); } } } } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { detachViewsFromParent(start, count); } offsetChildrenTopAndBottom(incrementalDeltaY); if (down) { mFirstPosition += count; } invalidate(); final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(down); } if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { final int childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(getChildAt(childIndex)); } } mBlockLayoutRequests = false; invokeOnItemScrollListener(); awakenScrollBars(); return false; } ~~~ 这个方法接收两个参数,deltaY表示从手指按下时的位置到当前手指位置的距离,incrementalDeltaY则表示据上次触发event事件手指在Y方向上位置的改变量,那么其实我们就可以通过incrementalDeltaY的正负值情况来判断用户是向上还是向下滑动的了。如第34行代码所示,如果incrementalDeltaY小于0,说明是向下滑动,否则就是向上滑动。 下面将会进行一个边界值检测的过程,可以看到,从第43行开始,当ListView向下滑动的时候,就会进入一个for循环当中,从上往下依次获取子View,第47行当中,如果该子View的bottom值已经小于top值了,就说明这个子View已经移出屏幕了,所以会调用RecycleBin的addScrapView()方法将这个View加入到废弃缓存当中,并将count计数器加1,计数器用于记录有多少个子View被移出了屏幕。那么如果是ListView向上滑动的话,其实过程是基本相同的,只不过变成了从下往上依次获取子View,然后判断该子View的top值是不是大于bottom值了,如果大于的话说明子View已经移出了屏幕,同样把它加入到废弃缓存中,并将计数器加1。 接下来在第76行,会根据当前计数器的值来进行一个detach操作,它的作用就是把所有移出屏幕的子View全部detach掉,在ListView的概念当中,所有看不到的View就没有必要为它进行保存,因为屏幕外还有成百上千条数据等着显示呢,一个好的回收策略才能保证ListView的高性能和高效率。紧接着在第78行调用了offsetChildrenTopAndBottom()方法,并将incrementalDeltaY作为参数传入,这个方法的作用是让ListView中所有的子View都按照传入的参数值进行相应的偏移,这样就实现了随着手指的拖动,ListView的内容也会随着滚动的效果。 然后在第84行会进行判断,如果ListView中最后一个View的底部已经移入了屏幕,或者ListView中第一个View的顶部移入了屏幕,就会调用fillGap()方法,那么因此我们就可以猜出fillGap()方法是用来加载屏幕外数据的,进入到这个方法中瞧一瞧,如下所示: ~~~ /** * Fills the gap left open by a touch-scroll. During a touch scroll, * children that remain on screen are shifted and the other ones are * discarded. The role of this method is to fill the gap thus created by * performing a partial layout in the empty space. * * @param down * true if the scroll is going down, false if it is going up */ abstract void fillGap(boolean down); ~~~ down参数用于表示ListView是向下滑动还是向上滑动的,可以看到,如果是向下滑动的话就会调用fillDown()方法,而如果是向上滑动的话就会调用fillUp()方法。那么这两个方法我们都已经非常熟悉了,内部都是通过一个循环来去对ListView进行填充,所以这两个方法我们就不看了,但是填充ListView会通过调用makeAndAddView()方法来完成,又是makeAndAddView()方法,但这次的逻辑再次不同了,所以我们还是回到这个方法瞧一瞧: ~~~ void fillGap(boolean down) { final int count = getChildCount(); if (down) { final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight : getListPaddingTop(); fillDown(mFirstPosition + count, startOffset); correctTooHigh(getChildCount()); } else { final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight : getHeight() - getListPaddingBottom(); fillUp(mFirstPosition - 1, startOffset); correctTooLow(getChildCount()); } } ~~~ 不管怎么说,这里首先仍然是会尝试调用RecycleBin的getActiveView()方法来获取子布局,只不过肯定是获取不到的了,因为在第二次Layout过程中我们已经从mActiveViews中获取过了数据,而根据RecycleBin的机制,mActiveViews是不能够重复利用的,因此这里返回的值肯定是null。 既然getActiveView()方法返回的值是null,那么就还是会走到第28行的obtainView()方法当中,代码如下所示: ~~~ /** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the * recycle bin. * * @param position Logical position in the list * @param y Top or bottom edge of the view to add * @param flow If flow is true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */ private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an exsiting view for this position child = mRecycler.getActiveView(position); if (child != null) { // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; } ~~~ 这里在第19行会调用RecyleBin的getScrapView()方法来尝试从废弃缓存中获取一个View,那么废弃缓存有没有View呢?当然有,因为刚才在trackMotionScroll()方法中我们就已经看到了,一旦有任何子View被移出了屏幕,就会将它加入到废弃缓存中,而从obtainView()方法中的逻辑来看,一旦有新的数据需要显示到屏幕上,就会尝试从废弃缓存中获取View。所以它们之间就形成了一个生产者和消费者的模式,那么ListView神奇的地方也就在这里体现出来了,不管你有任意多条数据需要显示,ListView中的子View其实来来回回就那么几个,移出屏幕的子View会很快被移入屏幕的数据重新利用起来,因而不管我们加载多少数据都不会出现OOM的情况,甚至内存都不会有所增加。 那么另外还有一点是需要大家留意的,这里获取到了一个scrapView,然后我们在第22行将它作为第二个参数传入到了Adapter的getView()方法当中。那么第二个参数是什么意思呢?我们再次看一下一个简单的getView()方法示例: ~~~ /** * Get a view and have it show the data associated with the specified * position. This is called when we have already discovered that the view is * not available for reuse in the recycle bin. The only choices left are * converting an old view or making a new one. * * @param position * The position to display * @param isScrap * Array of at least 1 boolean, the first entry will become true * if the returned view was taken from the scrap heap, false if * otherwise. * * @return A view displaying the data associated with the specified position */ View obtainView(int position, boolean[] isScrap) { isScrap[0] = false; View scrapView; scrapView = mRecycler.getScrapView(position); View child; if (scrapView != null) { child = mAdapter.getView(position, scrapView, this); if (child != scrapView) { mRecycler.addScrapView(scrapView); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } } else { isScrap[0] = true; dispatchFinishTemporaryDetach(child); } } else { child = mAdapter.getView(position, null, this); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } } return child; } ~~~ 第二个参数就是我们最熟悉的convertView呀,难怪平时我们在写getView()方法是要判断一下convertView是不是等于null,如果等于null才调用inflate()方法来加载布局,不等于null就可以直接利用convertView,因为convertView就是我们之间利用过的View,只不过被移出屏幕后进入到了废弃缓存中,现在又重新拿出来使用而已。然后我们只需要把convertView中的数据更新成当前位置上应该显示的数据,那么看起来就好像是全新加载出来的一个布局一样,这背后的道理你是不是已经完全搞明白了? 之后的代码又都是我们熟悉的流程了,从缓存中拿到子View之后再调用setupChild()方法将它重新attach到ListView当中,因为缓存中的View也是之前从ListView中detach掉的,这部分代码就不再重复进行分析了。 为了方便大家理解,这里我再附上一张图解说明: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7b07861.jpg) 那么到目前为止,我们就把ListView的整个工作流程代码基本分析结束了,文章比较长,希望大家可以理解清楚,下篇文章中会讲解我们平时使用ListView时遇到的问题,敬请期待。 **第一时间获得博客更新提醒,以及更多技术信息分享,欢迎关注我的微信公众号,扫一扫下方二维码或搜索微信号guolin_blog,即可关注。** [![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7b189de.jpg)]()
';

Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析

最后更新于:2022-04-01 09:53:18

【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重劳动成果】 ### 1 背景 之所以写这一篇博客的原因是因为之前有写过一篇[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721),然后有人在文章下面评论和微博私信中问我关于Android应用Activity、Dialog、PopWindow加载显示机制是咋回事,所以我就写一篇文章来分析分析吧(本文以Android5.1.1 (API 22)源码为基础分析),以便大家在应用层开发时不再迷糊。 PS一句:不仅有人微博私信我这个问题,还有人问博客插图这些是用啥画的,这里告诉大家。[就是我,快来猛戳我](https://www.processon.com/) 还记得之前[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)这篇文章的最后分析结果吗?就是如下这幅图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7959677.jpg "") 在那篇文章里我们当时重点是Activity的View加载解析xml机制分析,当时说到了Window的东西,但只是皮毛的分析了Activity相关的一些逻辑。(PS:看到这不清楚上面啥意思的建议先移步到[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721),完事再回头继续看这篇文章。)当时给大家承诺过我们要从应用控件一点一点往下慢慢深入分析,所以现在开始深入,但是本篇的深入也只是仅限Window相关的东东,之后文章还会继续慢慢深入。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重劳动成果】 ### 2 浅析Window与WindowManager相关关系及源码 通过上面那幅图可以很直观的看见,Android屏幕显示的就是Window和各种View,Activity在其中的作用主要是管理生命周期、建立窗口等。也就是说Window相关的东西对于Android屏幕来说是至关重要的(虽然前面分析Activity的setContentView等原理时说过一点Window,但那只是皮毛。),所以有必要在分析Android应用Activity、Dialog、PopWindow加载显示机制前再看看Window相关的一些东西。 #### 2-1 Window与WindowManager基础关系 在分析Window与WindowManager之前我们先看一张图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da79baa0a.jpg "") 接下来看一点代码,如下: ~~~ /** Interface to let you add and remove child views to an Activity. To get an instance * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. */ public interface ViewManager { public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); } ~~~ 可以看见,ViewManager接口定义了一组规则,也就是add、update、remove的操作View接口。也就是说ViewManager是用来添加和移除activity 中View的接口。继续往下看: ~~~ public interface WindowManager extends ViewManager { ...... public Display getDefaultDisplay(); public void removeViewImmediate(View view); ...... public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { ...... } } ~~~ 看见没有,WindowManager继承自ViewManager,然后自己还是一个接口,同时又定义了一个静态内部类LayoutParams(这个类比较重要,后面 会分析。提前透漏下,如果你在APP做过类似360助手屏幕的那个悬浮窗或者做过那种类似IOS的小白圆点,点击展开菜单功能,你或多或少就能猜到 这个类的重要性。)。WindowManager用来在应用与Window之间的接口、窗口顺序、消息等的管理。继续看下ViewManager的另一个实现子类 ViewGroup,如下: ~~~ public abstract class ViewGroup extends View implements ViewParent, ViewManager { //protected ViewParent mParent; //这个成员是View定义的,ViewGroup继承自View,所以也可以拥有。 //这个变量就是前面我们一系列文章分析View向上传递的父节点,类似于一个链表Node的next一样 //最终指向了ViewRoot ...... public void addView(View child, LayoutParams params) { addView(child, -1, params); } ...... public void addView(View child, int index, LayoutParams params) { ...... // addViewInner() will call child.requestLayout() when setting the new LayoutParams // therefore, we call requestLayout() on ourselves before, so that the child's request // will be blocked at our level requestLayout(); invalidate(true); addViewInner(child, index, params, false); } ...... } ~~~ 这下理解上面那幅图了吧,所以说View通过ViewGroup的addView方法添加到ViewGroup中,而ViewGroup层层嵌套到最顶级都会显示在在一个窗 口Window中(正如上面背景介绍中[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)的示意图一样),其中每个View都有 一个ViewParent类型的父节点mParent,最顶上的节点也是一个viewGroup,也即前面文章分析的Window的内部类DecorView(从[《Android应](http://blog.csdn.net/yanbober/article/details/45970721) [用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)的总结部分或者[《Android应用层View绘制流程与源码分析》](http://blog.csdn.net/yanbober/article/details/46128379)的5-1小节都可以验证 这个结论)对象。同时通过上面背景中那幅图可以看出来,对于一个Activity只有一个DecorView(ViewRoot),也只有一个Window。 #### 2-2 Activity窗口添加流程拓展 前面文章说过,ActivityThread类的performLaunchActivity方法中调运了activity.attach(…)方法进行初始化。如下是Activity的attach方法源码: ~~~ final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor) { ...... //创建Window类型的mWindow对象,实际为PhoneWindow类实现了抽象Window类 mWindow = PolicyManager.makeNewWindow(this); ...... //通过抽象Window类的setWindowManager方法给Window类的成员变量WindowManager赋值实例化 mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); ...... //把抽象Window类相关的WindowManager对象拿出来关联到Activity的WindowManager类型成员变量mWindowManager mWindowManager = mWindow.getWindowManager(); ...... } ~~~ 看见没有,Activity类中的attach方法又创建了Window类型的新成员变量mWindow(PhoneWindow实现类)与Activity相关联,接着在Activity类 的attach方法最后又通过mWindow.setWindowManager(…)方法创建了与Window相关联的WindowManager对象,最后又通过 mWindow.getWindowManager()将Window的WindowManager成员变量赋值给Activity的WindowManager成员变量mWindowManager。 接下来我们看下上面代码中的mWindow.setWindowManager(…)方法源码(PhoneWindow没有重写抽象Window的setWindowManager方法,所以直接看Window类的该方法源码),如下: ~~~ public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { ...... if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } //实例化Window类的WindowManager类型成员mWindowManager mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); } ~~~ 可以看见,Window的setWindowManager方法中通过WindowManagerImpl实例的createLocalWindowManager方法获取了WindowManager实 例,如下: ~~~ public final class WindowManagerImpl implements WindowManager { ...... private WindowManagerImpl(Display display, Window parentWindow) { mDisplay = display; mParentWindow = parentWindow; } ...... public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mDisplay, parentWindow); } ...... } ~~~ 看见没有?这样就把Activity的Window与WindowManager关联起来了。Activity类的Window类型成员变量mWindow及WindowManager类型成 员变量mWindowManager就是这么来的。 回过头继续看上面刚刚贴的Activity的attach方法代码,看见mWindow.setWindowManager方法传递的第一个参数没?有人会想(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)这行代码是什么意思,现在告诉你。 [《Android应用Context详解及源码解析》](http://blog.csdn.net/yanbober/article/details/45967639)一文中第三部分曾经说过ActivityThread中创建了Acitivty(执行attach等方法)等东东,在创建这个Activity之前得到了Context的实例。记不记得当时说Context的实现类就是ContextImpl吗?下面我们看下ContextImpl类的静态方法块,如下: ~~~ class ContextImpl extends Context { ...... //静态代码块,类加载时执行一次 static { ...... //这里有一堆类似的XXX_SERVICE的注册 ...... registerService(WINDOW_SERVICE, new ServiceFetcher() { Display mDefaultDisplay; public Object getService(ContextImpl ctx) { //搞一个Display实例 Display display = ctx.mDisplay; if (display == null) { if (mDefaultDisplay == null) { DisplayManager dm = (DisplayManager)ctx.getOuterContext(). getSystemService(Context.DISPLAY_SERVICE); mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY); } display = mDefaultDisplay; } //返回一个WindowManagerImpl实例 return new WindowManagerImpl(display); }}); ...... } //这就是你在外面调运Context的getSystemService获取到的WindowManagerImpl实例 @Override public Object getSystemService(String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name); return fetcher == null ? null : fetcher.getService(this); } //上面static代码块创建WindowManagerImpl实例用到的方法 private static void registerService(String serviceName, ServiceFetcher fetcher) { if (!(fetcher instanceof StaticServiceFetcher)) { fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++; } SYSTEM_SERVICE_MAP.put(serviceName, fetcher); } } ~~~ 看见没有,我们都知道Java的静态代码块是类加载是执行一次的,也就相当于一个全局的,这样就相当于每个Application只有一个 WindowManagerImpl(display)实例。 还记不记得[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)一文2-6小节中说的,setContentView的实质显示是触发了Activity的resume状态,也就是触发了makeVisible方法,那我们再来看下这个方法,如下: ~~~ void makeVisible() { if (!mWindowAdded) { //也就是获取Activity的mWindowManager //这个mWindowManager是在Activity的attach中通过mWindow.getWindowManager()获得 ViewManager wm = getWindowManager(); //调运的实质就是ViewManager接口的addView方法,传入的是mDecorView wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); } ~~~ 特别注意,看见makeVisible方法的wm变量没,这个变量就是Window类中通过调运WindowManagerImpl的createLocalWindowManager创建的 实例,也就是说每一个Activity都会新创建这么一个WindowManager实例来显示Activity的界面的,有点和上面分析的ContextImpl中static块创建 的WindowManager不太一样的地方就在于Context的WindowManager对每个APP来说是一个全局单例的,而Activity的WindowManager是 每个Activity都会新创建一个的(其实你从上面分析的两个实例化WindowManagerImpl的构造函数参数传递就可以看出来,Activity中Window 的WindowManager成员在构造实例化时传入给WindowManagerImpl中mParentWindow成员的是当前Window对象,而ContextImpl的static块 中单例实例化WindowManagerImpl时传入给WindowManagerImpl中mParentWindow成员的是null值)。 继续看makeVisible中调运的WindowManagerImpl的addView方法如下: ~~~ public final class WindowManagerImpl implements WindowManager { //继承自Object的单例类 private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); ...... public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); //mParentWindow是上面分析的在Activity中获取WindowManagerImpl实例化时传入的当前Window //view是Activity中最顶层的mDecor mGlobal.addView(view, params, mDisplay, mParentWindow); } ...... } ~~~ 这里当前传入的view是mDecor,LayoutParams呢?可以看见是getWindow().getAttributes(),那我们进去看看Window类的这个属性,如下: ~~~ // The current window attributes. private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); ~~~ 原来是WindowManager的静态内部类LayoutParams的默认构造函数: ~~~ public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; format = PixelFormat.OPAQUE; } ~~~ 看见没有,Activity窗体的WindowManager.LayoutParams类型是TYPE_APPLICATION的。 继续回到WindowManagerImpl的addView方法,分析可以看见WindowManagerImpl中有一个单例模式的WindowManagerGlobal成员mGlobal,addView最终调运了WindowManagerGlobal的addView,源码如下: ~~~ public final class WindowManagerGlobal { ...... private final ArrayList<View> mViews = new ArrayList<View>(); private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); private final ArraySet<View> mDyingViews = new ArraySet<View>(); ...... public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ...... //获取Activity的Window的getWindow().getAttributes()的LayoutParams final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; //如果是Activity中调运的,parentWindow=Window,如果不是Activity的,譬如是Context的静态代码块的实例化则parentWindow为null if (parentWindow != null) { //依据当前Activity的Window调节sub Window的LayoutParams parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { ...... } ViewRootImpl root; ...... synchronized (mLock) { ...... //为当前Window创建ViewRoot root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); //把当前Window相关的东西存入各自的List中,在remove中会删掉 mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { //把View和ViewRoot关联起来,很重要!!! root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { ...... } } ...... } ~~~ 可以看见,在addView方法中会利用LayoutParams获得Window的属性,然后为每个Window创建ViewRootImpl,最后通过ViewRootImpl的 setView方法通过mSession向WindowManagerService发送添加窗口请求把窗口添加到WindowManager中,并且由WindowManager来管理窗口 的view、事件、消息收集处理等(ViewRootImpl的这一添加过程后面会写文章分析,这里先记住这个概念即可)。 至此我们对上面背景中那幅图,也就是[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)这篇文章总结部分的那幅图又进行了更深入的一点分析,其目的也就是为了下面分析Android应用Dialog、PopWindow、Toast加载显示机制做铺垫准备。 #### 2-3 继续顺藤摸瓜WindowManager.LayoutParams类的源码 上面2-1分析Window与WindowManager基础关系时提到了WindowManager有一个静态内部类LayoutParams,它继承于ViewGroup.LayoutParams,用于向WindowManager描述Window的管理策略。现在我们来看下这个类(PS:在AD上也可以看见,[自备梯子点我看AD的](http://developer.android.com/reference/android/view/WindowManager.LayoutParams.html)),如下: ~~~ public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { //窗口的绝对XY位置,需要考虑gravity属性 public int x; public int y; //在横纵方向上为相关的View预留多少扩展像素,如果是0则此view不能被拉伸,其他情况下扩展像素被widget均分 public float horizontalWeight; public float verticalWeight; //窗口类型 //有3种主要类型如下: //ApplicationWindows取值在FIRST_APPLICATION_WINDOW与LAST_APPLICATION_WINDOW之间,是常用的顶层应用程序窗口,须将token设置成Activity的token; //SubWindows取值在FIRST_SUB_WINDOW和LAST_SUB_WINDOW之间,与顶层窗口相关联,需将token设置成它所附着宿主窗口的token; //SystemWindows取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW之间,不能用于应用程序,使用时需要有特殊权限,它是特定的系统功能才能使用; public int type; //WindowType:开始应用程序窗口 public static final int FIRST_APPLICATION_WINDOW = 1; //WindowType:所有程序窗口的base窗口,其他应用程序窗口都显示在它上面 public static final int TYPE_BASE_APPLICATION = 1; //WindowType:普通应用程序窗口,token必须设置为Activity的token来指定窗口属于谁 public static final int TYPE_APPLICATION = 2; //WindowType:应用程序启动时所显示的窗口,应用自己不要使用这种类型,它被系统用来显示一些信息,直到应用程序可以开启自己的窗口为止 public static final int TYPE_APPLICATION_STARTING = 3; //WindowType:结束应用程序窗口 public static final int LAST_APPLICATION_WINDOW = 99; //WindowType:SubWindows子窗口,子窗口的Z序和坐标空间都依赖于他们的宿主窗口 public static final int FIRST_SUB_WINDOW = 1000; //WindowType: 面板窗口,显示于宿主窗口的上层 public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; //WindowType:媒体窗口(例如视频),显示于宿主窗口下层 public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1; //WindowType:应用程序窗口的子面板,显示于所有面板窗口的上层 public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2; //WindowType:对话框,类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口 public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3; //WindowType:媒体信息,显示在媒体层和程序窗口之间,需要实现半透明效果 public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4; //WindowType:子窗口结束 public static final int LAST_SUB_WINDOW = 1999; //WindowType:系统窗口,非应用程序创建 public static final int FIRST_SYSTEM_WINDOW = 2000; //WindowType:状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方 public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW; //WindowType:搜索栏,只能有一个搜索栏,位于屏幕上方 public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1; //WindowType:电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下 public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2; //WindowType:系统提示,出现在应用程序窗口之上 public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3; //WindowType:锁屏窗口 public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4; //WindowType:信息窗口,用于显示Toast public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5; //WindowType:系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏 public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6; //WindowType:电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏 public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7; //WindowType:系统对话框 public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8; //WindowType:锁屏时显示的对话框 public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9; //WindowType:系统内部错误提示,显示于所有内容之上 public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10; //WindowType:内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖 public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11; //WindowType:内部输入法对话框,显示于当前输入法窗口之上 public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12; //WindowType:墙纸窗口 public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13; //WindowType:状态栏的滑动面板 public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14; //WindowType:安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘 public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15; //WindowType:拖放伪窗口,只有一个阻力层(最多),它被放置在所有其他窗口上面 public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16; //WindowType:状态栏下拉面板 public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17; //WindowType:鼠标指针 public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18; //WindowType:导航栏(有别于状态栏时) public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19; //WindowType:音量级别的覆盖对话框,显示当用户更改系统音量大小 public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20; //WindowType:起机进度框,在一切之上 public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21; //WindowType:假窗,消费导航栏隐藏时触摸事件 public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22; //WindowType:梦想(屏保)窗口,略高于键盘 public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23; //WindowType:导航栏面板(不同于状态栏的导航栏) public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24; //WindowType:universe背后真正的窗户 public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25; //WindowType:显示窗口覆盖,用于模拟辅助显示设备 public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26; //WindowType:放大窗口覆盖,用于突出显示的放大部分可访问性放大时启用 public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27; //WindowType:...... public static final int TYPE_KEYGUARD_SCRIM = FIRST_SYSTEM_WINDOW+29; public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30; public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31; public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32; //WindowType:系统窗口结束 public static final int LAST_SYSTEM_WINDOW = 2999; //MemoryType:窗口缓冲位于主内存 public static final int MEMORY_TYPE_NORMAL = 0; //MemoryType:窗口缓冲位于可以被DMA访问,或者硬件加速的内存区域 public static final int MEMORY_TYPE_HARDWARE = 1; //MemoryType:窗口缓冲位于可被图形加速器访问的区域 public static final int MEMORY_TYPE_GPU = 2; //MemoryType:窗口缓冲不拥有自己的缓冲区,不能被锁定,缓冲区由本地方法提供 public static final int MEMORY_TYPE_PUSH_BUFFERS = 3; //指出窗口所使用的内存缓冲类型,默认为NORMAL public int memoryType; //Flag:当该window对用户可见的时候,允许锁屏 public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001; //Flag:让该window后所有的东西都成暗淡 public static final int FLAG_DIM_BEHIND = 0x00000002; //Flag:让该window后所有东西都模糊(4.0以上已经放弃这种毛玻璃效果) public static final int FLAG_BLUR_BEHIND = 0x00000004; //Flag:让window不能获得焦点,这样用户快就不能向该window发送按键事 public static final int FLAG_NOT_FOCUSABLE = 0x00000008; //Flag:让该window不接受触摸屏事件 public static final int FLAG_NOT_TOUCHABLE = 0x00000010; //Flag:即使在该window在可获得焦点情况下,依旧把该window之外的任何event发送到该window之后的其他window public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020; //Flag:当手机处于睡眠状态时,如果屏幕被按下,那么该window将第一个收到 public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040; //Flag:当该window对用户可见时,让设备屏幕处于高亮(bright)状态 public static final int FLAG_KEEP_SCREEN_ON = 0x00000080; //Flag:让window占满整个手机屏幕,不留任何边界 public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100; //Flag:window大小不再不受手机屏幕大小限制,即window可能超出屏幕之外 public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200; //Flag:window全屏显示 public static final int FLAG_FULLSCREEN = 0x00000400; //Flag:恢复window非全屏显示 public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800; //Flag:开启抖动(dithering) public static final int FLAG_DITHER = 0x00001000; //Flag:当该window在进行显示的时候,不允许截屏 public static final int FLAG_SECURE = 0x00002000; //Flag:一个特殊模式的布局参数用于执行扩展表面合成时到屏幕上 public static final int FLAG_SCALED = 0x00004000; //Flag:用于windows时,经常会使用屏幕用户持有反对他们的脸,它将积极过滤事件流,以防止意外按在这种情况下,可能不需要为特定的窗口,在检测到这样一个事件流时,应用程序将接收取消运动事件表明,这样应用程序可以处理这相应地采取任何行动的事件,直到手指释放 public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000; //Flag:一个特殊的选项只用于结合FLAG_LAYOUT_IN_SC public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000; //Flag:转化的状态FLAG_NOT_FOCUSABLE对这个窗口当前如何进行交互的方法 public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000; //Flag:如果你设置了该flag,那么在你FLAG_NOT_TOUNCH_MODAL的情况下,即使触摸屏事件发送在该window之外,其事件被发送到了后面的window,那么该window仍然将以MotionEvent.ACTION_OUTSIDE形式收到该触摸屏事件 public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000; //Flag:当锁屏的时候,显示该window public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000; //Flag:在该window后显示系统的墙纸 public static final int FLAG_SHOW_WALLPAPER = 0x00100000; //Flag:当window被显示的时候,系统将把它当做一个用户活动事件,以点亮手机屏幕 public static final int FLAG_TURN_SCREEN_ON = 0x00200000; //Flag:消失键盘 public static final int FLAG_DISMISS_KEYGUARD = 0x00400000; //Flag:当该window在可以接受触摸屏情况下,让因在该window之外,而发送到后面的window的触摸屏可以支持split touch public static final int FLAG_SPLIT_TOUCH = 0x00800000; //Flag:对该window进行硬件加速,该flag必须在Activity或Dialog的Content View之前进行设置 public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000; //Flag:让window占满整个手机屏幕,不留任何边界 public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000; //Flag:请求一个半透明的状态栏背景以最小的系统提供保护 public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000; //Flag:请求一个半透明的导航栏背景以最小的系统提供保护 public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000; //Flag:...... public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000; public static final int FLAG_SLIPPERY = 0x20000000; public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000; public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000; //行为选项标记 public int flags; //PrivateFlags:...... public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001; public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002; public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004; public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010; public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040; public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080; public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100; public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200; public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400; public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800; //私有的行为选项标记 public int privateFlags; public static final int NEEDS_MENU_UNSET = 0; public static final int NEEDS_MENU_SET_TRUE = 1; public static final int NEEDS_MENU_SET_FALSE = 2; public int needsMenuKey = NEEDS_MENU_UNSET; public static boolean mayUseInputMethod(int flags) { ...... } //SOFT_INPUT:用于描述软键盘显示规则的bite的mask public static final int SOFT_INPUT_MASK_STATE = 0x0f; //SOFT_INPUT:没有软键盘显示的约定规则 public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0; //SOFT_INPUT:可见性状态softInputMode,请不要改变软输入区域的状态 public static final int SOFT_INPUT_STATE_UNCHANGED = 1; //SOFT_INPUT:用户导航(navigate)到你的窗口时隐藏软键盘 public static final int SOFT_INPUT_STATE_HIDDEN = 2; //SOFT_INPUT:总是隐藏软键盘 public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3; //SOFT_INPUT:用户导航(navigate)到你的窗口时显示软键盘 public static final int SOFT_INPUT_STATE_VISIBLE = 4; //SOFT_INPUT:总是显示软键盘 public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5; //SOFT_INPUT:显示软键盘时用于表示window调整方式的bite的mask public static final int SOFT_INPUT_MASK_ADJUST = 0xf0; //SOFT_INPUT:不指定显示软件盘时,window的调整方式 public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00; //SOFT_INPUT:当显示软键盘时,调整window内的控件大小以便显示软键盘 public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10; //SOFT_INPUT:当显示软键盘时,调整window的空白区域来显示软键盘,即使调整空白区域,软键盘还是有可能遮挡一些有内容区域,这时用户就只有退出软键盘才能看到这些被遮挡区域并进行 public static final int SOFT_INPUT_ADJUST_PAN = 0x20; //SOFT_INPUT:当显示软键盘时,不调整window的布局 public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30; //SOFT_INPUT:用户导航(navigate)到了你的window public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100; //软输入法模式选项 public int softInputMode; //窗口如何停靠 public int gravity; //水平边距,容器与widget之间的距离,占容器宽度的百分率 public float horizontalMargin; //纵向边距 public float verticalMargin; //积极的insets绘图表面和窗口之间的内容 public final Rect surfaceInsets = new Rect(); //期望的位图格式,默认为不透明,参考android.graphics.PixelFormat public int format; //窗口所使用的动画设置,它必须是一个系统资源而不是应用程序资源,因为窗口管理器不能访问应用程序 public int windowAnimations; //整个窗口的半透明值,1.0表示不透明,0.0表示全透明 public float alpha = 1.0f; //当FLAG_DIM_BEHIND设置后生效,该变量指示后面的窗口变暗的程度,1.0表示完全不透明,0.0表示没有变暗 public float dimAmount = 1.0f; public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f; public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f; public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f; public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE; //用来覆盖用户设置的屏幕亮度,表示应用用户设置的屏幕亮度,从0到1调整亮度从暗到最亮发生变化 public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE; public static final int ROTATION_ANIMATION_ROTATE = 0; public static final int ROTATION_ANIMATION_CROSSFADE = 1; public static final int ROTATION_ANIMATION_JUMPCUT = 2; //定义出入境动画在这个窗口旋转设备时使用 public int rotationAnimation = ROTATION_ANIMATION_ROTATE; //窗口的标示符 public IBinder token = null; //此窗口所在的包名 public String packageName = null; //屏幕方向 public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; //首选的刷新率的窗口 public float preferredRefreshRate; //控制status bar是否显示 public int systemUiVisibility; //ui能见度所请求的视图层次结构 public int subtreeSystemUiVisibility; //得到关于系统ui能见度变化的回调 public boolean hasSystemUiListeners; public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001; public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002; public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004; public int inputFeatures; public long userActivityTimeout = -1; ...... public final int copyFrom(LayoutParams o) { ...... } ...... public void scale(float scale) { ...... } ...... } ~~~ 看见没有,从上面类可以看出,Android窗口类型主要分成了三大类: 1. 应用程序窗口。一般应用程序的窗口,比如我们应用程序的Activity的窗口。 1. 子窗口。一般在Activity里面的窗口,比如对话框等。 1. 系统窗口。系统的窗口,比如输入法,Toast,墙纸等。 同时还可以看见,WindowManager.LayoutParams里面窗口的type类型值定义是一个递增保留的连续增大数值,从注释可以看出来其实就是窗口的Z-ORDER序列(值越大显示的位置越在上面,你需要将屏幕想成三维坐标模式)。创建不同类型的窗口需要设置不同的type值,譬如上面拓展Activity窗口加载时分析的makeVisible方法中的Window默认属性的type=TYPE_APPLICATION。 既然说这个类很重要,那总得感性的体验一下重要性吧,所以我们先来看几个实例。 #### 2-4 通过上面WindowManager.LayoutParams分析引出的应用层开发常用经典实例 有了上面分析相信你一定觉得WindowManager.LayoutParams还是蛮熟悉的,不信我们来看下。 Part1:开发APP时设置Activity全屏常亮的一种办法(设置Activity也就是Activity的Window): ~~~ public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置Activity的Window为全屏,当然也可以在xml中设置 Window window = getWindow(); WindowManager.LayoutParams windowAttributes = window.getAttributes(); windowAttributes.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN | windowAttributes.flags; window.setAttributes(windowAttributes); //设置Activity的Window为保持屏幕亮 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setContentView(R.layout.activity_main); } } ~~~ 这是运行结果:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da79da1ef.jpg "") Part2:App开发中弹出软键盘时下面的输入框被软件盘挡住问题的解决办法: 在Activity中的onCreate中setContentView之前写如下代码: ~~~ //你也可以在xml文件中设置,一样的效果 getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE|WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); ~~~ Part3:创建悬浮窗口(仿IPhone的小圆点或者魅族的小白点或者360手机卫士的小浮标),退出当前Activity依旧可见的一种实现方法: 省略了Activity的start与stop Service的按钮代码,直接给出了核心代码如下: ~~~ /** * Author : yanbo * Time : 14:47 * Description : 手机屏幕悬浮窗,仿IPhone小圆点 * (未完全实现,只提供思路,如需请自行实现) * Notice : <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> */ public class WindowService extends Service { private WindowManager mWindowManager; private ImageView mImageView; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); //创建悬浮窗 createFloatWindow(); } private void createFloatWindow() { //这里的参数设置上面刚刚讲过,不再说明 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE); //设置window的type layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; //设置效果为背景透明 layoutParams.format = PixelFormat.RGBA_8888; //设置浮动窗口不可聚焦 layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; layoutParams.gravity = Gravity.BOTTOM | Gravity.RIGHT; layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.x = -50; layoutParams.y = -50; mImageView = new ImageView(this); mImageView.setImageResource(android.R.drawable.ic_menu_add); //添加到Window mWindowManager.addView(mImageView, layoutParams); //设置监听 mImageView.setOnTouchListener(touchListener); } @Override public void onDestroy() { super.onDestroy(); if (mImageView != null) { //讲WindowManager时说过,add,remove成对出现,所以需要remove mWindowManager.removeView(mImageView); } } private View.OnTouchListener touchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //模拟触摸触发的事件 Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); return false; } }; } ~~~ 如下是运行过程模拟,特别留意屏幕右下角的变化: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7a01d37.jpg "") 怎么样,通过最后这个例子你是不是就能体会到WindowManager.LayoutParams的Z-ORDER序列类型,值越大显示的位置越在上面。 #### 2-5 总结Activity的窗口添加机制 有了上面这么多分析和前几篇的分析,我们对Activity的窗口加载再次深入分析总结如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7a4fa11.jpg "") 可以看见Context的WindowManager对每个APP来说是一个全局单例的,而Activity的WindowManager是每个Activity都会新创建一个的(其实你从上面分析的两个实例化WindowManagerImpl的构造函数参数传递就可以看出来,Activity中Window的WindowManager成员在构造实例化时传入给WindowManagerImpl中mParentWindow成员的是当前Window对象,而ContextImpl的static块中单例实例化WindowManagerImpl时传入给WindowManagerImpl中mParentWindow成员的是null值),所以上面模拟苹果浮动小图标使用了Application的WindowManager而不是Activity的,原因就在于这里;使用Activity的WindowManager时当Activity结束时WindowManager就无效了,所以使用Activity的getSysytemService(WINDOW_SERVICE)获取的是Local的WindowManager。同时可以看出来Activity中的WindowManager.LayoutParams的type为TYPE_APPLICATION。 好了,上面也说了不少了,有了上面这些知识点以后我们就来开始分析Android应用Activity、Dialog、PopWindow窗口显示机制。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重劳动成果】 ### 3 Android应用Dialog窗口添加显示机制源码 #### 3-1 Dialog窗口源码分析 写过APP都知道,Dialog是一系列XXXDialog的基类,我们可以new任意Dialog或者通过Activity提供的onCreateDialog(……)、onPrepareDialog(……)和showDialog(……)等方法来管理我们的Dialog,但是究其实质都是来源于Dialog基类,所以我们对于各种XXXDialog来说只用分析Dialog的窗口加载就可以了。 如下从Dialog的构造函数开始分析: ~~~ public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { ...... public Dialog(Context context) { this(context, 0, true); } //构造函数最终都调运了这个默认的构造函数 Dialog(Context context, int theme, boolean createContextThemeWrapper) { //默认构造函数的createContextThemeWrapper为true if (createContextThemeWrapper) { //默认构造函数的theme为0 if (theme == 0) { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, outValue, true); theme = outValue.resourceId; } mContext = new ContextThemeWrapper(context, theme); } else { mContext = context; } //mContext已经从外部传入的context对象获得值(一般是个Activity)!!!非常重要,先记住!!! //获取WindowManager对象 mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); //为Dialog创建新的Window Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; //Dialog能够接受到按键事件的原因 w.setCallback(this); w.setOnWindowDismissedCallback(this); //关联WindowManager与新Window,特别注意第二个参数token为null,也就是说Dialog没有自己的token //一个Window属于Dialog的话,那么该Window的mAppToken对象是null w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } ...... } ~~~ 可以看到,Dialog构造函数首先把外部传入的参数context对象赋值给了当前类的成员(我们的Dialog一般都是在Activity中启动的,所以这个 context一般是个Activity),然后调用context.getSystemService(Context.WINDOW_SERVICE)获取WindowManager,这个WindowManager 是哪来的呢?先按照上面说的context一般是个Activity来看待,可以发现这句实质就是Activity的getSystemService方法,我们看下源码,如下: ~~~ @Override public Object getSystemService(@ServiceName @NonNull String name) { if (getBaseContext() == null) { throw new IllegalStateException( "System services not available to Activities before onCreate()"); } //我们Dialog中获得的WindowManager对象就是这个分支 if (WINDOW_SERVICE.equals(name)) { //Activity的WindowManager return mWindowManager; } else if (SEARCH_SERVICE.equals(name)) { ensureSearchManager(); return mSearchManager; } return super.getSystemService(name); } ~~~ 看见没有,Dialog中的WindowManager成员实质和Activity里面是一样的,也就是共用了一个WindowManager。 回到Dialog的构造函数继续分析,在得到了WindowManager之后,程序又新建了一个Window对象(类型是PhoneWindow类型,和Activity的Window新建过程类似);接着通过w.setCallback(this)设置Dialog为当前window的回调接口,这样Dialog就能够接收事件处理了;接着把从Activity拿到的WindowManager对象关联到新创建的Window中。 至此Dialog的创建过程Window处理已经完毕,很简单,所以接下来我们继续看看Dialog的show与cancel方法,如下: ~~~ public void show() { ...... if (!mCreated) { //回调Dialog的onCreate方法 dispatchOnCreate(null); } //回调Dialog的onStart方法 onStart(); //类似于Activity,获取当前新Window的DecorView对象,所以有一种自定义Dialog布局的方式就是重写Dialog的onCreate方法,使用setContentView传入布局,就像前面文章分析Activity类似 mDecor = mWindow.getDecorView(); ...... //获取新Window的WindowManager.LayoutParams参数,和上面分析的Activity一样type为TYPE_APPLICATION WindowManager.LayoutParams l = mWindow.getAttributes(); ...... try { //把一个View添加到Activity共用的windowManager里面去 mWindowManager.addView(mDecor, l); ...... } finally { } } ~~~ 可以看见Dialog的新Window与Activity的Window的type同样都为TYPE_APPLICATION,上面介绍WindowManager.LayoutParams时 TYPE_APPLICATION的注释明确说过,普通应用程序窗口TYPE_APPLICATION的token必须设置为Activity的token来指定窗口属于谁。 所以可以看见,既然Dialog和Activity共享同一个WindowManager(也就是上面分析的WindowManagerImpl),而WindowManagerImpl里 面有个Window类型的mParentWindow变量,这个变量在Activity的attach中创建WindowManagerImpl时传入的为当前Activity的Window, 而当前Activity的Window里面的mAppToken值又为当前Activity的token,所以Activity与Dialog共享了同一个mAppToken值,只是Dialog和 Activity的Window对象不同。 #### 3-2 Dialog窗口加载总结 通过上面分析Dialog的窗口加载原理,我们总结如下图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7a6b7fe.jpg "") 从图中可以看出,Activity和Dialog共用了一个Token对象,Dialog必须依赖于Activity而显示(通过别的context搞完之后token都为null,最终会在ViewRootImpl的setView方法中加载时因为token为null抛出异常),所以Dialog的Context传入参数一般是一个存在的Activity,如果Dialog弹出来之前Activity已经被销毁了,则这个Dialog在弹出的时候就会抛出异常,因为token不可用了。在Dialog的构造函数中我们关联了新Window的callback事件监听处理,所以当Dialog显示时Activity无法消费当前的事件。 到此Dialog的窗口加载机制就分析完毕了,接下来我们说说应用开发中常见的一个诡异问题。 #### 3-3 从Dialog窗口加载分析引出的应用开发问题 有了上面的分析我们接下来看下平时开发App初学者容易犯的几个错误。 实现在一个Activity中显示一个Dialog,如下代码: ~~~ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); //重点关注构造函数的参数,创建一个Dialog然后显示出来 Dialog dialog = new ProgressDialog(this); dialog.setTitle("TestDialogContext"); dialog.show(); } } ~~~ 分析:使用了Activity为context,也即和Activity共用token,符合上面的分析,所以不会报错,正常执行。 实现在一个Activity中显示一个Dialog,如下代码: ~~~ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); //重点关注构造函数的参数,创建一个Dialog然后显示出来 Dialog dialog = new ProgressDialog(getApplicationContext()); dialog.setTitle("TestDialogContext"); dialog.show(); } } ~~~ 分析:传入的是Application的Context,导致TYPE_APPLICATION类型Dialog的token为null,所以抛出如下异常,无法显示对话框。 ~~~ Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application at android.view.ViewRootImpl.setView(ViewRootImpl.java:566) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:272) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69) at android.app.Dialog.show(Dialog.java:298) ~~~ 实现在一个Service中显示一个Dialog,如下代码: ~~~ public class WindowService extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); //重点关注构造函数的参数 Dialog dialog = new ProgressDialog(this); dialog.setTitle("TestDialogContext"); dialog.show(); } } ~~~ 分析:传入的Context是一个Service,类似上面传入ApplicationContext一样的后果,一样的原因,抛出如下异常: ~~~ Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application at android.view.ViewRootImpl.setView(ViewRootImpl.java:566) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:272) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69) at android.app.Dialog.show(Dialog.java:298) ~~~ 至此通过我们平时使用最多的Dialog也验证了Dialog成功显示的必要条件,同时也让大家避免了再次使用Dialog不当出现异常的情况,或者出现类似 异常后知道真实的背后原因是什么的问题。 可以看见,Dialog的实质无非也是使用WindowManager的addView、updateViewLayout、removeView进行一些操作展示。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重劳动成果】 ### 4 Android应用PopWindow窗口添加显示机制源码 PopWindow实质就是弹出式菜单,它与Dialag不同的地方是不会使依赖的Activity组件失去焦点(PopupWindow弹出后可以继续与依赖的Activity进行交互),Dialog却不能这样。同时PopupWindow与Dialog另一个不同点是PopupWindow是一个阻塞的对话框,如果你直接在Activity的onCreate等方法中显示它则会报错,所以PopupWindow必须在某个事件中显示地或者是开启一个新线程去调用。 说这么多还是直接看代码吧。 #### 4-1 PopWindow窗口源码分析 依据PopWindow的使用,我们选择最常用的方式来分析,如下先看其中常用的一种构造函数: ~~~ public class PopupWindow { ...... //我们只分析最常用的一种构造函数 public PopupWindow(View contentView, int width, int height, boolean focusable) { if (contentView != null) { //获取mContext,contentView实质是View,View的mContext都是构造函数传入的,View又层级传递,所以最终这个mContext实质是Activity!!!很重要 mContext = contentView.getContext(); //获取Activity的getSystemService的WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } //进行一些Window类的成员变量初始化赋值操作 setContentView(contentView); setWidth(width); setHeight(height); setFocusable(focusable); } ...... } ~~~ 可以看见,构造函数只是初始化了一些变量,看完构造函数继续看下PopWindow的展示函数,如下: ~~~ public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { ...... //anchor是Activity中PopWindow准备依附的View,这个View的token实质也是Activity的Window中的token,也即Activity的token //第一步 初始化WindowManager.LayoutParams WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); //第二步 preparePopup(p); ...... //第三步 invokePopup(p); } ~~~ 可以看见,当我们想将PopWindow展示在anchor的下方向(Z轴是在anchor的上面)旁边时经理了上面三步,我们一步一步来分析,先看第一步, 源码如下: ~~~ private WindowManager.LayoutParams createPopupLayout(IBinder token) { //实例化一个默认的WindowManager.LayoutParams,其中type=TYPE_APPLICATION WindowManager.LayoutParams p = new WindowManager.LayoutParams(); //设置Gravity p.gravity = Gravity.START | Gravity.TOP; //设置宽高 p.width = mLastWidth = mWidth; p.height = mLastHeight = mHeight; //依据背景设置format if (mBackground != null) { p.format = mBackground.getOpacity(); } else { p.format = PixelFormat.TRANSLUCENT; } //设置flags p.flags = computeFlags(p.flags); //修改type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type类型为子窗口 p.type = mWindowLayoutType; //设置token为Activity的token p.token = token; ...... return p; } ~~~ 接着回到showAsDropDown方法看看第二步,如下源码: ~~~ private void preparePopup(WindowManager.LayoutParams p) { ...... //有无设置PopWindow的background区别 if (mBackground != null) { ...... //如果有背景则创建一个PopupViewContainer对象的ViewGroup PopupViewContainer popupViewContainer = new PopupViewContainer(mContext); PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, height ); //把背景设置给PopupViewContainer的ViewGroup popupViewContainer.setBackground(mBackground); //把我们构造函数传入的View添加到这个ViewGroup popupViewContainer.addView(mContentView, listParams); //返回这个ViewGroup mPopupView = popupViewContainer; } else { //如果没有通过PopWindow的setBackgroundDrawable设置背景则直接赋值当前传入的View为PopWindow的View mPopupView = mContentView; } ...... } ~~~ 可以看见preparePopup方法的作用就是判断设置View,如果有背景则会在传入的contentView外面包一层PopupViewContainer(实质是一个重写 了事件处理的FrameLayout)之后作为mPopupView,如果没有背景则直接用contentView作为mPopupView。我们再来看下这里的 PopupViewContainer类,如下源码: ~~~ private class PopupViewContainer extends FrameLayout { ...... @Override protected int[] onCreateDrawableState(int extraSpace) { ...... } @Override public boolean dispatchKeyEvent(KeyEvent event) { ...... } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { return true; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { ...... if(xxx) { dismiss(); } ...... } @Override public void sendAccessibilityEvent(int eventType) { ...... } } ~~~ 可以看见,这个PopupViewContainer是一个PopWindow的内部私有类,它继承了FrameLayout,在其中重写了Key和Touch事件的分发处理逻辑。 同时查阅PopupView可以发现,PopupView类自身没有重写Key和Touch事件的处理,所以如果没有将传入的View对象放入封装的ViewGroup中, 则点击Back键或者PopWindow以外的区域PopWindow是不会消失的(其实PopWindow中没有向Activity及Dialog一样new新的Window, 所以不会有新的callback设置,也就没法处理事件消费了)。 接着继续回到showAsDropDown方法看看第三步,如下源码: ~~~ private void invokePopup(WindowManager.LayoutParams p) { if (mContext != null) { p.packageName = mContext.getPackageName(); } mPopupView.setFitsSystemWindows(mLayoutInsetDecor); setLayoutDirectionFromAnchor(); mWindowManager.addView(mPopupView, p); } ~~~ 可以看见,这里使用了Activity的WindowManager将我们的PopWindow进行了显示。 到此可以发现,PopWindow的实质无非也是使用WindowManager的addView、updateViewLayout、removeView进行一些操作展示。与Dialog不同的地方是没有新new Window而已(也就没法设置callback,无法消费事件,也就是前面说的PopupWindow弹出后可以继续与依赖的Activity进行交互的原因)。 到此PopWindw的窗口加载显示机制就分析完毕了,接下来进行总结与应用开发技巧提示。 #### 4-2 PopWindow窗口源码分析总结及应用开发技巧提示 通过上面分析可以发现总结如下图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7a874a0.jpg "") 可以看见,PopWindow完全使用了Activity的Window与WindowManager,相对来说比较简单容易记理解。 再来看一个开发技巧: 如果设置了PopupWindow的background,则点击Back键或者点击PopupWindow以外的区域时PopupWindow就会dismiss;如果不设置PopupWindow的background,则点击Back键或者点击PopupWindow以外的区域PopupWindow不会消失。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重劳动成果】 ### 5 Android应用Toast窗口添加显示机制源码 #### 5-1 基础知识准备 在开始分析这几个窗口之前需要脑补一点东东,我们从应用层开发来直观脑补,这样下面分析源码时就不蛋疼了。如下是一个我们写的两个应用实现Service跨进程调用服务ADIL的例子,客户端调运远程Service的start与stop方法控制远程Service的操作。 Android系统中的应用程序都运行在各自的进程中,进程之间是无法直接交换数据的,但是Android为开发者提供了AIDL跨进程调用Service的功能。其实AIDL就相当于双方约定的一个规则而已。 先看下在Android Studio中AIDL开发的工程目录结构,如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7aaa72e.jpg "") 由于AIDL文件中不能出现访问修饰符(如public),同时AIDL文件在两个项目中要完全一致而且只支持基本类型,所以我们定义的AIDL文件如下: ITestService.aidl ~~~ package io.github.yanbober.myapplication; interface ITestService { void start(int id); void stop(int id); } ~~~ 再来看下依据aidl文件自动生成的ITestService.java文件吧,如下: ~~~ /* * This file is auto-generated. DO NOT MODIFY. */ package io.github.yanbober.myapplication; public interface ITestService extends android.os.IInterface { //Stub类是ITestService接口的内部静态抽象类,该类继承了Binder类 public static abstract class Stub extends android.os.Binder implements io.github.yanbober.myapplication.ITestService { ...... //这是抽象静态Stub类中的asInterface方法,该方法负责将service返回至client的对象转换为ITestService.Stub //把远程Service的Binder对象传递进去,得到的是远程服务的本地代理 public static io.github.yanbober.myapplication.ITestService asInterface(android.os.IBinder obj) { ...... } ...... //远程服务的本地代理,也会继承自ITestService private static class Proxy implements io.github.yanbober.myapplication.ITestService { ...... @Override public void start(int id) throws android.os.RemoteException { ...... } @Override public void stop(int id) throws android.os.RemoteException { ...... } } ...... } //两个方法是aidl文件中定义的方法 public void start(int id) throws android.os.RemoteException; public void stop(int id) throws android.os.RemoteException; } ~~~ 这就是自动生成的java文件,接下来我们看看服务端的Service源码,如下: ~~~ //记得在AndroidManifet.xml中注册Service的<action android:name="io.github.yanbober.myapplication.aidl" /> public class TestService extends Service { private TestBinder mTestBinder; //该类继承ITestService.Stub类而不是Binder类,因为ITestService.Stub是Binder的子类 //进程内的Service定义TestBinder内部类是继承Binder类 public class TestBinder extends ITestService.Stub { @Override public void start(int id) throws RemoteException { Log.i(null, "Server Service is start!"); } @Override public void stop(int id) throws RemoteException { Log.i(null, "Server Service is stop!"); } } @Override public IBinder onBind(Intent intent) { //返回Binder return mTestBinder; } @Override public void onCreate() { super.onCreate(); //实例化Binder mTestBinder = new TestBinder(); } } ~~~ 现在服务端App的代码已经OK,我们来看下客户端的代码。客户端首先也要像上面的工程结构一样,把AIDL文件放好,接着在客户端使用远程服务端 的Service代码如下: ~~~ public class MainActivity extends Activity { private static final String REMOT_SERVICE_ACTION = "io.github.yanbober.myapplication.aidl"; private Button mStart, mStop; private ITestService mBinder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //获得另一个进程中的Service传递过来的IBinder对象 //用IMyService.Stub.asInterface方法转换该对象 mBinder = ITestService.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mStart = (Button) this.findViewById(R.id.start); mStop = (Button) this.findViewById(R.id.stop); mStart.setOnClickListener(clickListener); mStop.setOnClickListener(clickListener); //绑定远程跨进程Service bindService(new Intent(REMOT_SERVICE_ACTION), connection, BIND_AUTO_CREATE); } @Override protected void onDestroy() { super.onDestroy(); //取消绑定远程跨进程Service unbindService(connection); } private View.OnClickListener clickListener = new View.OnClickListener() { @Override public void onClick(View v) { ////调用远程Service中的start与stop方法 switch (v.getId()) { case R.id.start: try { mBinder.start(0x110); } catch (RemoteException e) { e.printStackTrace(); } break; case R.id.stop: try { mBinder.stop(0x120); } catch (RemoteException e) { e.printStackTrace(); } break; } } }; } ~~~ 到此你对应用层通过AIDL使用远程Service的形式已经很熟悉了,至于实质的通信使用Binder的机制我们后面会写文章一步一步往下分析。到此的准备 知识已经足够用来理解下面我们的源码分析了。 #### 5-2 Toast窗口源码分析 我们常用的Toast窗口其实和前面分析的Activity、Dialog、PopWindow都是不同的,因为它和输入法、墙纸类似,都是系统窗口。 我们还是按照最常用的方式来分析源码吧。 我们先看下Toast的静态makeText方法吧,如下: ~~~ public static Toast makeText(Context context, CharSequence text, @Duration int duration) { //new一个Toast对象 Toast result = new Toast(context); //获取前面有篇文章分析的LayoutInflater LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); //加载解析Toast的布局,实质transient_notification.xml是一个LinearLayout中套了一个@android:id/message的TextView而已 View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); //取出布局中的TextView TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); //把我们的文字设置到TextView上 tv.setText(text); //设置一些属性 result.mNextView = v; result.mDuration = duration; //返回新建的Toast return result; } ~~~ 可以看见,这个方法构造了一个Toast,然后把要显示的文本放到这个View的TextView中,然后初始化相关属性后返回这个新的Toast对象。 当我们有了这个Toast对象之后,可以通过show方法来显示出来,如下看下show方法源码: ~~~ public void show() { ...... //通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,当前Toast类相当于上面例子的客户端!!!相当重要!!! INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { //把TN对象和一些参数传递到远程NotificationManagerService中去 service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } } ~~~ 我们看看show方法中调运的getService方法,如下: ~~~ //远程NotificationManagerService的服务访问接口 private static INotificationManager sService; static private INotificationManager getService() { //单例模式 if (sService != null) { return sService; } //通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口 sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification")); return sService; } ~~~ 通过上面我们的基础脑补实例你也能看懂这个getService方法了吧。那接着我们来看mTN吧,好像mTN在Toast的构造函数里见过一眼,我们来看 看,如下: ~~~ public Toast(Context context) { mContext = context; mTN = new TN(); mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); mTN.mGravity = context.getResources().getInteger( com.android.internal.R.integer.config_toastDefaultGravity); } ~~~ 可以看见mTN确实是在构造函数中实例化的,那我们就来看看这个TN类,如下: ~~~ //类似于上面例子的服务端实例化的Service内部类Binder private static class TN extends ITransientNotification.Stub { ...... //实现了AIDL的show与hide方法 @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } ...... } ~~~ 看见没有,TN是Toast内部的一个私有静态类,继承自ITransientNotification.Stub。你这时指定好奇ITransientNotification.Stub是个啥玩意, 对吧?其实你在上面的脑补实例中见过它的,他出现在服务端实现的Service中,就是一个Binder对象,也就是对一个aidl文件的实现而已, 我们看下这个ITransientNotification.aidl文件,如下: ~~~ package android.app; /** @hide */ oneway interface ITransientNotification { void show(); void hide(); } ~~~ 看见没有,和我们上面的例子很类似吧。 再回到上面分析的show()方法中可以看到,我们的Toast是传给远程的NotificationManagerService管理的,为了NotificationManagerService回到我们的应用程序(回调),我们需要告诉NotificationManagerService我们当前程序的Binder引用是什么(也就是TN)。是不是觉得和上面例子有些不同,这里感觉Toast又充当客户端,又充当服务端的样子,实质就是一个回调过程而已。 继续来看Toast中的show方法的service.enqueueToast(pkg, tn, mDuration);语句,service实质是远程的NotificationManagerService,所以enqueueToast方法就是NotificationManagerService类的,如下: ~~~ private final IBinder mService = new INotificationManager.Stub() { // Toasts // ============================================================================ @Override public void enqueueToast(String pkg, ITransientNotification callback, int duration) { ...... synchronized (mToastQueue) { int callingPid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); try { ToastRecord record; //查看该Toast是否已经在队列当中 int index = indexOfToastLocked(pkg, callback); // If it's already in the queue, we update it in place, we don't // move it to the end of the queue. //注释说了,已经存在则直接取出update if (index >= 0) { record = mToastQueue.get(index); record.update(duration); } else { // Limit the number of toasts that any given package except the android // package can enqueue. Prevents DOS attacks and deals with leaks. ...... //将Toast封装成ToastRecord对象,放入mToastQueue中 record = new ToastRecord(callingPid, pkg, callback, duration); //把他添加到ToastQueue队列中 mToastQueue.add(record); index = mToastQueue.size() - 1; //将当前Toast所在的进程设置为前台进程 keepProcessAliveLocked(callingPid); } //如果index为0,说明当前入队的Toast在队头,需要调用showNextToastLocked方法直接显示 if (index == 0) { showNextToastLocked(); } } finally { Binder.restoreCallingIdentity(callingId); } } } } ~~~ 继续看下该方法中调运的showNextToastLocked方法,如下: ~~~ void showNextToastLocked() { //取出ToastQueue中队列最前面的ToastRecord ToastRecord record = mToastQueue.get(0); while (record != null) { try { //Toast类中实现的ITransientNotification.Stub的Binder接口TN,调运了那个类的show方法 record.callback.show(); scheduleTimeoutLocked(record); return; } catch (RemoteException e) { ...... } } } ~~~ 继续先看下该方法中调运的scheduleTimeoutLocked方法,如下: ~~~ private void scheduleTimeoutLocked(ToastRecord r) { //移除上一条消息 mHandler.removeCallbacksAndMessages(r); //依据Toast传入的duration参数LENGTH_LONG=1来判断决定多久发送消息 Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; //依据设置的MESSAGE_TIMEOUT后发送消息 mHandler.sendMessageDelayed(m, delay); } ~~~ 可以看见这里先回调了Toast的TN的show,下面timeout可能就是hide了。接着还在该类的mHandler处理了这条消息,然后调运了如下处理方法: ~~~ private void handleTimeout(ToastRecord record) { ...... synchronized (mToastQueue) { int index = indexOfToastLocked(record.pkg, record.callback); if (index >= 0) { cancelToastLocked(index); } } } ~~~ 我们继续看cancelToastLocked方法,如下: ~~~ void cancelToastLocked(int index) { ToastRecord record = mToastQueue.get(index); try { //回调Toast的TN中实现的hide方法 record.callback.hide(); } catch (RemoteException e) { ...... } //从队列移除当前显示的Toast mToastQueue.remove(index); keepProcessAliveLocked(record.pid); if (mToastQueue.size() > 0) { //如果当前的Toast显示完毕队列里还有其他的Toast则显示其他的Toast showNextToastLocked(); } } ~~~ 到此可以发现,Toast的远程管理NotificationManagerService类的处理实质是通过Handler发送延时消息显示取消Toast的,而且在远程 NotificationManagerService类中又远程回调了Toast的TN类实现的show与hide方法。 现在我们就回到Toast的TN类再看看这个show与hide方法,如下: ~~~ private static class TN extends ITransientNotification.Stub { ...... //仅仅是实例化了一个Handler,非常重要!!!!!!!! final Handler mHandler = new Handler(); ...... final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() mNextView = null; } }; ...... //实现了AIDL的show与hide方法 @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } ...... } ~~~ 可以看见,这里实现aidl接口的方法实质是通过handler的post来执行的一个方法,而这个Handler仅仅只是new了一下,也就是说,如果我们写 APP时使用Toast在子线程中则需要自行准备Looper对象,只有主线程Activity创建时帮忙准备了Looper(关于Handler与Looper如果整不明白请 阅读[《Android异步消息处理机制详解及源码分析》](http://blog.csdn.net/yanbober/article/details/45936145))。 那我们重点关注一下handleShow与handleHide方法,如下: ~~~ public void handleShow() { if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" + mNextView); if (mView != mNextView) { // remove the old view if necessary //如果有必要就通过WindowManager的remove删掉旧的 handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); String packageName = mView.getContext().getOpPackageName(); if (context == null) { context = mView.getContext(); } //通过得到的context(一般是ContextImpl的context)获取WindowManager对象(上一篇文章分析的单例的WindowManager) mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); ...... //在把Toast的View添加之前发现Toast的View已经被添加过(有partent)则删掉 if (mView.getParent() != null) { ...... mWM.removeView(mView); } ...... //把Toast的View添加到窗口,其中mParams.type在构造函数中赋值为TYPE_TOAST!!!!!!特别重要 mWM.addView(mView, mParams); ...... } } ~~~ ~~~ public void handleHide() { if (mView != null) { // note: checking parent() just to make sure the view has // been added... i have seen cases where we get here when // the view isn't yet added, so let's try not to crash. //注释说得很清楚了,不解释,就是remove if (mView.getParent() != null) { mWM.removeView(mView); } mView = null; } } ~~~ 到此Toast的窗口添加原理就分析完毕了,接下来我们进行总结。 #### 5-3 Toast窗口源码分析总结及应用开发技巧 经过上面的分析我们总结如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7abdb0e.jpg "") 通过上面分析及上图直观描述可以发现,之所以Toast的显示交由远程的NotificationManagerService管理是因为Toast是每个应用程序都会弹出的,而且位置和UI风格都差不多,所以如果我们不统一管理就会出现覆盖叠加现象,同时导致不好控制,所以Google把Toast设计成为了系统级的窗口类型,由NotificationManagerService统一队列管理。 在我们开发应用程序时使用Toast注意事项: 1. 通过分析TN类的handler可以发现,如果想在非UI线程使用Toast需要自行声明Looper,否则运行会抛出Looper相关的异常;UI线程不需要,因为系统已经帮忙声明。 1. 在使用Toast时context参数尽量使用getApplicationContext(),可以有效的防止静态引用导致的内存泄漏。 1. 有时候我们会发现Toast弹出过多就会延迟显示,因为上面源码分析可以看见Toast.makeText是一个静态工厂方法,每次调用这个方法都会产生一个新的Toast对象,当我们在这个新new的对象上调用show方法就会使这个对象加入到NotificationManagerService管理的mToastQueue消息显示队列里排队等候显示;所以如果我们不每次都产生一个新的Toast对象(使用单例来处理)就不需要排队,也就能及时更新了。 ### 6 Android应用Activity、Dialog、PopWindow、Toast窗口显示机制总结 可以看见上面无论Acitivty、Dialog、PopWindow、Toast的实质其实都是如下接口提供的方法操作: ~~~ public interface ViewManager { public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); } ~~~ 整个应用各种窗口的显示都离不开这三个方法而已,只是token及type与Window是否共用的问题。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重劳动成果】
';

Android异步消息处理机制详解及源码分析

最后更新于:2022-04-01 09:53:16

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN。因为CSDN也支持MarkDown语法了,牛逼啊! 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 最近相对来说比较闲,加上养病,所以没事干就撸些自己之前的知识点为博客,方便自己也方便别人。 ### 1 背景 之所以选择这个知识点来分析有以下几个原因: 1. 逛GitHub时发现关注的isuss中有人不停的在讨论Android中的Looper , Handler , Message有什么关系。 1. 其实这个知识点对于Android初学者来说很常用,但是初学者可能前期一直处于会用不知其原理的阶段。 1. 这个知识点也是Android面试中一个高频问题。 基于以上几点也得拿出来分析分析,该篇博客从实例到源码完全进行了剖析(包含Handler、Message、MessageQueue、Looper、HandlerThread等源码),不同于网上很多只是分析局部的博客。 你可能在刚开始接触Android开发时就会知道如下问题: Android的UI时线程不安全的,如果在线程中更新UI会出现异常,导致程序崩溃;同时如果UI中做耗时操作又会导致臭名昭著的ANR异常。 为了解决如上这些问题,我们怎办呢?很简单,通常最经典常用的做法就是使用Android的异步消息机制实现即可(创建一个Message对象,使用Handler发送出去,然后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作)。所以说还是很有必要了解异步消息机制的Looper , Handler , Message等原理的。 如下开始一个示例使用,然后通过源码分析吧。 ### 2 示例展示 如下示例展示了UI Thread与Child Thread之间互相发送消息,同时在UI Thread与Child Thread中分别定义Handler。这样如果没有mCount的判断,这段程序会一直死循环打印下去。 ~~~ public class MainActivity extends ActionBarActivity { private int mCount = 0; private Handler mHandlerThr = null; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.d(null, ">>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what="+msg.what); //接收发送到UI线程的消息,然后向线程中的Handler发送msg 1。 mHandlerThr.sendEmptyMessage(1); mCount++; if (mCount >= 3) { //由于mHandlerThr是在Child Thread创建,Looper手动死循环阻塞,所以需要quit。 mHandlerThr.getLooper().quit(); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); } @Override protected void onStop() { super.onStop(); //删除所有call与msg mHandler.removeCallbacksAndMessages(null); } private void initData() { Log.d(null, ">>>>>>>>>>>>>UI# begin start thread!!!"); new Thread() { @Override public void run() { super.run(); Looper.prepare(); mHandlerThr = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.d(null, ">>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=" + msg.what); //接收发送到子线程的消息,然后向UI线程中的Handler发送msg 0。 mHandler.sendEmptyMessage(0); } }; Log.d(null, ">>>>>>>>>>>>>Child# begin start send msg!!!"); //Activity中启动Thread,在Thread结束前发送msg 0到UI Thread。 mHandler.sendEmptyMessage(0); Looper.loop(); //不能在这个后面添加代码,程序是无法运行到这行之后的。 } }.start(); } } ~~~ 运行结果展示如下: ~~~ >>>>>>>>>>>>>UI# begin start thread!!! >>>>>>>>>>>>>Child# begin start send msg!!! >>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0 >>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=1 >>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0 >>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=1 >>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0 ~~~ 怎么样,这和你平时用的Handler一样吧,对于Handler异步处理的简单基础示例先说到这,接下来依据上面示例的写法分析原因与源代码原理。 ### 3 分析Android 5.1.1(API 22)异步消息机制源码 #### 3-1 看看Handler的实例化过程源码 #### 3-1-1 Handler实例化源码 从哪着手分析呢?当然是实例化构造函数呀,所以我们先从Handler的默认构造函数开始分析,如下: ~~~ /** * Default constructor associates this handler with the {@link Looper} for the * current thread. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. */ public Handler() { this(null, false); } ~~~ 通过注释也能看到,默认构造函数没有参数,而且调运了带有两个参数的其他构造函数,第一个参数传递为null,第二个传递为false。 这个构造函数得到的Handler默认属于当前线程,而且如果当前线程如果没有Looper则通过这个默认构造实例化Handler时会抛出异常,至于是啥异常还有为啥咱们继续往下分析,this(null, false)的实现如下: ~~~ public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } ~~~ 可以看到,在第11行调用了`mLooper = Looper.myLooper();`语句,然后获取了一个Looper对象mLooper ,如果mLooper实例为空,则会抛出一个运行 时异常(Can’t create handler inside thread that has not called Looper.prepare()!)。 #### 3-1-2 Looper实例化源码 好奇的你指定在想什么时候mLooper 对象才可能为空呢?很简单,跳进去看下吧,Looper类的静态方法myLooper如下: ~~~ /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); } ~~~ 咦?这里好简单。单单就是从sThreadLocal对象中get了一个Looper对象返回。跟踪了一下sThreadLocal对象,发现他定义在Looper中,是一个static  final类型的`ThreadLocal<Looper>`对象(在Java中,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要 访问的,也访问不到的,各个线程中访问的是不同的对象。)。所以可以看出,如果sThreadLocal中有Looper存在就返回Looper,没有Looper存在 自然就返回null了。 这时候你一定有疑惑,既然这里是通过sThreadLocal的get获得Looper,那指定有地方对sThreadLocal进行set操作吧?是的,我们在Looper类中跟踪发现如下: ~~~ private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } ~~~ 看着这个Looper的static方法prepare没有?这段代码首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。 那就看下Looper的实例化,如下: ~~~ private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } ~~~ 可以看见Looper构造函数无非就是创建了一个MessageQueue(它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出 队的方法。)和货到当前Thread实例引用而已。通过这里可以发现,一个Looper只能对应了一个MessageQueue。 你可能会说上面的例子在子线程中明明先调运的是Looper.prepare();方法,这里怎么有参数了?那就继续看吧,如下: ~~~ public static void prepare() { prepare(true); } ~~~ 可以看见,prepare()仅仅是对prepare(boolean quitAllowed) 的封装而已,默认传入了true,也就是将MessageQueue对象中的quitAllowed标记标 记为true而已,至于MessageQueue后面会分析。 稀奇古怪的事情来了!如果你足够留意上面的例子,你会发现我们在UI Thread中创建Handler时没有调用Looper.prepare();,而在initData方法中创建的Child Thread中首先就调运了Looper.prepare();。你指定很奇怪吧?UI Thread为啥不需要呢?上面源码分析明明需要先保证mLooper对象不为null呀? 这是由于在UI线程(Activity等)启动的时候系统已经帮我们自动调用了Looper.prepare()方法。 那么在哪启动的呢?这个涉及Android系统架构问题比较多,后面文章会分析Activity的启动流程。这里你只要知道,以前一直都说Activity的人口是onCreate方法,其实android上一个应用的入口应该是ActivityThread类的main方法就行了。 所以为了解开UI Thread为何不需要创建Looper对象的原因,我们看下ActivityThread的main方法,如下: ~~~ public static void main(String[] args) { SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } ~~~ 看见22行没?没说错吧?`Looper.prepareMainLooper();`,我们跳到Looper看下prepareMainLooper方法,如下: ~~~ public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } ~~~ 可以看到,UI线程中会始终存在一个Looper对象(sMainLooper 保存在Looper类中,UI线程通过getMainLooper方法获取UI线程的Looper对象), 从而不需要再手动去调用Looper.prepare()方法了。如下Looper类提供的get方法: ~~~ public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } } ~~~ 看见没有,到这里整个Handler实例化与为何子线程在实例化Handler之前需要先调运Looper.prepare();语句的原理分析完毕。 #### 3-1-3 Handler与Looper实例化总结 到此先初步总结下上面关于Handler实例化的一些关键信息,具体如下: 1. 在主线程中可以直接创建Handler对象,而在子线程中需要先调用Looper.prepare()才能创建Handler对象,否则运行抛出”Can’t create handler inside thread that has not called Looper.prepare()”异常信息。 1. 每个线程中最多只能有一个Looper对象,否则抛出异常。 1. 可以通过Looper.myLooper()获取当前线程的Looper实例,通过Looper.getMainLooper()获取主(UI)线程的Looper实例。 1. 一个Looper只能对应了一个MessageQueue。 1. 一个线程中只有一个Looper实例,一个MessageQueue实例,可以有多个Handler实例。 Handler对象也创建好了,接下来就该用了吧,所以下面咱们从Handler的收发消息角度来分析分析源码。 #### 3-2 继续看看Handler消息收发机制源码 #### 3-2-1 通过Handler发消息到消息队列 还记得上面的例子吗?我们在Child Thread的最后通过主线程的Handler对象调运sendEmptyMessage方法发送出去了一条消息。 当然,其实Handler类提供了许发送消息的方法,我们这个例子只是用了最简单的发送一个empty消息而已,有时候我们会先定义一个Message,然后通过Handler提供的其他方法进行发送。通过分析Handler源码发现Handler中提供的很多个发送消息方法中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都调用了sendMessageAtTime()方法。所以,咱们先来看下这个sendMessageAtTime方法,如下: ~~~ public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } ~~~ 再看下Handler中和其他发送方法不同的sendMessageAtFrontOfQueue方法,如下: ~~~ public final boolean sendMessageAtFrontOfQueue(Message msg) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, 0); } ~~~ 对比上面两个方法可以发现,表面上说Handler的sendMessageAtFrontOfQueue方法和其他发送方法不同,其实实质是相同的,仅仅是 sendMessageAtFrontOfQueue方法是sendMessageAtTime方法的一个特例而已(sendMessageAtTime最后一个参数传递0就变为了 sendMessageAtFrontOfQueue方法)。所以咱们现在继续分析sendMessageAtTime方法,如下分析: sendMessageAtTime(Message msg, long uptimeMillis)方法有两个参数;msg是我们发送的Message对象,uptimeMillis表示发送消息的时间,uptimeMillis的值等于从系统开机到当前时间的毫秒数再加上延迟时间。 在该方法的第二行可以看到queue = mQueue,而mQueue是在Handler实例化时构造函数中实例化的。在Handler的构造函数中可以看见mQueue = mLooper.mQueue;,而Looper的mQueue对象上面分析过了,是在Looper的构造函数中创建的一个MessageQueue。 接着第9行可以看到,上面说的两个参数和刚刚得到的queue对象都传递到了enqueueMessage(queue, msg, uptimeMillis)方法中,那就看下这个方法吧,如下: ~~~ private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } ~~~ 这个方法首先将我们要发送的消息Message的target属性设置为当前Handler对象(进行关联);接着将msg与uptimeMillis这两个参数都传递到 MessageQueue(消息队列)的enqueueMessage()方法中,所以接下来我们就继续分析MessageQueue类的enqueueMessage方法,如下: ~~~ boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } ~~~ 通过这个方法名可以看出来通过Handler发送消息实质就是把消息Message添加到MessageQueue消息队列中的过程而已。 通过上面遍历等next操作可以看出来,MessageQueue消息队列对于消息排队是通过类似c语言的链表来存储这些有序的消息的。其中的mMessages对象表示当前待处理的消息;然后18到49行可以看出,消息插入队列的实质就是将所有的消息按时间(uptimeMillis参数,上面有介绍)进行排序。所以还记得上面sendMessageAtFrontOfQueue方法吗?它的实质就是把消息添加到MessageQueue消息队列的头部(uptimeMillis为0,上面有分析)。 到此Handler的发送消息及发送的消息如何存入到MessageQueue消息队列的逻辑分析完成。 那么问题来了!既然消息都存入到了MessageQueue消息队列,当然要取出来消息吧,不然存半天有啥意义呢?我们知道MessageQueue的对象在Looper构造函数中实例化的;一个Looper对应一个MessageQueue,所以说Handler发送消息是通过Handler构造函数里拿到的Looper对象的成员MessageQueue的enqueueMessage方法将消息插入队列,也就是说出队列一定也与Handler和Looper和MessageQueue有关系。 还记不记得上面实例部分中Child Thread最后调运的Looper.loop();方法呢?这个方法其实就是取出MessageQueue消息队列里的消息方法。具体在下面分析。 #### 3-2-2 通过Handler接收发送的消息 先来看下上面例子中Looper.loop();这行代码调运的方法,如下: ~~~ /** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } } ~~~ 可以看到,第6行首先得到了当前线程的Looper对象me,接着第10行通过当前Looper对象得到与Looper对象一一对应的MessageQueue消息队列 也就类似上面发送消息部分,Handler通过myLoop方法得到Looper对象,然后获取Looper的MessageQueue消息队列对象)。17行进入一个死循环 ,18行不断地调用MessageQueue的next()方法,进入MessageQueue这个类查看next方法,如下: ~~~ Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (false) Log.v("MessageQueue", "Returning message: " + msg); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf("MessageQueue", "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } } ~~~ 可以看出来,这个next方法就是消息队列的出队方法(与上面分析的MessageQueue消息队列的enqueueMessage方法对比)。可以看见上面代码就 是如果当前MessageQueue中存在待处理的消息mMessages就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态(在 上面Looper类的loop方法上面也有英文注释,明确说到了阻塞特性),一直等到有新的消息入队。 继续看loop()方法的第30行(msg.target.dispatchMessage(msg);),每当有一个消息出队就将它传递到msg.target的dispatchMessage()方法中。其中这个msg.target其实就是上面分析Handler发送消息代码部分Handler的enqueueMessage方法中的msg.target = this;语句,也就是当前Handler对象。所以接下来的重点自然就是回到Handler类看看我们熟悉的dispatchMessage()方法,如下: ~~~ /** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } ~~~ 可以看见dispatchMessage方法中的逻辑比较简单,具体就是如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用 Handler的handleMessage()方法,并将消息对象作为参数传递过去。 这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧! 对了,既然上面说了获取消息在MessageQueue消息队列中是一个死循环的阻塞等待,所以Looper的quit方法也很重要,这样在不需要时可以退出这个死循环,如上面实例部分使用所示。 #### 3-2-3 结束MessageQueue消息队列阻塞死循环源码分析 如下展示了Looper类的quit方法源码: ~~~ public void quit() { mQueue.quit(false); } ~~~ 看见没有,quit方法实质就是调运了MessageQueue消息队列的quit,如下: ~~~ void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } } ~~~ 看见上面2到4行代码没有,通过判断标记mQuitAllowed来决定该消息队列是否可以退出,然而当mQuitAllowed为fasle时抛出的异常竟然是” Main thread not allowed to quit.”,Main Thread,所以可以说明Main Thread关联的Looper一一对应的MessageQueue消息队列是不能通过该方法退出的。 你可能会疑惑这个mQuitAllowed在哪设置的? 其实他是MessageQueue构造函数传递参数传入的,而MessageQueue对象的实例化是在Looper的构造函数实现的,所以不难发现mQuitAllowed参数实质是从Looper的构函数传入的。上面实例化Handler模块源码分析时说过,Looper实例化是在Looper的静态方法prepare(boolean quitAllowed)中处理的,也就是说mQuitAllowed是由Looper.prpeare(boolean quitAllowed)参数传入的。追根到底说明mQuitAllowed就是Looper.prpeare的参数,我们默认调运的Looper.prpeare();其中对mQuitAllowed设置为了true,所以可以通过quit方法退出,而主线程ActivityThread的main中使用的是Looper.prepareMainLooper();,这个方法里对mQuitAllowed设置为false,所以才会有上面说的”Main thread not allowed to quit.”。 回到quit方法继续看,可以发现实质就是对mQuitting标记置位,这个mQuitting标记在MessageQueue的阻塞等待next方法中用做了判断条件,所以可以通过quit方法退出整个当前线程的loop循环。 到此整个Android的一次完整异步消息机制分析使用流程结束。接下来进行一些总结提升与拓展。 #### 3-3 简单小结下Handler整个使用过程与原理 通过上面的源码分析原理我们可以总结出整个异步消息处理流程的关系图如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da799b791.jpg "") 这幅图很明显的表达出了Handler异步机制的来龙去脉,不做过多解释。 上面实例部分我们只是演示了Handler的局部方法,具体Handler还有很多方法,下面详细介绍。 #### 3-4 再来看看Handler源码的其他常用方法 在上面例子中我们只是演示了发送消息的sendEmptyMessage(int what)方法,其实Handler有如下一些发送方式: `sendMessage(Message msg);` `sendEmptyMessage(int what);` `sendEmptyMessageDelayed(int what, long delayMillis);``sendEmptyMessageAtTime(int what, long uptimeMillis);` `sendMessageDelayed(Message msg, long delayMillis);``sendMessageAtTime(Message msg, long uptimeMillis);` `sendMessageAtFrontOfQueue(Message msg);`方法。 这些方法不再做过多解释,用法雷同,顶一个Message决定啥时发送到target去。 `post(Runnable r);` `postDelayed(Runnable r, long delayMillis);`等post系列方法。 该方法实质源码其实就是如下: ~~~ public final boolean postDelayed(Runnable r, long delayMillis) { return sendMessageDelayed(getPostMessage(r), delayMillis); } ~~~ 额,原来post方法的实质也是调运sendMessageDelayed()方法去处理的额,看见getPostMessage(r)方法没?如下源码: ~~~ private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } ~~~ 如上方法仅仅是将消息的callback字段指定为传入的Runnable对象r。其实这个Message对象的m.callback就是上面分析Handler接收消息回调处理 dispatchMessage()方法中调运的。在Handler的dispatchMessage方法中首先判断如果Message的callback等于null才会去调用handleMessage()方法 ,否则就调用handleCallback()方法。那就再看下Handler的handleCallback()方法源码,如下: ~~~ private static void handleCallback(Message message) { message.callback.run(); } ~~~ 额,这里竟然直接执行了Runnable对象的run()方法。所以说我们在Runnable对象的run()方法里更新UI的效果完全和在handleMessage()方法中更新UI 相同,特别强调这个Runnable的run方法还在当前线程中阻塞执行,没有创建新的线程(很多人以为是Runnable就创建了新线程)。 `Activity.runOnUiThread(Runnable);`方法。 首先看下Activity的runOnUiThread方法源码: ~~~ public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } } ~~~ 看见没有,实质还是在UI线程中执行了Runnable的run方法。不做过多解释。 `View.post(Runnable);和View.postDelayed(Runnable action, long delayMillis);`方法。 首先看下View的postDelayed方法源码: ~~~ public boolean postDelayed(Runnable action, long delayMillis) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.postDelayed(action, delayMillis); } // Assume that post will succeed later ViewRootImpl.getRunQueue().postDelayed(action, delayMillis); return true; } ~~~ 看见没有,实质还是在UI线程中执行了Runnable的run方法。不做过多解释。 到此基本上关于Handler的所有发送消息方式都被解释明白了。既然会用了基本的那就得提高下,接下来看看关于Message的一点优化技巧。 #### 3-5 关于Handler发送消息的一点优化分析 还记得我们说过,当发送一个消息时我们首先会new一个Message对象,然后再发送吗?你是不是觉得每次new Message很浪费呢?那么我们就来分析一下这个问题。 如下是我们正常发送消息的代码局部片段: ~~~ Message message = new Message(); message.arg1 = 110; message.arg2 = 119; message.what = 0x120; message.obj = "Test Message Content!"; mHandler.sendMessage(message); ~~~ 相信很多初学者都是这么发送消息的吧?当有大量多次发送时如上写法会不太高效。不卖关子,先直接看达到同样效果的优化写法,如下: ~~~ mHandler.sendMessage(mHandler.obtainMessage(0x120, 110, 119, "\"Test Message Content!\"")); ~~~ 咦?怎么send时没实例化Message?这是咋回事?我们看下`mHandler.obtainMessage(0x120, 110, 119, "\"Test Message Content!\"")`这一段代码, obtainMessage是Handler提供的一个方法,看下源码: ~~~ public final Message obtainMessage(int what, int arg1, int arg2, Object obj) { return Message.obtain(this, what, arg1, arg2, obj); } ~~~ 这方法竟然直接调运了Message类的静态方法obtain,我们再去看看obtain的源码,如下: ~~~ public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj) { Message m = obtain(); m.target = h; m.what = what; m.arg1 = arg1; m.arg2 = arg2; m.obj = obj; return m; } ~~~ 看见没有?首先又调运一个无参的obtain方法,然后设置Message各种参数后返回。我们继续看下这个无参方法,如下: ~~~ /** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); } ~~~ 真相大白了!看见注释没有?从整个Messge池中返回一个新的Message实例,在许多情况下使用它,因为它能避免分配新的对象。 所以看见这两种获取Message写法的优缺点没有呢?明显可以看见通过调用Handler的obtainMessage方法获取Message对象就能避免创建对象,从而减少内存的开销了。所以推荐这种写法!!! #### 3-6 关于Handler导致内存泄露的分析与解决方法 正如上面我们实例部分的代码,使用Android Lint会提示我们这样一个Warning,如下: ~~~ In Android, Handler classes should be static or leaks might occur. ~~~ 意思是说在Android中Handler类应该是静态的否则可能发生泄漏。 啥是内存泄漏呢? Java通过GC自动检查内存中的对象,如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。本该被回收的对象没被回收就是内存泄漏。 Handler中怎么泄漏的呢? 当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。而Handler通常会伴随着一个耗时的后台线程一起出现,这个后台线程在任务执行完毕之后,通过消息机制通知Handler,然后Handler把消息发送到UI线程。然而,如果用户在耗时线程执行过程中关闭了Activity(正常情况下Activity不再被使用,它就有可能在GC检查时被回收掉),由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity暂时无法被回收(即内存泄露)。 Handler内存泄漏解决方案呢? 方案一:通过程序逻辑来进行保护 1. 在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。 1. 如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了(如上面的例子部分的onStop中代码)。 方案二:将Handler声明为静态类 静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下: ~~~ static class TestHandler extends Handler { @Override public void handleMessage(Message msg) { mImageView.setImageBitmap(mBitmap); } } ~~~ 这时你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个 对Activity的弱引用(WeakReference),如下: ~~~ static class TestHandler extends Handler { WeakReference<Activity > mActivityReference; TestHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); } } } ~~~ 如上就是关于Handler内存泄漏的分析及解决方案。 可能在你会用了Handler之后见过HandlerThread这个关键字,那我们接下来就看看HandlerThread吧。 ### 4 关于Android异步消息处理机制进阶的HandlerThread源码分析 #### 4-1 Android 5.1.1(API 22) HandlerThread源码 很多人在会使用Handler以后会发现有些代码里出现了HandlerThread,然后就分不清HandlerThread与Handler啥关系,咋回事之类的。这里就来分析分析HandlerThread的源码。如下: ~~~ public class HandlerThread extends Thread { //线程的优先级 int mPriority; //线程的id int mTid = -1; //一个与Handler关联的Looper对象 Looper mLooper; public HandlerThread(String name) { super(name); //设置优先级为默认线程 mPriority = android.os.Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread(String name, int priority) { super(name); mPriority = priority; } //可重写方法,Looper.loop之前在线程中需要处理的其他逻辑在这里实现 protected void onLooperPrepared() { } //HandlerThread线程的run方法 @Override public void run() { //获取当前线程的id mTid = Process.myTid(); //创建Looper对象 //这就是为什么我们要在调用线程的start()方法后才能得到Looper(Looper.myLooper不为Null) Looper.prepare(); //同步代码块,当获得mLooper对象后,唤醒所有线程 synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } //设置线程优先级 Process.setThreadPriority(mPriority); //Looper.loop之前在线程中需要处理的其他逻辑 onLooperPrepared(); //建立了消息循环 Looper.loop(); //一般执行不到这句,除非quit消息队列 mTid = -1; } public Looper getLooper() { if (!isAlive()) { //线程死了 return null; } //同步代码块,正好和上面run方法中同步块对应 //只要线程活着并且mLooper为null,则一直等待 // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; } public boolean quit() { Looper looper = getLooper(); if (looper != null) { //退出消息循环 looper.quit(); return true; } return false; } public boolean quitSafely() { Looper looper = getLooper(); if (looper != null) { //退出消息循环 looper.quitSafely(); return true; } return false; } public int getThreadId() { //返回线程id return mTid; } } ~~~ 看见没有,这就是HandlerThread的系统源码,整个HandlerThread类很简单。如上对重点都进行了注释。 现在可以很负责的告诉你Handler到底与HandlerThread啥关系,其实HandlerThread就是Thread、Looper和Handler的组合实现,Android系统这么封装体现了Android系统组件的思想,同时也方便了开发者开发。 上面源码可以看到,HandlerThread主要是对Looper进行初始化,并提供一个Looper对象给新创建的Handler对象,使得Handler处理消息事件在子线程中处理。这样就发挥了Handler的优势,同时又可以很好的和线程结合到一起。 到此HandlerThread源码原理也分析完了,那么就差实战了,如下继续。 #### 4-2 Android HandlerThread实战 上面分析了关于HandlerThread源码,下面就来演示一个实例,如下: ~~~ public class ListenerActivity extends Activity { private HandlerThread mHandlerThread = null; private Handler mThreadHandler = null; private Handler mUiHandler = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initData(); } private void initData() { Log.i(null, "Main Thread id="+Thread.currentThread().getId()); mHandlerThread = new HandlerThread("HandlerWorkThread"); //必须在实例化mThreadHandler之前调运start方法,原因上面源码已经分析了 mHandlerThread.start(); //将当前mHandlerThread子线程的Looper传入mThreadHandler,使得 //mThreadHandler的消息队列依赖于子线程(在子线程中执行) mThreadHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.i(null, "在子线程中处理!id="+Thread.currentThread().getId()); //从子线程往主线程发送消息 mUiHandler.sendEmptyMessage(0); } }; mUiHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.i(null, "在UI主线程中处理!id="+Thread.currentThread().getId()); } }; //从主线程往子线程发送消息 mThreadHandler.sendEmptyMessage(1); } } ~~~ 运行结果如下: ~~~ Main Thread id=1 在子线程中处理!id=113 在UI主线程中处理!id=1 ~~~ 好了,不做过多解释了,很简单的。 ### 5 关于Android异步消息处理机制总结 到此整个Android的异步处理机制Handler与HandlerThread等分析完成(关于Android的另一种异步处理机制AsyncTask后面有时间再分析)。相信通过这一篇文章你对Android的Handler使用还是原理都会有一个质的飞跃。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】
';

Android应用Context详解及源码解析

最后更新于:2022-04-01 09:53:13

【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 1 背景 今天突然想起之前在上家公司(做TV与BOX盒子)时有好几个人问过我关于Android的Context到底是啥的问题,所以就马上要诞生这篇文章。我们平时在开发App应用程序时一直都在使用Context(别说你没用过,访问当前应用的资源、启动一个activity等都用到了Context),但是很少有人关注过这玩意到底是啥,也很少有人知道getApplication与getApplicationContext方法有啥区别,以及一个App到底有多少个Context等等的细节。 更为致命的是Context使用不当还会造成内存泄漏。所以说完全有必要拿出来单独分析分析(基于Android 5.1.1 (API 22)源码分析)。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 2 Context基本信息 #### 2-1 Context概念 先看下源码Context类基本情况,如下: ~~~ /** * Interface to global information about an application environment. This is * an abstract class whose implementation is provided by * the Android system. It * allows access to application-specific resources and classes, as well as * up-calls for application-level operations such as launching activities, * broadcasting and receiving intents, etc. */ public abstract class Context { ...... } ~~~ 从源码注释可以看见,Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的 资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。 看见上面的Class OverView了吗?翻译就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。 既然上面Context是一个抽象类,那么肯定有他的实现类咯,我们在Context的源码中通过IDE可以查看到他的子类如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7970f2f.jpg "") 吓尿了,737个子类,经过粗略浏览这些子类名字和查阅资料发现,这些子类无非就下面一些主要的继承关系。这737个类都是如下关系图的直接或者间接子类而已。如下是主要的继承关系: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da798370c.jpg "") 从这里可以发现,Service和Application的类继承类似,Activity继承ContextThemeWrapper。这是因为Activity有主题(Activity提供UI显示,所以需要主题),而Service是没有界面的服务。 所以说,我们从这张主要关系图入手来分析Context相关源码。 #### 2-2 Context之间关系源码概述 有了上述通过IDE查看的大致关系和图谱之后我们在源码中来仔细看下这些继承关系。 先来看下Context类源码注释: ~~~ /** * Interface to global information about an application environment. This is * an abstract class whose implementation is provided by * the Android system. It * allows access to application-specific resources and classes, as well as * up-calls for application-level operations such as launching activities, * broadcasting and receiving intents, etc. */ public abstract class Context { ...... } ~~~ 看见没有,抽象类Context ,提供了一组通用的API。 再来看看Context的实现类ContextImpl源码注释: ~~~ /** * Common implementation of Context API, which provides the base * context object for Activity and other application components. */ class ContextImpl extends Context { private Context mOuterContext; ...... } ~~~ 该类实现了Context类的所有功能。 再来看看Context的包装类ContextWrapper源码注释: ~~~ /** * Proxying implementation of Context that simply delegates all of its calls to * another Context. Can be subclassed to modify behavior without changing * the original Context. */ public class ContextWrapper extends Context { Context mBase; public ContextWrapper(Context base) { mBase = base; } /** * Set the base context for this ContextWrapper. All calls will then be * delegated to the base context. Throws * IllegalStateException if a base context has already been set. * * @param base The new base context for this wrapper. */ protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } ...... } ~~~ 该类的构造函数包含了一个真正的Context引用(ContextImpl对象),然后就变成了ContextImpl的装饰着模式。 再来看看ContextWrapper的子类ContextThemeWrapper源码注释: ~~~ /** * A ContextWrapper that allows you to modify the theme from what is in the * wrapped context. */ public class ContextThemeWrapper extends ContextWrapper { ...... } ~~~ 该类内部包含了主题Theme相关的接口,即android:theme属性指定的。 再来看看Activity、Service、Application类的继承关系源码: ~~~ public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback { ...... } ~~~ ~~~ public abstract class Service extends ContextWrapper implements ComponentCallbacks2 { ...... } ~~~ ~~~ public class Application extends ContextWrapper implements ComponentCallbacks2 { ...... } ~~~ 看见没有?他们完全符合上面我们绘制的结构图与概述。 #### 2-3 解决应用Context个数疑惑 有了上面的Context继承关系验证与分析之后我们来看下一个应用程序到底有多个Context? Android应用程序只有四大组件,而其中两大组件都继承自Context,另外每个应用程序还有一个全局的Application对象。所以在我们了解了上面继承关系之后我们就可以计算出来Context总数,如下: ~~~ APP Context总数 = Application数(1) + Activity数(Customer) + Service数(Customer); ~~~ 到此,我们也明确了Context是啥,继承关系是啥样,应用中Context个数是多少的问题。接下来就有必要继续深入分析这些Context都是怎么来的。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 3 各种Context在ActivityThread中实例化过程源码分析 在开始分析之前还是和[《Android异步消息处理机制详解及源码分析》](http://blog.csdn.net/yanbober/article/details/45936145)的3-1-2小节及[《Android应用setContentView与LayoutInflater加载解析机制源码分析》](http://blog.csdn.net/yanbober/article/details/45970721)的2-6小节一样直接先给出关于Activity启动的一些概念,后面会写文章分析这一过程。 Context的实现是ContextImpl,Activity与Application和Service的创建都是在ActivityThread中完成的,至于在ActivityThread何时、怎样调运的关系后面会写文章分析,这里先直接给出结论,因为我们分析的重点是Context过程。 #### 3-1 Activity中ContextImpl实例化源码分析 通过startActivity启动一个新的Activity时系统会回调ActivityThread的handleLaunchActivity()方法,该方法内部会调用performLaunchActivity()方法去创建一个Activity实例,然后回调Activity的onCreate()等方法。所以Activity的ContextImpl实例化是在ActivityThread类的performLaunchActivity方法中,如下: ~~~ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... //已经创建好新的activity实例 if (activity != null) { //创建一个Context对象 Context appContext = createBaseContextForActivity(r, activity); ...... //将上面创建的appContext传入到activity的attach方法 activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor); ...... } ...... return activity; } ~~~ 看见上面performLaunchActivity的核心代码了吗?通过`createBaseContextForActivity(r, activity);`创建appContext,然后通过activity.attach设置值。 具体我们先看下createBaseContextForActivity方法源码,如下: ~~~ private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) { //实质就是new一个ContextImpl对象,调运ContextImpl的有参构造初始化一些参数 ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token); //特别特别留意这里!!! //ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Activity对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Activity)。 appContext.setOuterContext(activity); //创建返回值并且赋值 Context baseContext = appContext; ...... //返回ContextImpl对象 return baseContext; } ~~~ 再来看看activity.attach,也就是Activity中的attach方法,如下: ~~~ final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor) { //特别特别留意这里!!! //与上面createBaseContextForActivity方法中setOuterContext语句类似,不同的在于: //通过ContextThemeWrapper类的attachBaseContext方法,将createBaseContextForActivity中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImpl attachBaseContext(context); ...... } ~~~ 通过上面Activity的Context实例化分析再结合上面Context继承关系可以看出: Activity通过ContextWrapper的成员mBase来引用了一个ContextImpl对象,这样,Activity组件以后就可以通过这个ContextImpl对象来执行一些具体的操作(启动Service等);同时ContextImpl类又通过自己的成员mOuterContext引用了与它关联的Activity,这样ContextImpl类也可以操作Activity。 SO,由此说明一个Activity就有一个Context,而且生命周期和Activity类相同(记住这句话,写应用就可以避免一些低级的内存泄漏问题)。 #### 3-2 Service中ContextImpl实例化源码分析 写APP时我们通过startService或者bindService方法创建一个新Service时就会回调ActivityThread类的handleCreateService()方法完成相关数据操作(具体关于ActivityThread调运handleCreateService时机等细节分析与上面Activity雷同,后边文章会做分析)。具体handleCreateService方法代码如下: ~~~ private void handleCreateService(CreateServiceData data) { ...... //类似上面Activity的创建,这里创建service对象实例 Service service = null; try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); service = (Service) cl.loadClass(data.info.name).newInstance(); } catch (Exception e) { ...... } try { ...... //不做过多解释,创建一个Context对象 ContextImpl context = ContextImpl.createAppContext(this, packageInfo); //特别特别留意这里!!! //ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Service对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Service)。 context.setOuterContext(service); Application app = packageInfo.makeApplication(false, mInstrumentation); //将上面创建的context传入到service的attach方法 service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); service.onCreate(); ...... } catch (Exception e) { ...... } } ~~~ 再来看看service.attach,也就是Service中的attach方法,如下: ~~~ public final void attach( Context context, ActivityThread thread, String className, IBinder token, Application application, Object activityManager) { //特别特别留意这里!!! //与上面handleCreateService方法中setOuterContext语句类似,不同的在于: //通过ContextWrapper类的attachBaseContext方法,将handleCreateService中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImpl attachBaseContext(context); ...... } ~~~ 可以看出步骤流程和Activity的类似,只是实现细节略有不同而已。 SO,由此说明一个Service就有一个Context,而且生命周期和Service类相同(记住这句话,写应用就可以避免一些低级的内存泄漏问题)。 #### 3-3 Application中ContextImpl实例化源码分析 当我们写好一个APP以后每次重新启动时都会首先创建Application对象(每个APP都有一个唯一的全局Application对象,与整个APP的生命周期相同)。创建Application的过程也在ActivityThread类的handleBindApplication()方法完成相关数据操作(具体关于ActivityThread调运handleBindApplication时机等细节分析与上面Activity雷同,后边文章会做分析)。而ContextImpl的创建是在该方法中调运LoadedApk类的makeApplication方法中实现,LoadedApk类的makeApplication()方法中源代码如下: ~~~ public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { //只有新创建的APP才会走if代码块之后的剩余逻辑 if (mApplication != null) { return mApplication; } //即将创建的Application对象 Application app = null; String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } try { java.lang.ClassLoader cl = getClassLoader(); if (!mPackageName.equals("android")) { initializeJavaContextClassLoader(); } //不做过多解释,创建一个Context对象 ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); //将Context传入Instrumentation类的newApplication方法 app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); //特别特别留意这里!!! //ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Application对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Application)。 appContext.setOuterContext(app); } catch (Exception e) { ...... } ...... return app; } ~~~ 接着看看Instrumentation.newApplication方法。如下源码: ~~~ public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return newApplication(cl.loadClass(className), context); } ~~~ 继续看重载两个参数的newApplication方法,如下: ~~~ static public Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { ...... //继续传递context app.attach(context); return app; } ~~~ 继续看下Application类的attach方法,如下: ~~~ final void attach(Context context) { //特别特别留意这里!!! //与上面makeApplication方法中setOuterContext语句类似,不同的在于: //通过ContextWrapper类的attachBaseContext方法,将makeApplication中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Application的实现类ContextImpl attachBaseContext(context); ...... } ~~~ 可以看出步骤流程和Activity的类似,只是实现细节略有不同而已。 SO,由此说明一个Application就有一个Context,而且生命周期和Application类相同(然而一个App只有一个Application,而且与应用生命周期相同)。 ### 4 应用程序APP各种Context访问资源的唯一性分析 你可能会有疑问,这么多Context都是不同实例,那么我们平时写App时通过context.getResources得到资源是不是就不是同一份呢?下面我们从源码来分析下,如下: ~~~ class ContextImpl extends Context { ...... private final ResourcesManager mResourcesManager; private final Resources mResources; ...... @Override public Resources getResources() { return mResources; } ...... } ~~~ 看见没,有了上面分析我们可以很确定平时写的App中context.getResources方法获得的Resources对象就是上面ContextImpl的成员变量mResources。 那我们追踪可以发现mResources的赋值操作如下: ~~~ private ContextImpl(ContextImpl container, ActivityThread mainThread, LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted, Display display, Configuration overrideConfiguration) { ...... //单例模式获取ResourcesManager对象 mResourcesManager = ResourcesManager.getInstance(); ...... //packageInfo对于一个APP来说只有一个,所以resources 是同一份 Resources resources = packageInfo.getResources(mainThread); if (resources != null) { if (activityToken != null || displayId != Display.DEFAULT_DISPLAY || overrideConfiguration != null || (compatInfo != null && compatInfo.applicationScale != resources.getCompatibilityInfo().applicationScale)) { //mResourcesManager是单例,所以resources是同一份 resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(), packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, activityToken); } } //把resources赋值给mResources mResources = resources; ...... } ~~~ 由此可以看出在设备其他因素不变的情况下我们通过不同的Context实例得到的Resources是同一套资源。 PS一句,同样的分析方法也可以发现Context类的packageInfo对于一个应用来说也只有一份。感兴趣可以自行分析。 ### 5 应用程序APP各种Context使用区分源码分析 #### 5-1 先来解决getApplication和getApplicationContext的区别 很多人一直区分不开这两个方法的区别,这里从源码来分析一下,如下: 首先来看getApplication方法,你会发现Application与Context都没有提供该方法,这个方法是哪提供的呢?我们看下Activity与Service中的代码,可以发下如下: ~~~ public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback { ...... public final Application getApplication() { return mApplication; } ...... } ~~~ ~~~ public abstract class Service extends ContextWrapper implements ComponentCallbacks2 { ...... public final Application getApplication() { return mApplication; } ...... } ~~~ Activity和Service提供了getApplication,而且返回类型都是Application。这个mApplication都是在各自类的attach方法参数出入的,也就是说这个 mApplication都是在ActivityThread中各自实例化时获取的makeApplication方法返回值。 所以不同的Activity和Service返回的Application均为同一个全局对象。 再来看看getApplicationContext方法,如下: ~~~ class ContextImpl extends Context { ...... @Override public Context getApplicationContext() { return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication(); } ...... } ~~~ 可以看到getApplicationContext方法是Context的方法,而且返回值是Context类型,返回对象和上面通过Service或者Activity的getApplication返回 的是一个对象。所以说对于客户化的第三方应用来说两个方法返回值一样,只是返回值类型不同,还有就是依附的对象不同而已。 #### 5-2 各种获取Context方法的差异及开发要点提示 可以看出来,Application的Context生命周期与应用程序完全相同。  Activity或者Service的Context与他们各自类生命周期相同。 所以说对于Context使用不当会引起内存泄漏。 譬如一个单例模式的自定义数据库管理工具类需要传入一个Context,而这个数据库管理对象又需要在Activity中使用,如果我们传递Activity的Context就可能造成内存泄漏,所以需要传递Application的Context。 ### 6 Context分析总结 到此整个Android应用的Context疑惑就完全解开了,同时也依据源码分析结果给出了平时开发APP中该注意的内存泄漏问题提示与解决方案。相信通过这一篇你在开发APP时对于Context的使用将不再迷惑。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】
';

Android应用setContentView与LayoutInflater加载解析机制源码分析

最后更新于:2022-04-01 09:53:11

【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 1 背景 其实之所以要说这个话题有几个原因: 1. 理解xml等控件是咋被显示的原理,通常大家写代码都是直接在onCreate里setContentView就完事,没怎么关注其实现原理。 1. 前面分析[《Android触摸屏事件派发机制详解与源码分析三(Activity篇)》](http://blog.csdn.net/yanbober/article/details/45932123)时提到了一些关于布局嵌套的问题,当时没有深入解释。 所以接下来主要分析的就是View或者ViewGroup对象是如何添加至应用程序界面(窗口)显示的。我们准备从Activity的setContentView方法开始来说(因为默认Activity中放入我们的xml或者Java控件是通过setContentView方法来操作的,当调运了setContentView所有的控件就得到了显示)。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 2 Android5.1.1(API 22)从Activity的setContentView方法说起 #### 2-1 Activity的setContentView方法解析 Activity的源码中提供了三个重载的setContentView方法,如下: ~~~ public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } public void setContentView(View view) { getWindow().setContentView(view); initWindowDecorActionBar(); } public void setContentView(View view, ViewGroup.LayoutParams params) { getWindow().setContentView(view, params); initWindowDecorActionBar(); } ~~~ 可以看见他们都先调运了getWindow()的setContentView方法,然后调运Activity的initWindowDecorActionBar方法,关于 initWindowDecorActionBar方法后面准备写一篇关于Android ActionBar原理解析的文章,所以暂时跳过不解释。 #### 2-2 关于窗口Window类的一些关系 在开始分析Activity组合对象Window的setContentView方法之前请先明确如下关系(前面分析[《Android触摸屏事件派发机制详解与源码分析三(Activity篇)》](http://blog.csdn.net/yanbober/article/details/45932123)时也有说过)。 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7904526.jpg "") 看见上面图没?Activity中有一个成员为Window,其实例化对象为PhoneWindow,PhoneWindow为抽象Window类的实现类。 这里先简要说明下这些类的职责: 1. Window是一个抽象类,提供了绘制窗口的一组通用API。 1. PhoneWindow是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。 1. DecorView是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View 。 依据面向对象从抽象到具体我们可以类比上面关系就像如下: Window是一块电子屏,PhoneWindow是一块手机电子屏,DecorView就是电子屏要显示的内容,Activity就是手机电子屏安装位置。 #### 2-3 窗口PhoneWindow类的setContentView方法 我们可以看见Window类的setContentView方法都是抽象的。所以我们直接先看PhoneWindow类的setContentView(int layoutResID)方法源码,如下: ~~~ public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } ~~~ 可以看见,第五行首先判断mContentParent是否为null,也就是第一次调运);如果是第一次调用,则调用installDecor()方法,否则判断是否设置 FEATURE_CONTENT_TRANSITIONS Window属性(默认false),如果没有就移除该mContentParent内所有的所有子View;接着16行 `mLayoutInflater.inflate(layoutResID, mContentParent);`将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中 (其中mLayoutInflater是在PhoneWindow的构造函数中得到实例对象的`LayoutInflater.from(context);`)。 再来看下PhoneWindow类的setContentView(View view)方法和setContentView(View view, ViewGroup.LayoutParams params)方法源码,如下: ~~~ @Override public void setContentView(View view) { setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } @Override public void setContentView(View view, ViewGroup.LayoutParams params) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { view.setLayoutParams(params); final Scene newScene = new Scene(mContentParent, view); transitionTo(newScene); } else { mContentParent.addView(view, params); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } ~~~ 看见没有,我们其实只用分析setContentView(View view, ViewGroup.LayoutParams params)方法即可,如果你在Activity中调运 setContentView(View view)方法,实质也是调运setContentView(View view, ViewGroup.LayoutParams params),只是LayoutParams设置为了 MATCH_PARENT而已。 所以直接分析setContentView(View view, ViewGroup.LayoutParams params)方法就行,可以看见该方法与setContentView(int layoutResID)类似,只是少了LayoutInflater将xml文件解析装换为View而已,这里直接使用View的addView方法追加道了当前mContentParent而已。 所以说在我们的应用程序里可以多次调用setContentView()来显示界面,因为会removeAllViews。 #### 2-4 窗口PhoneWindow类的installDecor方法 回过头,我们继续看上面PhoneWindow类setContentView方法的第6行installDecor();代码,在PhoneWindow中查看installDecor源码如下: ~~~ private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null) { //根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent mContentParent = generateLayout(mDecor); //...... //初始化一堆属性值 } } ~~~ 我勒个去!又是一个死长的方法,抓重点分析吧。第2到9行可以看出,首先判断mDecor对象是否为空,如果为空则调用generateDecor()创建一个 DecorView(该类是 FrameLayout子类,即一个ViewGroup视图),然后设置一些属性,我们看下PhoneWindow的generateDecor方法,如下: ~~~ protected DecorView generateDecor() { return new DecorView(getContext(), -1); } ~~~ 可以看见generateDecor方法仅仅是new一个DecorView的实例。 回到installDecor方法继续往下看,第10行开始到方法结束都需要一个`if (mContentParent == null)`判断为真才会执行,当mContentParent对象不为空则调用generateLayout()方法去创建mContentParent对象。所以我们看下generateLayout方法源码,如下: ~~~ protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle(); //...... //依据主题style设置一堆值进行设置 // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); //...... //根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值 //把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值 View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } //...... //继续一堆属性设置,完事返回contentParent return contentParent; } ~~~ 可以看见上面方法主要作用就是根据窗口的风格修饰类型为该窗口选择不同的窗口根布局文件。mDecor做为根视图将该窗口根布局添加进去,然后获 取id为content的FrameLayout返回给mContentParent对象。所以installDecor方法实质就是产生mDecor和mContentParent对象。 在这里顺带提一下:还记得我们平时写应用Activity时设置的theme或者feature吗(全屏啥的,NoTitle等)?我们一般是不是通过XML的android:theme属性或者java的requestFeature()方法来设置的呢?譬如: ~~~ 通过java文件设置: requestWindowFeature(Window.FEATURE_NO_TITLE); 通过xml文件设置: android:theme="@android:style/Theme.NoTitleBar" ~~~ 对的,其实我们平时requestWindowFeature()设置的值就是在这里通过getLocalFeature()获取的;而android:theme属性也是通过这里的 getWindowStyle()获取的。所以这下你应该就明白在java文件设置Activity的属性时必须在setContentView方法之前调用requestFeature()方法的原因了吧。 我们继续关注一下generateLayout方法的layoutResource变量赋值情况。因为它最终通过`View in = mLayoutInflater.inflate(layoutResource, null);`和`decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));`将in添加到PhoneWindow的mDecor对象。为例验证这一段代码分析我们用一个实例来进行说明,如下是一个简单的App主要代码: AndroidManifest.xml文件 ~~~ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yanbober.myapplication" > <application ...... //看重点,我们将主题设置为NoTitleBar android:theme="@android:style/Theme.Black.NoTitleBar" > ...... </application> </manifest> ~~~ 主界面布局文件: ~~~ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> ~~~ APP运行界面:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da791859b.jpg "") 看见没有,上面我们将主题设置为NoTitleBar,所以在generateLayout方法中的layoutResource变量值为`R.layout.screen_simple`,所以我们看下系统这个screen_simple.xml布局文件,如下: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout> ~~~ 布局中,一般会包含ActionBar,Title,和一个id为content的FrameLayout,这个布局是NoTitle的。 再来看下上面这个App的hierarchyviewer图谱,如下: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da79299db.jpg "") 看见了吧,通过这个App的hierarchyviewer和系统screen_simple.xml文件比较就验证了上面我们分析的结论,不再做过多解释。 然后回过头可以看见上面PhoneWindow类的setContentView方法最后通过调运`mLayoutInflater.inflate(layoutResID, mContentParent);`或者`mContentParent.addView(view, params);`语句将我们的xml或者java View插入到了mContentParent(id为content的FrameLayout对象)ViewGroup中。最后setContentView还会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件视图内容发生了变化。 #### 2-5 Window类内部接口Callback的onContentChanged方法 上面刚刚说了PhoneWindow类的setContentView方法中最后调运了onContentChanged方法。我们这里看下setContentView这段代码,如下: ~~~ public void setContentView(int layoutResID) { ...... final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } ~~~ 看着没有,首先通过getCallback获取对象cb(回调接口),PhoneWindow没有重写Window的这个方法,所以到抽象类Window中可以看到: ~~~ /** * Return the current Callback interface for this window. */ public final Callback getCallback() { return mCallback; } ~~~ 这个mCallback在哪赋值的呢,继续看Window类发现有一个方法,如下: ~~~ public void setCallback(Callback callback) { mCallback = callback; } ~~~ Window中的mCallback是通过这个方法赋值的,那就回想一下,Window又是Activity的组合成员,那就是Activity一定调运这个方法了,回到Activity 发现在Activity的attach方法中进行了设置,如下: ~~~ final void attach(Context context, ActivityThread aThread, ...... mWindow.setCallback(this); ...... } ~~~ 也就是说Activity类实现了Window的Callback接口。那就是看下Activity实现的onContentChanged方法。如下: ~~~ public void onContentChanged() { } ~~~ 咦?onContentChanged是个空方法。那就说明当Activity的布局改动时,即setContentView()或者addContentView()方法执行完毕时就会调用该方法。 所以当我们写App时,Activity的各种View的findViewById()方法等都可以放到该方法中,系统会帮忙回调。 #### 2-6 setContentView源码分析总结 可以看出来setContentView整个过程主要是如何把Activity的布局文件或者java的View添加至窗口里,上面的过程可以重点概括为: 1. 创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。 1. 依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。 1. 将Activity的布局文件添加至id为content的FrameLayout内。 至此整个setContentView的主要流程就分析完毕。你可能这时会疑惑,这么设置完一堆View关系后系统是怎么知道该显示了呢?下面我们就初探一下关于Activity的setContentView在onCreate中如何显示的(声明一下,这里有些会暂时直接给出结论,该系列文章后面会详细分析的)。 #### 2-7 setContentView完以后Activity显示界面初探 这一小部分已经不属于sentContentView的分析范畴了,只是简单说明setContentView之后怎么被显示出来的(注意:Activity调运setContentView方法自身不会显示布局的)。 记得前面有一篇文章[《Android异步消息处理机制详解及源码分析》](http://blog.csdn.net/yanbober/article/details/45936145)的3-1-2小节说过,一个Activity的开始实际是ActivityThread的main方法(至于为什么后面会写文章分析,这里站在应用层角度先有这个概念就行)。 那在这一篇我们再直接说一个知识点(至于为什么后面会写文章分析,这里站在应用层角度先有这个概念就行)。 当启动Activity调运完ActivityThread的main方法之后,接着调用ActivityThread类performLaunchActivity来创建要启动的Activity组件,在创建Activity组件的过程中,还会为该Activity组件创建窗口对象和视图对象;接着Activity组件创建完成之后,通过调用ActivityThread类的handleResumeActivity将它激活。 所以我们先看下handleResumeActivity方法一个重点,如下: ~~~ final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. ...... // TODO Push resumeArgs into the activity for consideration ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { ...... // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. ...... // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. ...... // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { ...... if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } ...... } else { // If an exception was thrown when trying to resume, then // just end this activity. ...... } } ~~~ 看见`r.activity.makeVisible();`语句没?调用Activity的makeVisible方法显示我们上面通过setContentView创建的mDecor视图族。所以我们看下 Activity的makeVisible方法,如下: ~~~ void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); } ~~~ 看见没有,通过DecorView(FrameLayout,也即View)的setVisibility方法将View设置为VISIBLE,至此显示出来。 到此setContentView的完整流程分析完毕。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】 ### 3 Android5.1.1(API 22)看看LayoutInflater机制原理 上面在分析setContentView过程中可以看见,在PhoneWindow的setContentView中调运了`mLayoutInflater.inflate(layoutResID, mContentParent);`,在PhoneWindow的generateLayout中调运了`View in = mLayoutInflater.inflate(layoutResource, null);`,当时我们没有详细分析,只是告诉通过xml得到View对象。现在我们就来分析分析这一问题。 #### 3-1 通过实例引出问题 在开始之前我们先来做一个测试,我们平时最常见的就是ListView的Adapter中使用LayoutInflater加载xml的item布局文件,所以咱们就以ListView为例,如下: 省略掉Activity代码等,首先给出Activity的布局文件,如下: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android:id="@+id/listview" android:dividerHeight="5dp" android:layout_width="match_parent" android:layout_height="match_parent"></ListView> </LinearLayout> ~~~ 给出两种不同的ListView的item布局文件。 textview_layout.xml文件: ~~~ <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="100dp" android:layout_height="40dp" android:text="Text Test" android:background="#ffa0a00c"/> ~~~ textview_layout_parent.xml文件: ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:layout_height="wrap_content" android:layout_width="wrap_content" xmlns:android="http://schemas.android.com/apk/res/android"> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="100dp" android:layout_height="40dp" android:text="Text Test" android:background="#ffa0a00c"/> </LinearLayout> ~~~ ListView的自定义Adapter文件: ~~~ public class InflateAdapter extends BaseAdapter { private LayoutInflater mInflater = null; public InflateAdapter(Context context) { mInflater = LayoutInflater.from(context); } @Override public int getCount() { return 8; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { //说明:这里是测试inflate方法参数代码,不再考虑性能优化等TAG处理 return getXmlToView(convertView, position, parent); } private View getXmlToView(View convertView, int position, ViewGroup parent) { View[] viewList = { mInflater.inflate(R.layout.textview_layout, null), // mInflater.inflate(R.layout.textview_layout, parent), mInflater.inflate(R.layout.textview_layout, parent, false), // mInflater.inflate(R.layout.textview_layout, parent, true), mInflater.inflate(R.layout.textview_layout, null, true), mInflater.inflate(R.layout.textview_layout, null, false), mInflater.inflate(R.layout.textview_layout_parent, null), // mInflater.inflate(R.layout.textview_layout_parent, parent), mInflater.inflate(R.layout.textview_layout_parent, parent, false), // mInflater.inflate(R.layout.textview_layout_parent, parent, true), mInflater.inflate(R.layout.textview_layout_parent, null, true), mInflater.inflate(R.layout.textview_layout_parent, null, false), }; convertView = viewList[position]; return convertView; } } ~~~ 当前代码运行结果:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7946743.jpg "") PS:当打开上面viewList数组中任意一行注释都会抛出异常(java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView)。 你指定有些蒙圈了,而且比较郁闷,同时想弄明白inflate的这些参数都是啥意思。运行结果为何有这么大差异呢? 那我告诉你,你现在先别多想,记住这回事,咱们先看源码,下面会告诉你为啥。 #### 3-2 从LayoutInflater源码实例化说起 我们先看一下源码中LayoutInflater实例化获取的方法: ~~~ public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; } ~~~ 看见没有?是否很熟悉?我们平时写应用获取LayoutInflater实例时不也就两种写法吗,如下: ~~~ LayoutInflater lif = LayoutInflater.from(Context context); LayoutInflater lif = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); ~~~ 可以看见from方法仅仅是对getSystemService的一个安全封装而已。 #### 3-3 LayoutInflater源码的View inflate(…)方法族剖析 得到LayoutInflater对象之后我们就是传递xml然后解析得到View,如下方法: ~~~ public View inflate(int resource, ViewGroup root) { return inflate(resource, root, root != null); } ~~~ 继续看inflate(int resource, ViewGroup root, boolean attachToRoot)方法,如下: ~~~ public View inflate(int resource, ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } ~~~ 这个方法的第8行获取到XmlResourceParser接口的实例(Android默认实现类为Pull解析XmlPullParser)。接着看第10行`inflate(parser, root, attachToRoot);` ,你会发现无论哪个inflate重载方法最后都调运了`inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)`方法,如下: ~~~ public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; //定义返回值,初始化为传入的形参root View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } //如果一开始就是END_DOCUMENT,那说明xml文件有问题 if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } //有了上面判断说明这里type一定是START_TAG,也就是xml文件里的root node final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } if (TAG_MERGE.equals(name)) { //处理merge tag的情况(merge,你懂的,APP的xml性能优化) //root必须非空且attachToRoot为true,否则抛异常结束(APP使用merge时要注意的地方, //因为merge的xml并不代表某个具体的view,只是将它包起来的其他xml的内容加到某个上层 //ViewGroup中。) if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } //递归inflate方法调运 rInflate(parser, root, attrs, false, false); } else { // Temp is the root view that was found in the xml //xml文件中的root view,根据tag节点创建view对象 final View temp = createViewFromTag(root, name, attrs, false); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied //根据root生成合适的LayoutParams实例 params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) //如果attachToRoot=false就调用view的setLayoutParams方法 temp.setLayoutParams(params); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp //递归inflate剩下的children rInflate(parser, temp, attrs, true, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { //root非空且attachToRoot=true则将xml文件的root view加到形参提供的root里 root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { //返回xml里解析的root view result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } Trace.traceEnd(Trace.TRACE_TAG_VIEW); //返回参数root或xml文件里的root view return result; } } ~~~ 从上面的源码分析我们可以看出inflate方法的参数含义: - inflate(xmlId, null); 只创建temp的View,然后直接返回temp。 - inflate(xmlId, parent); 创建temp的View,然后执行root.addView(temp, params);最后返回root。 - inflate(xmlId, parent, false); 创建temp的View,然后执行temp.setLayoutParams(params);然后再返回temp。 - inflate(xmlId, parent, true); 创建temp的View,然后执行root.addView(temp, params);最后返回root。 - inflate(xmlId, null, false); 只创建temp的View,然后直接返回temp。 - inflate(xmlId, null, true); 只创建temp的View,然后直接返回temp。 到此其实已经可以说明我们上面示例部分执行效果差异的原因了(在此先强调一个Android的概念,下一篇文章我们会对这段话作一解释:我们经常使用View的layout_width和layout_height来设置View的大小,而且一般都可以正常工作,所以有人时常认为这两个属性就是设置View的真实大小一样;然而实际上这些属性是用于设置View在ViewGroup布局中的大小的;这就是为什么Google的工程师在变量命名上将这种属性叫作layout_width和layout_height,而不是width和height的原因了。),如下: - `mInflater.inflate(R.layout.textview_layout, null)`不能正确处理我们设置的宽和高是因为layout_width,layout_height是相对了父级设置的,而此temp的getLayoutParams为null。 - `mInflater.inflate(R.layout.textview_layout, parent)`能正确显示我们设置的宽高是因为我们的View在设置setLayoutParams时`params = root.generateLayoutParams(attrs)`不为空。  Inflate(resId , parent,false ) 可以正确处理,因为temp.setLayoutParams(params);这个params正是root.generateLayoutParams(attrs);得到的。 - `mInflater.inflate(R.layout.textview_layout, null, true)与mInflater.inflate(R.layout.textview_layout, null, false)`不能正确处理我们设置的宽和高是因为layout_width,layout_height是相对了父级设置的,而此temp的getLayoutParams为null。 - textview_layout_parent.xml作为item可以正确显示的原因是因为TextView具备上级ViewGroup,上级ViewGroup的layout_width,layout_height会失效,当前的TextView会有效而已。 - 上面例子中说放开那些注释运行会报错java.lang.UnsupportedOperationException:  addView(View, LayoutParams) is not supported是因为AdapterView源码中调用了root.addView(temp, params);而此时的root是我们的ListView,ListView为AdapterView的子类,所以我们看下AdapterView抽象类中addView源码即可明白为啥了,如下: ~~~ /** * This method is not supported and throws an UnsupportedOperationException when called. * * @param child Ignored. * * @throws UnsupportedOperationException Every time this method is invoked. */ @Override public void addView(View child) { throw new UnsupportedOperationException("addView(View) is not supported in AdapterView"); } ~~~ - 这里不再做过多解释。 咦?别急,到这里指定机智的人会问,我们在写App时Activity中指定布局文件的时候,xml布局文件或者我们用java编写的View最外层的那个布局是可以指定大小的啊?他们最外层的layout_width和layout_height都是有作用的啊? 是这样的,还记得我们上面的分析吗?我们自己的xml布局通过setContentView()方法放置到哪去了呢?记不记得id为content的FrameLayout呢?所以我们xml或者java的View的最外层布局的layout_width和layout_height属性才会有效果,就是这么回事而已。 #### 3-4 LayoutInflater源码inflate(…)方法中调运的一些非public方法剖析 看下inflate方法中被调运的rInflate方法,源码如下: ~~~ void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; //XmlPullParser解析器的标准解析模式 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { //找到START_TAG节点程序才继续执行这个判断语句之后的逻辑 if (type != XmlPullParser.START_TAG) { continue; } //获取Name标记 final String name = parser.getName(); //处理REQUEST_FOCUS的标记 if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { //处理tag标记 parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { //处理include标记 if (parser.getDepth() == 0) { //include节点如果是根节点就抛异常 throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs, inheritContext); } else if (TAG_MERGE.equals(name)) { //merge节点必须是xml文件里的根节点(这里不该再出现merge节点) throw new InflateException("<merge /> must be the root element"); } else { //其他自定义节点 final View view = createViewFromTag(parent, name, attrs, inheritContext); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true, true); viewGroup.addView(view, params); } } //parent的所有子节点都inflate完毕的时候回onFinishInflate方法 if (finishInflate) parent.onFinishInflate(); } ~~~ 可以看见,上面方法主要就是循环递归解析xml文件,解析结束回调View类的onFinishInflate方法,所以View类的onFinishInflate方法是一个空方法,如下: ~~~ /** * Finalize inflating a view from XML. This is called as the last phase * of inflation, after all child views have been added. * * <p>Even if the subclass overrides onFinishInflate, they should always be * sure to call the super method, so that we get called. */ protected void onFinishInflate() { } ~~~ 可以看见,当我们自定义View时在构造函数inflate一个xml后可以实现onFinishInflate这个方法一些自定义的逻辑。 至此LayoutInflater的源码核心部分已经分析完毕。 ### 4 从LayoutInflater与setContentView来说说应用布局文件的优化技巧 通过上面的源码分析可以发现,xml文件解析实质是递归控件,解析属性的过程。所以说嵌套过深不仅效率低下还可能引起调运栈溢出。同时在解析那些tag时也有一些特殊处理,从源码看编写xml还是有很多要注意的地方的。所以说对于Android的xml来说是有一些优化技巧的(PS:布局优化可以通过hierarchyviewer来查看,通过lint也可以自动检查出来一些),如下: 尽量使用相对布局,减少不必要层级结构。不用解释吧?递归解析的原因。 使用merge属性。使用它可以有效的将某些符合条件的多余的层级优化掉。使用merge的场合主要有两处:自定义View中使用,父元素尽量是FrameLayout,当然如果父元素是其他布局,而且不是太复杂的情况下也是可以使用的;Activity中的整体布局,根元素需要是FrameLayout。但是使用merge标签还是有一些限制的,具体是:merge只能用在布局XML文件的根元素;使用merge来inflate一个布局时,必须指定一个ViewGroup作为其父元素,并且要设置inflate的attachToRoot参数为true。(参照inflate(int, ViewGroup, boolean)方法);不能在ViewStub中使用merge标签;最直观的一个原因就是ViewStub的inflate方法中根本没有attachToRoot的设置。 使用ViewStub。一个轻量级的页面,我们通常使用它来做预加载处理,来改善页面加载速度和提高流畅性,ViewStub本身不会占用层级,它最终会被它指定的层级取代。ViewStub也是有一些缺点,譬如:ViewStub只能Inflate一次,之后ViewStub对象会被置为空。按句话说,某个被ViewStub指定的布局被Inflate后,就不能够再通过ViewStub来控制它了。所以它不适用 于需要按需显示隐藏的情况;ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。如果想操作一个具体的view,还是使用visibility属性吧;VIewStub中不能嵌套merge标签。 使用include。这个标签是为了布局重用。 控件设置widget以后对于layout_hORw-xxx设置0dp。减少系统运算次数。 如上就是一些APP布局文件基础的优化技巧。 ### 5 总结 至此整个Activity的setContentView与Android的LayoutInflater相关原理都已经分析完毕。关于本篇中有些地方直接给出结论的知识点后面的文章中会做一说明。 setContentView整个过程主要是如何把Activity的布局文件或者java的View添加至窗口里,重点概括为: 1. 创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。 1. 依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。 1. 将Activity的布局文件添加至id为content的FrameLayout内。 1. 当setContentView设置显示OK以后会回调Activity的onContentChanged方法。Activity的各种View的findViewById()方法等都可以放到该方法中,系统会帮忙回调。 如下就是整个Activity的分析简单关系图: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7959677.jpg "") LayoutInflater的使用中重点关注inflate方法的参数含义: - inflate(xmlId, null); 只创建temp的View,然后直接返回temp。 - inflate(xmlId, parent); 创建temp的View,然后执行root.addView(temp, params);最后返回root。 - inflate(xmlId, parent, false); 创建temp的View,然后执行temp.setLayoutParams(params);然后再返回temp。 - inflate(xmlId, parent, true); 创建temp的View,然后执行root.addView(temp, params);最后返回root。 - inflate(xmlId, null, false); 只创建temp的View,然后直接返回temp。 - inflate(xmlId, null, true); 只创建temp的View,然后直接返回temp。 当我们自定义View时在构造函数inflate一个xml后可以实现onFinishInflate这个方法一些自定义的逻辑。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober) 转载烦请注明出处,尊重分享成果】
';

Android触摸屏事件派发机制详解与源码分析三(Activity篇)

最后更新于:2022-04-01 09:53:09

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN。因为CSDN也支持MarkDown语法了,牛逼啊! 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober)】 该篇承接上一篇[《Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)》](http://blog.csdn.net/yanbober/article/details/45912661),阅读本篇之前建议先阅读。 ### 1 背景 还记得前面两篇从Android的基础最小元素控件(View)到ViewGroup控件的触摸屏事件分发机制分析吗?你可能看完会有疑惑,View的事件是ViewGroup派发的,那ViewGroup的事件呢?他包含在Activity上,是不是Activity也有类似的事件派发方法呢?带着这些疑惑咱们继续实例验证加源码分析吧。 PS:阅读本篇前建议先查看前一篇[《Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)》](http://blog.csdn.net/yanbober/article/details/45912661)与[《Android触摸屏事件派发机制详解与源码分析一(View篇)》](http://blog.csdn.net/yanbober/article/details/45887547),这一篇承接上一篇。 ### 2 实例验证 #### 2-1 代码 如下实例与前面实例相同,一个Button在LinearLayout里,只不过我们这次重写了Activity的一些方法而已。具体如下: 自定义的Button与LinearLayout: ~~~ public class TestButton extends Button { public TestButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "TestButton--dispatchTouchEvent--action="+event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "TestButton--onTouchEvent--action="+event.getAction()); return super.onTouchEvent(event); } } ~~~ ~~~ public class TestLinearLayout extends LinearLayout { public TestLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.i(null, "TestLinearLayout--onInterceptTouchEvent--action="+ev.getAction()); return super.onInterceptTouchEvent(ev); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "TestLinearLayout--dispatchTouchEvent--action=" + event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "TestLinearLayout--onTouchEvent--action="+event.getAction()); return super.onTouchEvent(event); } } ~~~ 整个界面的布局文件: ~~~ <com.example.yanbo.myapplication.TestLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/layout"> <com.example.yanbo.myapplication.TestButton android:text="click test" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/button"/> </com.example.yanbo.myapplication.TestLinearLayout> ~~~ 整个界面Activity,重写了Activity的一些关于触摸派发的方法(三个): ~~~ public class MainActivity extends Activity implements View.OnClickListener, View.OnTouchListener { private TestButton mButton; private TestLinearLayout mLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (TestButton) this.findViewById(R.id.button); mLayout = (TestLinearLayout) this.findViewById(R.id.layout); mButton.setOnClickListener(this); mLayout.setOnClickListener(this); mButton.setOnTouchListener(this); mLayout.setOnTouchListener(this); } @Override public void onClick(View v) { Log.i(null, "onClick----v=" + v); } @Override public boolean onTouch(View v, MotionEvent event) { Log.i(null, "onTouch--action="+event.getAction()+"--v="+v); return false; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.i(null, "MainActivity--dispatchTouchEvent--action=" + ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public void onUserInteraction() { Log.i(null, "MainActivity--onUserInteraction"); super.onUserInteraction(); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "MainActivity--onTouchEvent--action="+event.getAction()); return super.onTouchEvent(event); } } ~~~ 如上就是实例测试代码,非常简单,没必要分析,直接看结果吧。 #### 2-2 结果分析 直接点击Button按钮打印如下: ~~~ MainActivity--dispatchTouchEvent--action=0 MainActivity--onUserInteraction TestLinearLayout--dispatchTouchEvent--action=0 TestLinearLayout--onInterceptTouchEvent--action=0 TestButton--dispatchTouchEvent--action=0 onTouch--action=0--v=com.example.yanbo.myapplication.TestButton TestButton--onTouchEvent--action=0 MainActivity--dispatchTouchEvent--action=1 TestLinearLayout--dispatchTouchEvent--action=1 TestLinearLayout--onInterceptTouchEvent--action=1 TestButton--dispatchTouchEvent--action=1 onTouch--action=1--v=com.example.yanbo.myapplication.TestButton TestButton--onTouchEvent--action=1 onClick----v=com.example.yanbo.myapplication.TestButton ~~~ 分析可以发现,当点击Button时除过派发Activity的几个新方法之外其他完全符合前面两篇分析的View与ViewGroup的触摸事件派发机制。对于Activity 来说,ACTION_DOWN事件首先触发dispatchTouchEvent,然后触发onUserInteraction,再次onTouchEvent,接着的ACTION_UP事件触发 dispatchTouchEvent后触发了onTouchEvent,也就是说ACTION_UP事件时不会触发onUserInteraction(待会可查看源代码分析原因)。 直接点击Button以外的其他区域: ~~~ MainActivity--dispatchTouchEvent--action=0 MainActivity--onUserInteraction TestLinearLayout--dispatchTouchEvent--action=0 TestLinearLayout--onInterceptTouchEvent--action=0 onTouch--action=0--v=com.example.yanbo.myapplication.TestLinearLayout TestLinearLayout--onTouchEvent--action=0 MainActivity--dispatchTouchEvent--action=1 TestLinearLayout--dispatchTouchEvent--action=1 onTouch--action=1--v=com.example.yanbo.myapplication.TestLinearLayout TestLinearLayout--onTouchEvent--action=1 onClick----v=com.example.yanbo.myapplication.TestLinearLayout ~~~ 怎么样?完全符合上面点击Button结果分析的猜想。 那接下来还是要看看Activity里关于这几个方法的源码了。 ### 3 Android 5.1.1(API 22) Activity触摸屏事件传递源码分析 通过上面例子的打印我们可以确定分析源码的顺序,那就开始分析呗。 #### 3-1 从Activity的dispatchTouchEvent方法说起 #### 3-1-1 开始分析 先上源码,如下: ~~~ /** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed. */ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } ~~~ 哎呦!这次看着代码好少的样子,不过别高兴,浓缩才是精华,这里代码虽少,涉及的问题点还是很多的,那么咱们就来一点一点分析吧。 12到14行看见了吧?上面例子咱们看见只有ACTION_DOWN事件派发时调运了onUserInteraction方法,当时还在疑惑呢,这下明白了吧,不多解释,咱们直接跳进去可以看见是一个空方法,具体下面会分析。 好了,自己分析15到17行,看着简单吧,我勒个去,我怎么有点懵,这是哪的方法?咱们分析分析吧。 首先分析Activity的attach方法可以发现getWindow()返回的就是PhoneWindow对象(PhoneWindow为抽象Window的实现子类),那就简单了,也就相当于PhoneWindow类的方法,而PhoneWindow类实现于Window抽象类,所以先看下Window类中抽象方法的定义,如下: ~~~ /** * Used by custom windows, such as Dialog, to pass the touch screen event * further down the view hierarchy. Application developers should * not need to implement or call this. * */ public abstract boolean superDispatchTouchEvent(MotionEvent event); ~~~ 看见注释没有?用户不需要重写实现的方法,实质也不能,在Activity中没有提供重写的机会,因为Window是以组合模式与Activity建立关系的。好了 ,看完了抽象的Window方法,那就去PhoneWindow里看下Window抽象方法的实现吧,如下: ~~~ @Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); } ~~~ 又是看着好简单的样子哦,实际又是一堆问题,继续分析。你会发现在PhoneWindow的superDispatchTouchEvent方法里又直接返回了另一个mDecor 对象的superDispatchTouchEvent方法,mDecor是啥?继续分析吧。 在PhoneWindow类里发现,mDecor是DecorView类的实例,同时DecorView是PhoneWindow的内部类。最惊人的发现是DecorView extends FrameLayout implements RootViewSurfaceTaker,看见没有?它是一个真正Activity的root view,它继承了FrameLayout。怎么验证他一定是root view呢?很简单,不知道大家是不是熟悉Android App开发技巧中关于UI布局优化使用的SDK工具Hierarchy Viewer。咱们通过他来看下上面刚刚展示的那个例子的Hierarchy Viewer你就明白了,如下我在Ubuntu上截图的Hierarchy Viewer分析结果: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da78e50fa.jpg "") 看见没有,我们上面例子中Activity中setContentView时放入的xml layout是一个LinearLayout,其中包含一个Button,上图展示了我们放置的LinearLayout被放置在一个id为content的FrameLayout的布局中,这也就是为啥Activity的setContentView方法叫set content view了,就是把我们的xml放入了这个id为content的FrameLayout中。 赶快回过头,你是不是发现上面PhoneWindow的superDispatchTouchEvent直接返回了DecorView的superDispatchTouchEvent,而DecorView又是FrameLayout的子类,FrameLayout又是ViewGroup的子类。机智的你想到了啥木有? 没想到就继续看下DecorView类的superDispatchTouchEvent方法吧,如下: ~~~ public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } ~~~ 这回你一定恍然大悟了吧,不然就得脑补前面两篇博客的内容了。。。 搞半天Activity的dispatchTouchEvent方法的15行`if (getWindow().superDispatchTouchEvent(ev))`本质执行的是一个ViewGroup的dispatchTouchEvent方法(这个ViewGroup是Activity特有的root view,也就是id为content的FrameLayout布局),接下来就不用多说了吧,完全是前面两篇分析的执行过程。 接下来依据派发事件返回值决定是否触发Activity的onTouchEvent方法。 #### 3-1-2 小总结一下 在Activity的触摸屏事件派发中: 1. 首先会触发Activity的dispatchTouchEvent方法。 1. dispatchTouchEvent方法中如果是ACTION_DOWN的情况下会接着触发onUserInteraction方法。 1. 接着在dispatchTouchEvent方法中会通过Activity的root View(id为content的FrameLayout),实质是ViewGroup,通过super.dispatchTouchEvent把touchevent派发给各个activity的子view,也就是我们再Activity.onCreat方法中setContentView时设置的view。 1. 若Activity下面的子view拦截了touchevent事件(返回true)则Activity.onTouchEvent方法就不会执行。 ### 3-2 继续Activity的dispatchTouchEvent方法中调运的onUserInteraction方法 如下源码: ~~~ /** * Called whenever a key, touch, or trackball event is dispatched to the * activity. Implement this method if you wish to know that the user has * interacted with the device in some way while your activity is running. * This callback and {@link #onUserLeaveHint} are intended to help * activities manage status bar notifications intelligently; specifically, * for helping activities determine the proper time to cancel a notfication. * * <p>All calls to your activity's {@link #onUserLeaveHint} callback will * be accompanied by calls to {@link #onUserInteraction}. This * ensures that your activity will be told of relevant user activity such * as pulling down the notification pane and touching an item there. * * <p>Note that this callback will be invoked for the touch down action * that begins a touch gesture, but may not be invoked for the touch-moved * and touch-up actions that follow. * * @see #onUserLeaveHint() */ public void onUserInteraction() { } ~~~ 搞了半天就像上面说的,这是一个空方法,那它的作用是啥呢? 此方法是activity的方法,当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法。下拉statubar、旋转屏幕、锁屏不会触发此方法。所以它会用在屏保应用上,因为当你触屏机器 就会立马触发一个事件,而这个事件又不太明确是什么,正好屏保满足此需求;或者对于一个Activity,控制多长时间没有用户点响应的时候,自己消失等。 这个方法也分析完了,那就剩下onTouchEvent方法了,如下继续分析。 #### 3-3 继续Activity的dispatchTouchEvent方法中调运的onTouchEvent方法 如下源码: ~~~ /** * Called when a touch screen event was not handled by any of the views * under it. This is most useful to process touch events that happen * outside of your window bounds, where there is no view to receive it. * * @param event The touch screen event being processed. * * @return Return true if you have consumed the event, false if you haven't. * The default implementation always returns false. */ public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; } ~~~ 看见没有,这个方法看起来好简单的样子。 如果一个屏幕触摸事件没有被这个Activity下的任何View所处理,Activity的onTouchEvent将会调用。这对于处理window边界之外的Touch事件非常有用,因为通常是没有View会接收到它们的。返回值为true表明你已经消费了这个事件,false则表示没有消费,默认实现中返回false。 继续分析吧,重点就一句,mWindow.shouldCloseOnTouch(this, event)中的mWindow实际就是上面分析dispatchTouchEvent方法里的getWindow()对象,所以直接到Window抽象类和PhoneWindow子类查看吧,发现PhoneWindow没有重写Window的shouldCloseOnTouch方法,所以看下Window类的shouldCloseOnTouch实现吧,如下: ~~~ /** @hide */ public boolean shouldCloseOnTouch(Context context, MotionEvent event) { if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) && peekDecorView() != null) { return true; } return false; } ~~~ 这其实就是一个判断,判断mCloseOnTouchOutside标记及是否为ACTION_DOWN事件,同时判断event的x、y坐标是不是超出Bounds,然后检查 FrameLayout的content的id的DecorView是否为空。其实没啥太重要的,这只是对于处理window边界之外的Touch事件有判断价值而已。 所以,到此Activity的onTouchEvent分析完毕。 ### 4 Android触摸事件综合总结 到此整个Android的Activity->ViewGroup->View的触摸屏事件分发机制完全分析完毕。这时候你可以回过头看这三篇文章的例子,你会完全明白那些打印的含义与原理。 当然,了解这些源码机制不仅对你写普通代码时有帮助,最重要的是对你想自定义装逼控件时有不可磨灭的基础性指导作用与技巧提示作用。
';

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

最后更新于:2022-04-01 09:53:06

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN。因为CSDN也支持MarkDown语法了,牛逼啊! 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober)】 该篇承接上一篇[《Android触摸屏事件派发机制详解与源码分析一(View篇)》](http://blog.csdn.net/yanbober/article/details/45887547),阅读本篇之前建议先阅读。当然,阅读完这一篇之后可以阅读继续进阶的下一篇[《Android触摸屏事件派发机制详解与源码分析三(Activity篇)》](http://blog.csdn.net/yanbober/article/details/45932123)。 ### 1 背景 还记得前一篇[《Android触摸屏事件派发机制详解与源码分析一(View篇)》](http://blog.csdn.net/yanbober/article/details/45887547)中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制。 PS:阅读本篇前建议先查看前一篇[《Android触摸屏事件派发机制详解与源码分析一(View篇)》](http://blog.csdn.net/yanbober/article/details/45887547),这一篇承接上一篇。 关于View与ViewGroup的区别在前一篇的Android 5.1.1(API 22) View触摸屏事件传递源码分析部分的写在前面的话里面有详细介绍。其实你只要记住类似Button这种控件都是View的子类,类似布局这种控件都是ViewGroup的子类,而ViewGroup又是View的子类而已。具体查阅[《Android触摸屏事件派发机制详解与源码分析一(View篇)》](http://blog.csdn.net/yanbober/article/details/45887547)。 ### 2 基础实例现象 #### 2-1 例子 这个例子布局等还和上一篇的例子相似,只是重写了Button和LinearLayout而已,所以效果图不在提供,具体参见上一篇。 首先我们简单的自定义一个Button(View的子类),再自定义一个LinearLayout(ViewGroup的子类),其实没有自定义任何属性,只是重写部分方法(添加了打印,方便查看)而已,如下: ~~~ public class TestButton extends Button { public TestButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "TestButton dispatchTouchEvent-- action=" + event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "TestButton onTouchEvent-- action=" + event.getAction()); return super.onTouchEvent(event); } } ~~~ ~~~ public class TestLinearLayout extends LinearLayout { public TestLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.i(null, "TestLinearLayout onInterceptTouchEvent-- action=" + ev.getAction()); return super.onInterceptTouchEvent(ev); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "TestLinearLayout dispatchTouchEvent-- action=" + event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "TestLinearLayout onTouchEvent-- action=" + event.getAction()); return super.onTouchEvent(event); } } ~~~ 如上两个控件很简单吧,不解释,继续看其他代码: ~~~ <?xml version="1.0" encoding="utf-8"?> <com.zzci.light.TestLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:gravity="center" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mylayout"> <com.zzci.light.TestButton android:id="@+id/my_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="click test"/> </com.zzci.light.TestLinearLayout> ~~~ ~~~ public class ListenerActivity extends Activity implements View.OnTouchListener, View.OnClickListener { private TestLinearLayout mLayout; private TestButton mButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mLayout = (TestLinearLayout) this.findViewById(R.id.mylayout); mButton = (TestButton) this.findViewById(R.id.my_btn); mLayout.setOnTouchListener(this); mButton.setOnTouchListener(this); mLayout.setOnClickListener(this); mButton.setOnClickListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { Log.i(null, "OnTouchListener--onTouch-- action="+event.getAction()+" --"+v); return false; } @Override public void onClick(View v) { Log.i(null, "OnClickListener--onClick--"+v); } } ~~~ 到此基础示例的代码编写完成。没有啥难度,很简单易懂,不多解释了。 #### 2-2 运行现象 当直接点击Button时打印现象如下: ~~~ TestLinearLayout dispatchTouchEvent-- action=0 TestLinearLayout onInterceptTouchEvent-- action=0 TestButton dispatchTouchEvent-- action=0 OnTouchListener--onTouch-- action=0 --com.zzci.light.TestButton TestButton onTouchEvent-- action=0 TestLinearLayout dispatchTouchEvent-- action=1 TestLinearLayout onInterceptTouchEvent-- action=1 TestButton dispatchTouchEvent-- action=1 OnTouchListener--onTouch-- action=1 --com.zzci.light.TestButton TestButton onTouchEvent-- action=1 OnClickListener--onClick--com.zzci.light.TestButton ~~~ 分析:你会发现这个结果好惊讶吧,点击了Button却先执行了TestLinearLayout(ViewGroup)的dispatchTouchEvent,接着执行 TestLinearLayout(ViewGroup)的onInterceptTouchEvent,接着执行TestButton(TestLinearLayout包含的成员View)的dispatchTouchEvent ,接着就是View触摸事件的分发流程,上一篇已经讲过了。也就是说当点击View时事件派发每一个down,up的action顺序是先触发最父级控件 (这里为LinearLayout)的dispatchTouchEvent->onInterceptTouchEvent->然后向前一级传递(这里就是传递到Button View)。 那么继续看,当直接点击除Button以外的其他部分时打印如下: ~~~ TestLinearLayout dispatchTouchEvent-- action=0 TestLinearLayout onInterceptTouchEvent-- action=0 OnTouchListener--onTouch-- action=0 --com.zzci.light.TestLinearLayout TestLinearLayout onTouchEvent-- action=0 TestLinearLayout dispatchTouchEvent-- action=1 OnTouchListener--onTouch-- action=1 --com.zzci.light.TestLinearLayout TestLinearLayout onTouchEvent-- action=1 OnClickListener--onClick--com.zzci.light.TestLinearLayout ~~~ 分析:你会发现一个奇怪的现象,派发ACTION_DOWN(action=0)事件时顺序为dispatchTouchEvent->onInterceptTouchEvent->onTouch ->onTouchEvent,而接着派发ACTION_UP(action=1)事件时与上面顺序不同的时竟然没触发onInterceptTouchEvent方法。这是为啥呢? 我也纳闷,那就留着下面分析源码再找答案吧,先记住这个问题。 有了上面这个例子你是不是发现包含ViewGroup与View的事件触发有些相似又有很大差异吧(PS:在Android中继承View实现的控件已经是最小单位了,也即在XML布局等操作中不能再包含子项了,而继承ViewGroup实现的控件通常不是最小单位,可以包含不确定数目的子项)。具体差异是啥呢?咱们类似上篇一样,带着这个实例疑惑去看源码找答案吧。 ### 3 Android 5.1.1(API 22) ViewGroup触摸屏事件传递源码分析 通过上面例子的打印我们可以确定分析源码的顺序,那就开始分析呗。 #### 3-1 从ViewGroup的dispatchTouchEvent方法说起 前一篇的3-2小节说在Android中你只要触摸控件首先都会触发控件的dispatchTouchEvent方法(其实这个方法一般都没在具体的控件类中,而在他的父类View中)。这其实是思维单单局限在View的角度去看待的,这里通过上面的例子你是否发现触摸控件会先从他的父级dispatchTouchEvent方法开始派发呢?是的,所以咱们先从ViewGroup的dispatchTouchEvent方法说起,如下: ~~~ public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // If the event is targeting accessiiblity focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping // state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; } ~~~ 我勒个去!!!这比View的dispatchTouchEvent方法长很多啊,那就只关注重点分析吧。 第一步,17-24行,对ACTION_DOWN进行处理。 因为ACTION_DOWN是一系列事件的开端,当是ACTION_DOWN时进行一些初始化操作,从上面源码中注释也可以看出来,清除以往的Touch状态然后开始新的手势。在这里你会发现cancelAndClearTouchTargets(ev)方法中有一个非常重要的操作就是将mFirstTouchTarget设置为了null(刚开始分析大眼瞄一眼没留意,结果越往下看越迷糊,所以这个是分析ViewGroup的dispatchTouchEvent方法第一步中重点要记住的一个地方),接着在resetTouchState()方法中重置Touch状态标识。 第二步,26-47行,检查是否要拦截。 在dispatchTouchEvent(MotionEvent ev)这段代码中使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递,该变量类似第一步的mFirstTouchTarget变量,在后续代码中起着很重要的作用。`if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)`这一条判断语句说明当事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到能够接收touch事件的目标组件)时if成立,否则if不成立,然后将intercepted设置为true,也即拦截事件。当当事件为ACTION_DOWN或者mFirstTouchTarget不为null时判断disallowIntercept(禁止拦截)标志位,而这个标记在ViewGroup中提供了public的设置方法,如下: ~~~ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } } ~~~ 所以你可以在其他地方调用requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法,从而禁止执行是否需要拦截的判断。 当disallowIntercept为true(禁止拦截判断)时则intercepted直接设置为false,否则调用onInterceptTouchEvent(ev)方法,然后将结果赋值给 intercepted。那就来看下ViewGroup与众不同与View特有的onInterceptTouchEvent方法,如下: ~~~ public boolean onInterceptTouchEvent(MotionEvent ev) { return false; } ~~~ 看见了吧,默认的onInterceptTouchEvent方法只是返回了一个false,也即intercepted=false。所以可以说明上面例子的部分打印 (dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent),这里很明显表明在ViewGroup的dispatchTouchEvent()中默认 (不在其他地方调运requestDisallowInterceptTouchEvent方法设置禁止拦截标记)首先调用了onInterceptTouchEvent()方法。 第三步,49-51行,检查cancel。 通过标记和action检查cancel,然后将结果赋值给局部boolean变量canceled。 第四步,53-函数结束,事件分发。 54行首先可以看见获取一个boolean变量标记split来标记,默认是true,作用是是否把事件分发给多个子View,这个同样在ViewGroup中提供了public的方法设置,如下: ~~~ public void setMotionEventSplittingEnabled(boolean split) { // TODO Applications really shouldn't change this setting mid-touch event, // but perhaps this should handle that case and send ACTION_CANCELs to any child views // with gestures in progress when this is changed. if (split) { mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS; } else { mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS; } } ~~~ 接着57行`if (!canceled && !intercepted)`判断表明,事件不是ACTION_CANCEL并且ViewGroup的拦截标志位intercepted为false(不拦截)则会进入 其中。事件分发步骤中关于ACTION_DOWN的特殊处理 接着67行这个很大的if语句`if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || ` `actionMasked == MotionEvent.ACTION_HOVER_MOVE)`处理ACTION_DOWN事件,这个环节比较繁琐,也比较重要,如下具体分析。 在79行判断了childrenCount个数是否不为0,然后接着在84行拿到了子View的list集合preorderedList;接着在88行通过一个for循环i从childrenCount - 1开始遍历到0,倒序遍历所有的子view,这是因为preorderedList中的顺序是按照addView或者XML布局文件中的顺序来的,后addView添加的子View,会因为Android的UI后刷新机制显示在上层;假如点击的地方有两个子View都包含的点击的坐标,那么后被添加到布局中的那个子view会先响应事件;这样其实也是符合人的思维方式的,因为后被添加的子view会浮在上层,所以我们去点击的时候一般都会希望点击最上层的那个组件先去响应事件。 接着在106到112行通过getTouchTarget去查找当前子View是否在mFirstTouchTarget.next这条target链中的某一个targe中,如果在则返回这个target,否则返回null。在这段代码的if判断通过说明找到了接收Touch事件的子View,即newTouchTarget,那么,既然已经找到了,所以执行break跳出for循环。如果没有break则继续向下执行走到115行开始到134行,这里你可以看见一段if判断的代码`if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))`,这个被if的大括弧括起来的一段代码很重要,具体解释如下: 调用方法dispatchTransformedTouchEvent()将Touch事件传递给特定的子View。该方法十分重要,在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法。在dispatchTouchEvent()中如果子View为ViewGroup并且Touch没有被拦截那么递归调用dispatchTouchEvent(),如果子View为View那么就会调用其onTouchEvent()。dispatchTransformedTouchEvent方法如果返回true则表示子View消费掉该事件,同时进入 该if判断。满足if语句后重要的操作有: - 给newTouchTarget赋值; - 给alreadyDispatchedToNewTouchTarget赋值为true; - 执行break,因为该for循环遍历子View判断哪个子View接受Touch事件,既然已经找到了就跳出该外层for循环; 如果115行if判断中的dispatchTransformedTouchEvent()方法返回false,即子View的onTouchEvent返回false(即Touch事件未被消费),那么就不满足该if条件,也就无法执行addTouchTarget(),从而导致mFirstTouchTarget为null(没法对mFirstTouchTarget赋值,因为上面分析了mFirstTouchTarget一进来是ACTION_DOWN就置位为null了),那么该子View就无法继续处理ACTION_MOVE事件和ACTION_UP事件(28行的判断为false,也即intercepted=true了,所以之后一系列判断无法通过)。 如果115行if判断中的dispatchTransformedTouchEvent()方法返回true,即子View的onTouchEvent返回true(即Touch事件被消费),那么就满足该if条件,从而mFirstTouchTarget不为null。 继续看143行的判断`if (newTouchTarget == null && mFirstTouchTarget != null)`。该if表示经过前面的for循环没有找到子View接收Touch事件并且之前的mFirstTouchTarget不为空则为真,然后newTouchTarget指向了最初的TouchTarget。 通过上面67到157行关于事件分发步骤中ACTION_DOWN的特殊处理可以发现,对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent()方法,该方法返回值具备如下特征: | return | description | set | |-----|-----|-----| | true | 事件被消费 | mFirstTouchTarget!=null | | false | 事件未被消费 | mFirstTouchTarget==null | 因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent(),所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的。简单地说onTouchEvent()是否消费了Touch事件的返回值决定了dispatchTransformedTouchEvent()的返回值,从而决定mFirstTouchTarget是否为null,进一步决定了ViewGroup是否处理Touch事件,这一点在160行开始的代码中有体现。如下分析事件分发步骤中关于ACTION_DOWN处理之后的其他处理逻辑,也即160行开始剩余的逻辑。 事件分发步骤中关于ACTION_DOWN处理之后的其他处理逻辑 可以看到,如果派发的事件不是ACTION_DOWN就不会经过上面的流程,而是直接从此处开始执行。上面说了,经过上面对于ACTION_DOWN的处理后mFirstTouchTarget可能为null或者不为null。所以可以看见161行代码`if (mFirstTouchTarget == null)与else`判断了mFirstTouchTarget值是否为null的情况,完全符合如上分析。那我们分情况继续分析一下: 当161行if判断的mFirstTouchTarget为null时,也就是说Touch事件未被消费,即没有找到能够消费touch事件的子组件或Touch事件被拦截了,则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件(和普通View一样),即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件。具体就是在调用dispatchTransformedTouchEvent()时第三个参数为null,关于dispatchTransformedTouchEvent方法下面会分析,暂时先记住就行。 这下再回想上面例子,点击Button时为啥触发了Button的一系列touch方法而没有触发父级LinearLayout的touch方法的疑惑?明白了吧? 子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了,子view对于Touch事件处理返回false那么其上层的ViewGroup才可以处理Touch事件。 当161行if判断的mFirstTouchTarget不为null时,也就是说找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View。可以看见在源码的else中对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用dispatchTransformedTouchEvent()方法来实现的处理。 到此ViewGroup的dispatchTouchEvent方法分析完毕。 上面说了ViewGroup的dispatchTouchEvent方法详细情况,也知道在其中可能会执行onInterceptTouchEvent方法,所以接下来咱们先简单分析一下这个方法。 #### 3-2 说说ViewGroup的dispatchTouchEvent中可能执行的onInterceptTouchEvent方法 如下系统源码: ~~~ public boolean onInterceptTouchEvent(MotionEvent ev) { return false; } ~~~ 看到了吧,这个方法算是ViewGroup不同于View特有的一个事件派发调运方法。在源码中可以看到这个方法实现很简单,但是有一堆注释。其实上面 分析了,如果ViewGroup的onInterceptTouchEvent返回false就不阻止事件继续传递派发,否则阻止传递派发。 对了,还记得在dispatchTouchEvent方法中除过可能执行的onInterceptTouchEvent以外在后面派发事件时执行的dispatchTransformedTouchEvent方法吗?上面分析dispatchTouchEvent时说了下面会仔细分析,那么现在就来继续看看这个方法吧。 #### 3-3 继续说说ViewGroup的dispatchTouchEvent中执行的dispatchTransformedTouchEvent方法 ViewGroup的dispatchTransformedTouchEvent方法系统源码如下: ~~~ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; } ~~~ 看到了吧,这个方法也算是ViewGroup不同于View特有的一个事件派发调运方法,而且奇葩的就是这个方法也很长。那也继续分析吧。。。 上面分析了,在dispatchTouchEvent()中调用dispatchTransformedTouchEvent()将事件分发给子View处理。在此我们需要重点分析该方法的第三个参数(View child)。在dispatchTouchEvent()中多次调用了dispatchTransformedTouchEvent()方法,而且有时候第三个参数为null,有时又不是,他们到底有啥区别呢?这段源码中很明显展示了结果。在dispatchTransformedTouchEvent()源码中可以发现多次对于child是否为null的判断,并且均做出如下类似的操作。其中,当child == null时会将Touch事件传递给该ViewGroup自身的dispatchTouchEvent()处理,即super.dispatchTouchEvent(event)(也就是View的这个方法,因为ViewGroup的父类是View);当child != null时会调用该子view(当然该view可能是一个View也可能是一个ViewGroup)的dispatchTouchEvent(event)处理,即child.dispatchTouchEvent(event)。别的代码几乎没啥需要具体注意分析的。 所以,到此你也会发现ViewGroup没有重写View的onTouchEvent(MotionEvent event) 方法,也就是说接下来的调运关系就是上一篇分析的流程了,这里不在多说。 好了,到此你是不是即明白了上面实例演示的代码结果,也明白了上一篇最后升级实例验证模块留下的点击Button触发了LinearLayout的一些疑惑呢?答案自然是必须的! ### 4 Android 5.1.1(API 22) ViewGroup触摸屏事件传递总结 如上就是所有ViewGroup关于触摸屏事件的传递机制源码分析与实例演示。具体总结如下: 1. Android事件派发是先传递到最顶级的ViewGroup,再由ViewGroup递归传递到View的。 1. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。 1. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober)】 好了,至此整个View与ViewGroup的触摸屏事件派发机制分析完毕。关于他们的事件是哪派发来的可以继续进阶的阅读下一篇[《Android触摸屏事件派发机制详解与源码分析三(Activity篇)》](http://blog.csdn.net/yanbober/article/details/45932123)
';

Android触摸屏事件派发机制详解与源码分析一(View篇)

最后更新于:2022-04-01 09:53:04

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN。因为CSDN也支持MarkDown语法了,牛逼啊! 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober)】 Notice:阅读完该篇之后如果想继续深入阅读Android触摸屏事件派发机制详解与源码分析下一篇请点击[《Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)》](http://blog.csdn.net/yanbober/article/details/45912661)查看。 ### 1 背景 最近在简书和微博还有Q群看见很多人说Android自定义控件(View/ViewGroup)如何学习?为啥那么难?其实答案很简单:“基础不牢,地动山摇。” 不扯蛋了,进入正题。就算你不自定义控件,你也必须要了解Android控件的触摸屏事件传递机制(之所以说触摸屏是因为该系列以触摸屏的事件机制分析为主,对于类似TV设备等的物理事件机制的分析雷同但有区别。哈哈,谁让我之前是做Android TV BOX的,悲催!),只有这样才能将你的控件事件运用的如鱼得水。接下来的控件触摸屏事件传递机制分析依据Android 5.1.1源码(API 22)。 ### 2 基础实例现象 #### 2-1 例子 从一个例子分析说起吧。如下是一个很简单不过的Android实例:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7762b9f.jpg "") ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:gravity="center" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mylayout"> <Button android:id="@+id/my_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="click test"/> </LinearLayout> ~~~ ~~~ public class ListenerActivity extends Activity implements View.OnTouchListener, View.OnClickListener { private LinearLayout mLayout; private Button mButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mLayout = (LinearLayout) this.findViewById(R.id.mylayout); mButton = (Button) this.findViewById(R.id.my_btn); mLayout.setOnTouchListener(this); mButton.setOnTouchListener(this); mLayout.setOnClickListener(this); mButton.setOnClickListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { Log.i(null, "OnTouchListener--onTouch-- action="+event.getAction()+" --"+v); return false; } @Override public void onClick(View v) { Log.i(null, "OnClickListener--onClick--"+v); } } ~~~ #### 2-2 现象 如上代码很简单,但凡学过几天Android的人都能看懂吧。Activity中有一个LinearLayout(ViewGroup的子类,ViewGroup是View的子类)布局,布局中包含一个按钮(View的子类);然后分别对这两个控件设置了Touch与Click的监听事件,具体运行结果如下: 1. 当稳稳的点击Button时打印如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da77852f5.jpg "") 1. 当稳稳的点击除过Button以外的其他地方时打印如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da7797997.jpg "") 1. 当收指点击Button时按在Button上晃动了一下松开后的打印如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da77ab23c.jpg "") 机智的你看完这个结果指定知道为啥吧?  我们看下onTouch和onClick,从参数都能看出来onTouch比onClick强大灵活,毕竟多了一个event参数。这样onTouch里就可以处理ACTION_DOWN、ACTION_UP、ACTION_MOVE等等的各种触摸。现在来分析下上面的打印结果;在1中,当我们点击Button时会先触发onTouch事件(之所以打印action为0,1各一次是因为按下抬起两个触摸动作被触发)然后才触发onClick事件;在2中也同理类似1;在3中会发现onTouch被多次调运后才调运onClick,是因为手指晃动了,所以触发了ACTION_DOWN->ACTION_MOVE…->ACTION_UP。 如果你眼睛比较尖你会看见onTouch会有一个返回值,而且在上面返回了false。你可能会疑惑这个返回值有啥效果?那就验证一下吧,我们将上面的onTouch返回值改为ture。如下: ~~~ @Override public boolean onTouch(View v, MotionEvent event) { Log.i(null, "OnTouchListener--onTouch-- action="+event.getAction()+" --"+v); return true; } ~~~ 再次点击Button结果如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da77c1e09.jpg "")   看见了吧,如果onTouch返回true则onClick不会被调运了。 #### 2-3 总结结论 好了,经过这个简单的实例验证你可以总结发现: 1. Android控件的Listener事件触发顺序是先触发onTouch,其次onClick。 1. 如果控件的onTouch返回true将会阻止事件继续传递,返回false事件会继续传递。 对于伸手党码农来说其实到这足矣应付常规的App事件监听处理使用开发了,但是对于复杂的事件监听处理或者想自定义控件的码农来说这才是刚刚开始,只是个热身。既然这样那就继续喽。。。 ### 3 Android 5.1.1(API 22) View触摸屏事件传递源码分析 #### 3-1 写在前面的话 其实Android源码无论哪个版本对于触摸屏事件的传递机制都类似,这里只是选用了目前最新版本的源码来分析而已。分析Android View事件传递机制之前有必要先看下源码的一些关系,如下是几个继承关系图:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da77d212b.jpg "")   ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da77e8671.jpg "") 怎么样?看了官方这个继承图是不是明白了上面例子中说的LinearLayout是ViewGroup的子类,ViewGroup是View的子类,Button是View的子类关系呢?其实,在Android中所有的控件无非都是ViewGroup或者View的子类,说高尚点就是所有控件都是View的子类。 这里通过继承关系是说明一切控件都是View,同时View与ViewGroup又存在一些区别,所以该模块才只单单先分析View触摸屏事件传递机制。 #### 3-2 从View的dispatchTouchEvent方法说起 在Android中你只要触摸控件首先都会触发控件的dispatchTouchEvent方法(其实这个方法一般都没在具体的控件类中,而在他的父类View中),所以我们先来看下View的dispatchTouchEvent方法,如下: ~~~ public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; } ~~~ dispatchTouchEvent的代码有点长,咱们看重点就可以。前面都是设置一些标记和处理input与手势等传递,到24行的 `if (onFilterTouchEventForSecurity(event))`语句判断当前View是否没被遮住等,接着26行定义ListenerInfo局部变量,ListenerInfo是View的静态 内部类,用来定义一堆关于View的XXXListener等方法;接着`if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ` `ENABLED && li.mOnTouchListener.onTouch(this, event))`语句就是重点,首先li对象自然不会为null,li.mOnTouchListener呢?你会发现ListenerInfo 的mOnTouchListener成员是在哪儿赋值的呢?怎么确认他是不是null呢?通过在View类里搜索可以看到: ~~~ /** * Register a callback to be invoked when a touch event is sent to this view. * @param l the touch listener to attach to this view */ public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; } ~~~ li.mOnTouchListener是不是null取决于控件(View)是否设置setOnTouchListener监听,在上面的实例中我们是设置过Button的 setOnTouchListener方法的,所以也不为null;接着通过位与运算确定控件(View)是不是ENABLED 的,默认控件都是ENABLED 的; 接着判断onTouch的返回值是不是true。通过如上判断之后如果都为true则设置默认为false的result为true,那么接下来的 `if (!result && onTouchEvent(event))`就不会执行,最终dispatchTouchEvent也会返回true。而如果 `if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))` 语句有一个为false则`if (!result && onTouchEvent(event))`就会执行,如果onTouchEvent(event)返回false则dispatchTouchEvent返回false, 否则返回true。 这下再看前面的实例部分明白了吧?控件触摸就会调运dispatchTouchEvent方法,而在dispatchTouchEvent中先执行的是onTouch方法,所以验证了实例结论总结中的onTouch优先于onClick执行道理。如果控件是ENABLE且在onTouch方法里返回了true则dispatchTouchEvent方法也返回true,不会再继续往下执行;反之,onTouch返回false则会继续向下执行onTouchEvent方法,且dispatchTouchEvent的返回值与onTouchEvent返回值相同。 所以依据这个结论和上面实例打印结果你指定已经大胆猜测认为onClick一定与onTouchEvent有关系?是不是呢?先告诉你,是的。下面我们会分析。 #### 3-2-1 总结结论 在View的触摸屏传递机制中通过分析dispatchTouchEvent方法源码我们会得出如下基本结论: 1. 触摸控件(View)首先执行dispatchTouchEvent方法。 1. 在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent中执行,下面会分析)。 1. 如果控件(View)的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运onTouchEvent,dispatchTouchEvent返回值与onTouchEvent返回一样。 1. 如果控件不是enable的设置了onTouch方法也不会执行,只能通过重写控件的onTouchEvent方法处理(上面已经处理分析了),dispatchTouchEvent返回值与onTouchEvent返回一样。 1. 如果控件(View)是enable且onTouch返回true情况下,dispatchTouchEvent直接返回true,不会调用onTouchEvent方法。 上面说了onClick一定与onTouchEvent有关系,那么接下来就分析分析dispatchTouchEvent方法中的onTouchEvent方法。 #### 3-3 继续说说View的dispatchTouchEvent方法中调运的onTouchEvent方法 上面说了dispatchTouchEvent方法中如果onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运onTouchEvent,所以接着看就知道了,如下: ~~~ public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; } ~~~ 我勒个去!一个方法比一个方法代码多。好吧,那咱们继续只挑重点来说明呗。 首先地6到14行可以看出,如果控件(View)是disenable状态,同时是可以clickable的则onTouchEvent直接消费事件返回true,反之如果控件(View)是disenable状态,同时是disclickable的则onTouchEvent直接false。多说一句,关于控件的enable或者clickable属性可以通过java或者xml直接设置,每个view都有这些属性。 接着22行可以看见,如果一个控件是enable且disclickable则onTouchEvent直接返回false了;反之,如果一个控件是enable且clickable则继续进入过于一个event的switch判断中,然后最终onTouchEvent都返回了true。switch的ACTION_DOWN与ACTION_MOVE都进行了一些必要的设置与置位,接着到手抬起来ACTION_UP时你会发现,首先判断了是否按下过,同时是不是可以得到焦点,然后尝试获取焦点,然后判断如果不是longPressed则通过post在UI Thread中执行一个PerformClick的Runnable,也就是performClick方法。具体如下: ~~~ public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; } ~~~ 这个方法也是先定义一个ListenerInfo的变量然后赋值,接着判断li.mOnClickListener是不是为null,决定执行不执行onClick。你指定现在已经很 机智了,和onTouch一样,搜一下mOnClickListener在哪赋值的呗,结果发现: ~~~ public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; } ~~~ 看见了吧!控件只要监听了onClick方法则mOnClickListener就不为null,而且有意思的是如果调运setOnClickListener方法设置监听且控件是 disclickable的情况下默认会帮设置为clickable。 我勒个去!!!惊讶吧!!!猜的没错onClick就在onTouchEvent中执行的,而且是在onTouchEvent的ACTION_UP事件中执行的。 #### 3-3-1 总结结论 1. onTouchEvent方法中会在ACTION_UP分支中触发onClick的监听。 1. 当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action。 到此上面例子中关于Button点击的各种打印的真实原因都找到了可靠的证据,也就是说View的触摸屏事件传递机制其实也就这么回事。 ### 4 透过源码继续进阶实例验证 其实上面分析完View的触摸传递机制之后已经足够用了。如下的实例验证可以说是加深阅读源码的理解,还有一个主要作用就是为将来自定义控件打下坚实基础。因为自定义控件中时常会与这几个方法打交道。 #### 4-1 例子 我们自定义一个Button(Button实质继承自View),如下: ~~~ public class TestButton extends Button { public TestButton(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "dispatchTouchEvent-- action=" + event.getAction()); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "onTouchEvent-- action="+event.getAction()); return super.onTouchEvent(event); } } ~~~ 其他代码如下: ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:gravity="center" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/mylayout"> <com.zzci.light.TestButton android:id="@+id/my_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="click test"/> </LinearLayout> ~~~ ~~~ public class ListenerActivity extends Activity implements View.OnTouchListener, View.OnClickListener { private LinearLayout mLayout; private TestButton mButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mLayout = (LinearLayout) this.findViewById(R.id.mylayout); mButton = (TestButton) this.findViewById(R.id.my_btn); mLayout.setOnTouchListener(this); mButton.setOnTouchListener(this); mLayout.setOnClickListener(this); mButton.setOnClickListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { Log.i(null, "OnTouchListener--onTouch-- action="+event.getAction()+" --"+v); return false; } @Override public void onClick(View v) { Log.i(null, "OnClickListener--onClick--"+v); } } ~~~ 其实这段代码只是对上面例子中的Button换为了自定义Button而已。 #### 4-2 现象分析 #### 4-2-1 点击Button(手抽筋了一下) ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da78078ce.jpg "") 可以发现,如上打印完全符合源码分析结果,dispatchTouchEvent方法先派发down事件,完事调运onTouch,完事调运onTouchEvent返回true,同时dispatchTouchEvent返回true,然后dispatchTouchEvent继续派发move或者up事件,循环,直到onTouchEvent处理up事件时调运onClick事件,完事返回true,同时dispatchTouchEvent返回true;一次完整的View事件派发流程结束。 #### 4-2-2 简单修改onTouchEvent返回值为true 将TestButton类的onTouchEvent方法修改如下,其他和基础代码保持不变: ~~~ @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "onTouchEvent-- action="+event.getAction()); return true; } ~~~ 点击Button打印如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da781ca33.jpg "") 可以发现,当自定义了控件(View)的onTouchEvent直接返回true而不调运super方法时,事件派发机制如同4.2.1类似,只是最后up事件没有触发onClick而已(因为没有调用super)。 所以可想而知,如果TestButton类的onTouchEvent修改为如下: ~~~ @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "onTouchEvent-- action="+event.getAction()); super.onTouchEvent(event); return true; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da783178c.jpg "") 整个派发机制和4.2.1完全类似。 #### 4-2-3 简单修改onTouchEvent返回值为false 将TestButton类的onTouchEvent方法修改如下,其他和基础代码保持不变: ~~~ @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "onTouchEvent-- action="+event.getAction()); return false; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da784413a.jpg "")   你会发现如果onTouchEvent返回false(也即dispatchTouchEvent一旦返回false将不再继续派发其他action,立即停止派发),这里只派发了down事件。至于后面触发了LinearLayout的touch与click事件我们这里不做关注,下一篇博客会详细解释为啥(其实你可以想下的,LinearLayout是ViewGroup的子类,你懂的),这里你只用知道View的onTouchEvent返回false会阻止继续派发事件。 同理修改如下: ~~~ @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "onTouchEvent-- action="+event.getAction()); super.onTouchEvent(event); return false; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da78585d7.jpg "") #### 4-2-4 简单修改dispatchTouchEvent返回值为true 将TestButton类的dispatchTouchEvent方法修改如下,其他和基础代码保持不变: ~~~ @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "dispatchTouchEvent-- action=" + event.getAction()); return true; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da786b345.jpg "") 你会发现如果dispatchTouchEvent直接返回true且不调运super任何事件都得不到触发。 继续修改如下呢?  将TestButton类的dispatchTouchEvent方法修改如下,其他和基础代码保持不变: ~~~ @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "dispatchTouchEvent-- action=" + event.getAction()); super.dispatchTouchEvent(event); return true; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da787d54b.jpg "") 可以发现所有事件都可以得到正常派发,和4.2.1类似。 #### 4-2-5 简单修改dispatchTouchEvent返回值为false 将TestButton类的dispatchTouchEvent方法修改如下,其他和基础代码保持不变: ~~~ @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "dispatchTouchEvent-- action=" + event.getAction()); return false; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da788f493.jpg "") 你会发现事件不进行任何继续触发,关于点击Button触发了LinearLayout的事件暂时不用关注,下篇详解。 继续修改如下呢?  将TestButton类的dispatchTouchEvent方法修改如下,其他和基础代码保持不变: ~~~ @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "dispatchTouchEvent-- action=" + event.getAction()); super.dispatchTouchEvent(event); return false; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da78a22b4.jpg "")   你会发现结果和4.2.3的第二部分结果一样,也就是说如果dispatchTouchEvent返回false事件将不再继续派发下一次。 #### 4-2-6 简单修改dispatchTouchEvent与onTouchEvent返回值 修改dispatchTouchEvent返回值为true,onTouchEvent为false: 将TestButton类的dispatchTouchEvent方法和onTouchEvent方法修改如下,其他和基础代码保持不变: ~~~ @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "dispatchTouchEvent-- action=" + event.getAction()); super.dispatchTouchEvent(event); return true; } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "onTouchEvent-- action=" + event.getAction()); super.onTouchEvent(event); return false; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da78b40f5.jpg "") 修改dispatchTouchEvent返回值为false,onTouchEvent为true: 将TestButton类的dispatchTouchEvent方法和onTouchEvent方法修改如下,其他和基础代码保持不变: ~~~ @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.i(null, "dispatchTouchEvent-- action=" + event.getAction()); super.dispatchTouchEvent(event); return false; } @Override public boolean onTouchEvent(MotionEvent event) { Log.i(null, "onTouchEvent-- action=" + event.getAction()); super.onTouchEvent(event); return true; } ~~~ 点击Button如下:  ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-16_56e8da78c71ff.jpg "") 由此对比得出结论,dispatchTouchEvent事件派发是传递的,如果返回值为false将停止下次事件派发,如果返回true将继续下次派发。譬如,当前派发down事件,如果返回true则继续派发up,如果返回false派发完down就停止了。 #### 4-1 总结 这个例子组合了很多种情况的值去验证上面源码的分析,同时也为自定义控件打下了基础。仔细理解这个例子对于View的事件传递就差不多了。 ### 5 总结View触摸屏事件传递机制 上面例子也测试了,源码也分析了,总得有个最终结论方便平时写代码作为参考依据呀,不能每次都再去分析一遍源码,那得多蛋疼呢! 综合得出Android View的触摸屏事件传递机制有如下特征: 1. 触摸控件(View)首先执行dispatchTouchEvent方法。 1. 在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent中执行,下面会分析)。 1. 如果控件(View)的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运onTouchEvent,dispatchTouchEvent返回值与onTouchEvent返回一样。 1. 如果控件不是enable的设置了onTouch方法也不会执行,只能通过重写控件的onTouchEvent方法处理(上面已经处理分析了),dispatchTouchEvent返回值与onTouchEvent返回一样。 1. 如果控件(View)是enable且onTouch返回true情况下,dispatchTouchEvent直接返回true,不会调用onTouchEvent方法。 1. 当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action(也就是说dispatchTouchEvent返回true才会进行下一次action派发)。 【工匠若水 [http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober)】 关于上面的疑惑还有ViewGroup事件派发机制你可以继续阅读下一篇博客[《Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)》](http://blog.csdn.net/yanbober/article/details/45912661),以便继续分析View之外的ViewGroup事件传递机制。
';

前言

最后更新于:2022-04-01 09:53:02

> 原文出处:[android源码解析](http://blog.csdn.net/column/details/androidframeworks.html) 作者:[qq_23547831](http://blog.csdn.net/qq_23547831) **本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!** # android源码解析 > 主要用于解析android framework层源码,干货满满;
';