一、初识ViewRoot和DecorView

ViewRoot类对应ViewRootImpl类,它是连接WindowManage和DecorView的纽带。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecoView建立关联。
View的绘制流程是从ViewRoot的performTraversals方法开始的,经过measure、layout、draw三个过程将一个View绘制出来。
Measure过程决定了View的宽和高,Measure完成后,一般情况下可以通过getMeasureWidth和getMeasureHeight获取View测量后的高,特殊情况除外;Layout过程决定了View四个顶点的坐标和实际的View的宽高;Draw过程则决定了View的显示。
DecorView作为顶级View,当我们setContentView时,布局添加到了id为content的FrameLayout中,以下方式可以得到content和我们设置的view:

ViewGroup content  = (ViewGroup)findViewById(android.R.id.content);View view = content.getChildAt(0);

二、理解MeasureSpec

  1. MeasureSpec
    MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表specSize,前者指测量模式,后者指某种测量模式下的规格大小。

    private static final int MODE_SHIFT = 30;private static final int MODE_MASK  = 0x3 << MODE_SHIFT;public static final int UNSPECIFIED = 0 << MODE_SHIFT;public static final int EXACTLY     = 1 << MODE_SHIFT;public static final int AT_MOST     = 2 << MODE_SHIFT;public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,                                      @MeasureSpecMode int mode) {     if (sUseBrokenMakeMeasureSpec) {            return size + mode;        } else {            return (size & ~MODE_MASK) | (mode & MODE_MASK);        }    }        public static int getMode(int measureSpec) {        //noinspection ResourceType        return (measureSpec & MODE_MASK);    }     public static int getSize(int measureSpec) {        return (measureSpec & ~MODE_MASK);    }

    MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,并提供了打包解包方法。一组SpecMode和SpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize。

    SpecMode有三类:

    1. UNSPECIFIED
      父容器不对View有任何限制,要多大给多大,一般用于系统内部,表示一种测量的状态。
    2. EXACTLY
      父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值,对应LayoutParams中的match_parent和具体的数值两种模式。
    3. AT_MOST
      指定一个可用大小即SpecSize,View的大小不能大于这个值,对应LayoutParams中的wrap_content。
  2. MeasureSpec和LayoutParams的对应关系
    对于DecorView,其MeasureSpec由其窗口的尺寸和其自身的LayoutParams来共同确定;对于普通view,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure就可以确定View的测量宽/高。
    DecorView的MeasureSpec产生过程根据LayoutParams中的宽和高来划分:

    • LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小;
    • LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小;
    • 固定大小:精确模式,大小为LayoutParams指定的大小;

    普通View来说,针对不同的父容器和View本身不同的LayoutParams,View就可以有多种MeasureSpec。

    1. 当View采用固定宽高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循LayoutParams的大小。
    2. 当View的宽高是match_parent时,如果父容器的模式是精确模式,那么view也是精确模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。
    3. 当view的宽高是wrap_content时,不管父容器的模式是精确还是最大化吗,View的模式总是最大化并且大小不能超过父容器的剩余空间。

三、View的工作流程

  1. measure过程

    View的measure过程

    View的measure方法是一个final类型的方法,意味着子类不能重写这个方法。View的measure方法中总会去调用View的onMeasure方法。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}

    我们只需要看这个getDefaultSize方法:

    public static int getDefaultSize(int size, int measureSpec) {    int result = 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;}

    从上面源码可以看出,一般我们只需要分析AT_MOST和EXACTLY两种情况,getDefaultSize返回的大小就是measureSpec中的specSize,而这个specSize就是View测量后的大小,View的最终大小是在layout阶段确定的,几乎所有情况下View的测量大小和最终大小是相等的。

    UNSPECIFIED这种情况,一般用于系统内部的测量过程,View的大小就是getDefaultSize第一个参数size,即宽高分别为getSuggestedMinimumWidth()和
    getSuggestedMinimumHeight()这两个方法的返回值,源码如下:

    protected int getSuggestedMinimumWidth() {    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());} protected int getSuggestedMinimumHeight() {    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());}

    从上面方法可以看出,getSuggestedMinimumWidth和getSuggestedMinimumHeight方法实现原理一样。从getSuggestedMinimumWidth方法里面可以看出,如果没有设置背景那么view的宽度为mMinWidth,而mMinWidth对应于android:minWidth这个属性所指定的值,因此view的宽度即为android:minWidth属性所指定的值,如果没有指定则默认为0;如果指定了背景,那么View的宽度就是max(mMinWidth, mBackground.getMinimumWidth()),我们看一下mBackground.getMinimumWidth(),Drawable的getMinimumWidth方法,如下所示:

    public int getMinimumWidth() {    final int intrinsicWidth = getIntrinsicWidth();    return intrinsicWidth > 0 ? intrinsicWidth : 0;}

    这个方法返回的就是Drawable的原始宽度,前提是有原始宽度,否则返回0。
    getSuggestedMinimumWidth这个方法逻辑如下:如果view没有设置了背景,那么返回android:minWidth这个属性所指定的值,这个值可以为0;如果View设置了背景,则返回android:minWidth和背景最小宽度中的最大值。getSuggestedMinimumWidth和getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情况下的测量宽高。

    从getDefaultSize方法的实现来看,View的宽高由specSize决定,所以我们一般自定义控件的时候需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content相当于match_parent。

    ViewGroup的measure过程

    ViewGroup除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个过程。ViewGroup是一个抽象类,提供了一个叫measureChildren的方法,如下:

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {    final int size = mChildrenCount;    final View[] children = mChildren;    for (int i = 0; i < size; ++i) {        final View child = children[i];        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {            measureChild(child, widthMeasureSpec, heightMeasureSpec);        }    }}

    上面方法会对每一个子元素进行measure,measureChild这个方法如下:

    protected void measureChild(View child, int parentWidthMeasureSpec,        int parentHeightMeasureSpec) {    final LayoutParams lp = child.getLayoutParams();    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            mPaddingLeft + mPaddingRight, lp.width);    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,            mPaddingTop + mPaddingBottom, lp.height);    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

    上面主要是取出子元素的LayoutParams,然后在通过getChildMeasureSpec来创建子元素的MeasureSpec,将MeasureSpec直接传递给View的measure方法来进行测量。在ViewGroup没有定义测量的具体过程,不同的ViewGroup子类有不同的布局特性,需要各个子类去具体实现,下面分析LinearLayout的onMeasure的具体实现。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    if (mOrientation == VERTICAL) {        measureVertical(widthMeasureSpec, heightMeasureSpec);    } else {        measureHorizontal(widthMeasureSpec, heightMeasureSpec);    }}

    上述代码主要针对不同方向实现不同的测量,看一下竖直方向上的布局:

    // See how tall everyone is. Also remember max width.    for (int i = 0; i < count; ++i) {        final View child = getVirtualChildAt(i);        ...// Determine how big this child would like to be. If this or// previous children have given a weight, then we allow it to// use all available space (and we will shrink things later// if needed).final int usedHeight = totalWeight == 0 ? mTotalLength : 0;measureChildBeforeLayout(child, i, widthMeasureSpec, 0,                    heightMeasureSpec, usedHeight);final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));

    系统会遍历子元素对每个子元素执行measureChildBeforeLayout这个方法,这个方法内部还是会调用子元素的measure方法,这样各个子元素就依次开始进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方向上的初步高度。每测量一个元素,mTotalLength就会增加,增加的部分主要包括了子元素的高度以及子元素在竖直方向上的margin等。子元素测量完毕,LinearLayout会测量自己的大小,如下:

    // Add in our padding    mTotalLength += mPaddingTop + mPaddingBottom;    int heightSize = mTotalLength;    // Check against our minimum height    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());    // Reconcile our calculated size with the heightMeasureSpec    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);    heightSize = heightSizeAndState & MEASURED_SIZE_MASK;    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),            heightSizeAndState);

    当子元素测量完毕后,LinearLayout会根据子元素的情况来测量自己的大小。如果在布局中高度采用的match_parent或者具体的数值,那么则和View的测量过程一致,即高度为specSize;如果高度采用的是wrap_content,高度就是所有子元素所占用的高度总和,但是不能超过它的父容器的剩余空间,最终高度还要考虑其在竖直方向的padding,如下源码所示:

    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {    final int specMode = MeasureSpec.getMode(measureSpec);    final int specSize = MeasureSpec.getSize(measureSpec);    final int result;    switch (specMode) {        case MeasureSpec.AT_MOST:            if (specSize < size) {                result = specSize | MEASURED_STATE_TOO_SMALL;            } else {                result = size;            }            break;        case MeasureSpec.EXACTLY:            result = specSize;            break;        case MeasureSpec.UNSPECIFIED:        default:            result = size;    }    return result | (childMeasuredState & MEASURED_STATE_MASK);}

    View的measure完成以后,通过getMeasureWidth/Height方法就可以正确的获取到view的测量宽高。但是在某些极端情况下,系统可能需要多次measure才能获取到最终的测量宽高,最好在onLayout方法中获取View的测量宽高或者最终宽高。

    在Activity里面获取某一个View的宽高:

    1. Activity/View#onWindowFocusChanged
      View初始化完毕,可以获取宽高,不过会被调用多次,在Activity的窗口得到焦点和失去焦点的时候均会被调用一次,当Activity继续执行和暂停执行的时候,onWindowFocusChanged均会被调用。

      public void onWindowFocusChanged(boolean hasFocus) {    super.onWindowFocusChanged(hasFocus);    if(hasFocus){        int width = view.getMeasuredWidth();        int height = view.getMeasuredHeight();    }}
    2. view.post(runnable)
      通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnabe的时候,View已经初始化好了。

      protected void onStart(){    super.onStart();    view.post(new Runnable() {        @Override        public void run() {            int width = view.getMeasuredWidth();            int height = view.getMeasuredHeight();        }    });}
    3. ViewTreeObserver
      使用ViewTreeObserver的众多回调也可以完成这个功能,比如使用OnGlobalLayoutListener这个接口,随着View树的状态发生改变,onGloballayout会被调用多次,如下:

      ViewTreeObserver observer = view.getViewTreeObserver();    observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {        @Override        public void onGlobalLayout() {            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);            int width = view.getMeasuredWidth();            int height = view.getMeasuredHeight();        }    });

      view.measure(int widthMeasureSpec, int heightMeasureSpec)
      通过手动对view进行measure来得到具体View的宽高,需要根据LayoutParams来分:

      • match_parent
        无法测出具体宽高,无法知道父容器的剩余空间。
      • 具体数值(dp/px)
        比如宽和高都是100px,如下measure

        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);    view.measure(widthMeasureSpec,heightMeasureSpec);
      • wrap_content
        如下measure

         int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);    int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);    view.measure(widthMeasureSpec,heightMeasureSpec);

        注意(1 << 30)-1,View的尺寸使用30位二进制表示,最大是30个1(2^30-1),在最大化模式下,用View理论上能支持的最大值去构造MeasureSpec是合理的。

  2. layout过程
    Layout的作用是ViewGroup用来确定子元素的位置,当viewGroup的位置被确定后,在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout又会被调用。layout确定view本身位置,onLayout方法确定所有子元素的位置,源码如下:

    public void layout(int l, int t, int r, int b) {    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;    }    int oldL = mLeft;    int oldT = mTop;    int oldB = mBottom;    int oldR = mRight;    boolean changed = isLayoutModeOptical(mParent) ?            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {        onLayout(changed, l, t, r, b);        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnLayoutChangeListeners != null) {            ArrayList listenersCopy =                    (ArrayList)li.mOnLayoutChangeListeners.clone();            int numListeners = listenersCopy.size();            for (int i = 0; i < numListeners; ++i) {                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);            }        }    }    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;}

    首先通过setFrame方法来设定View的四个顶点的位置,初始化mLeft、mRight、mTop、mBottom四个值,四个顶点确定,View在父容器中的位置也就确定了;接着调用onLayout方法,父容器用来确定子元素位置,onLayout的实现和布局有关,我们看一下LinearLayout的onLayout源码:

    void layoutVertical(int left, int top, int right, int bottom) {...final int count = getVirtualChildCount();for (int i = 0; i < count; i++) {        final View child = getVirtualChildAt(i);        if (child == null) {            childTop += measureNullChild(i);        } else if (child.getVisibility() != GONE) {            final int childWidth = child.getMeasuredWidth();            final int childHeight = child.getMeasuredHeight();            final LinearLayout.LayoutParams lp =                    (LinearLayout.LayoutParams) child.getLayoutParams();        ...     if (hasDividerBeforeChildAt(i)) {                childTop += mDividerHeight;            }            childTop += lp.topMargin;            setChildFrame(child, childLeft, childTop + getLocationOffset(child),                    childWidth, childHeight);            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);            i += getChildrenSkipCount(child, i);

    我们这里还是分析在竖直方向上的布局,这里会遍历所有子元素并调用setChildFrame方法来为子元素指定对应的位置,其中childTop会逐渐增大,非常符合LinearLayout在竖直方向上的特性。setChildFrame调用子元素的layout方法而已,这样当父元素在layout方法中完成自己的定位后,就通过onLayout方法去调用子元素的layout方法,子元素又会通过自己的layout方法来确定自己的位置,一层一层地传递下去就完成了整个view树的layout过程。setChildFrame源码如下:

    private void setChildFrame(View child, int left, int top, int width, int height) {            child.layout(left, top, left + width, top + height);}

    方法中的width和height实际上就是子元素的测量宽高。
    而在layout方法中会通过setFrame去设置子元素的四个顶点的位置,有如下赋值语句:

            mLeft = left;        mTop = top;        mRight = right;        mBottom = bottom;

    View的测量宽高和最终宽高有什么不同?

    public final int getWidth() {    return mRight - mLeft;}
    public final int getHeight() {    return mBottom - mTop;}

    从上面getWidth和getHeight的源码结合mLeft、mRight、mTop、mBottom这四个变量的赋值过程来看,getWidth的返回值就是View的测量宽度,getHeight同理。在View的默认实现中,View的测量宽高和最终宽高是相等的,测量宽高是形成于View的measure过程,而最终宽高形成于View的layout过程,赋值时机不一样,一般情况下两者是相等的,但在有些情况下不一致,举个例子:

    protected void onLayout(boolean changed, int l, int t, int r, int b) {    super.layout(l, t, r + 100, b + 100);}

    上述代码则会导致在任何情况下View的最终宽高总是比测量宽高大100px;另外一种情况则是View需要多次measure才能确定自己的测量宽高,在前几次的测量过程中,得出的测量宽高和最终宽高有可能不一致,但是最终来说,测量宽高和最终宽高还是一样的。

  3. draw过程
    Draw的作用是将View绘制到屏幕上面,View的绘制过程遵循如下几步:

    • 绘制背景background.draw(canvas)
    • 绘制自己(onDraw)
    • 绘制children(dispatchDraw)
    • 绘制装饰(onDrawScrollBars)

    可以通过draw方法的源码看出:

    public void draw(Canvas canvas) {    final int privateFlags = mPrivateFlags;    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;    /*     * 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    int saveCount;    if (!dirtyOpaque) {        drawBackground(canvas);    }    // skip step 2 & 5 if possible (common case)    final int viewFlags = mViewFlags;    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;    if (!verticalEdges && !horizontalEdges) {        // Step 3, draw the content        if (!dirtyOpaque) onDraw(canvas);        // Step 4, draw the children        dispatchDraw(canvas);        // Overlay is part of the content and draws beneath Foreground        if (mOverlay != null && !mOverlay.isEmpty()) {            mOverlay.getOverlayView().dispatchDraw(canvas);        }        // Step 6, draw decorations (foreground, scrollbars)        onDrawForeground(canvas);        // we're done...        return;    }

    View的绘制过程是通过dispatchDraw来实现的,会遍历所有元素的draw方法,draw事件就一层一层传递下去。View有一个特殊的方法setWillNotDraw:

    public void setWillNotDraw(boolean willNotDraw) {    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}

    如果一个view不需要绘制任何内容,那么设置这个标记为true后,系统会进行相应的优化。默认情况下,View没有启用这个标记位,但是ViewGroup会启用这个标记位。一般我们的自定义控件继承于ViewGroup本身并不具备绘制功能时,就可以开启这个标记位便于系统进行后续优化。当知道ViewGroup需要通过onDraw来绘制内容时,我们需要显式的关闭WILL_NOT_DRAW 这个标记位。

四、自定义View

  1. 自定义view的分类

    • 继承View重写onDraw方法
      重写onDraw方法实现一些不顾则的图形
    • 继承ViewGroup派生特殊的Layout
      主要用于实现自定义布局
    • 继承特定的View(比如TextView)
      一般用于扩展某种已有的View的功能
    • 集成特定的ViewGroup(比如LinearLayout)
      当某种效果看起来像几种view组合在一起的时候,可以采用这种方法。
  2. 自定义view须知

    • 让View支持wrap_content
    • 如果有必要,View支持padding
      直接继承View的控件,如果不在draw方法中处理padding,那么padding属性不起作用的;直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然导致padding和子元素的margin失效。
    • 尽量不要在View中使用Handler,没必要
      View内部本身提供了post系列的方法,完全可以替代Handler
    • View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow
      有线程或者动画需要停止时,那么onDetachedFromWindow是一个很好的时机。当包含此View的Activity退出或者当前View被Remove时,View的onDetachedFromWindow方法会被调用;相对应的一个方法是onAttachedToWindow,当包含此View的Activity启动时,View的onAttachedToWindow会被调用,当view变得不可见时我们也需要停止线程和动画。
    • View带有滑动嵌套情形时,需要处理好滑动冲突
  3. 自定义View示例
    自定义view的示例可以在这里找到:
    自定义CricleView
    继承ViewGroup派生的特殊layout

关于View事件体系,有几篇比较好的博文,值得一读:

Android应用层View绘制流程与源码分析
Android中View的量算、布局及绘图机制
源码解析Android中View的measure量算过程
源码解析Android中View的layout布局过程

更多相关文章

  1. Android(安卓)studio把一段代码快速提取,放在自己定义方法里面的
  2. Retrofit2源码分析(2) CallAdapter详解
  3. Android(安卓)EditText 密码输入框可见性设置。
  4. Android(安卓)2.0新增类ExifInterface使用
  5. RelativeLayout相对布局介绍及属性介绍
  6. Android(安卓)学习路线
  7. Android待机状态更新
  8. Android之Intent显示和隐式调用
  9. android自动加载模块.ko文件的小方法

随机推荐

  1. android studio for android learning (九
  2. Android之SlidingDrawer抽屉效果
  3. Android(安卓)TV Audio基本框架及启动流
  4. Android异步加载图像小结
  5. [android]android自动化测试八之让你的AV
  6. Android异步处理系列文章
  7. Android(安卓)开发中 Parcel存储类型和数
  8. Android图形选择 - Selector
  9. android的binder机制
  10. 【Android】Android(安卓)监听apk安装替