全网最全,精心整理系列(2)

614 阅读28分钟

一、精心整理,备战2021(1)

二、精心整理,备战2021(2)

13. View绘制流程

绘制从根视图ViewRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个View控件负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作。performTraversals()的核心代码如下:

private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //执行测量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //执行布局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //执行绘制流程
    performDraw();
}

preformLayoutperformDraw的传递流程和performMeasure是类似的,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的

  • 理解MeasureSpec

MeasureSpec表示的是一个32位的整形值,它的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSizeMeasureSpecView类的一个静态内部类,用来说明应该如何测量这个View

MeasureSpec通过将SpecModeSpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包makeMeasureSpec和解包的方法getMode / getSize源码如下:

    // 根据指定的大小和模式创建一个MeasureSpec
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
    
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
  • ViewGroup#addView(view),再没有给子View设置LayoutParams,,那么LayoutParams是何时生成生成的
public void addView(View child, int index) {
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    LayoutParams params = child.getLayoutParams();
    if (params == null) {
        params = generateDefaultLayoutParams();
        if (params == null) {
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
        }
    }
    addView(child, index, params);
}

可以看出,如果view没有设置过LayoutParams,就通过generateDefaultLayoutParams()方法生成一个

protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}

默认的LayoutParams中宽高给的都是wrap_content

  • xml添加View
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        
    View result = root;

    // Look for the root node.
    int type;
    // 寻找根节点
    while ((type = parser.next()) != XmlPullParser.START_TAG &&
            type != XmlPullParser.END_DOCUMENT) {
        // Empty
    }

    final String name = parser.getName();
    if (TAG_MERGE.equals(name)) {
        rInflate(parser, root, inflaterContext, attrs, false);
    } else {
        // 1
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
        ViewGroup.LayoutParams params = null;
        // 2、3
        if (root != null) {
            // Create layout params that match root, if supplied
            params = root.generateLayoutParams(attrs);
            if (!attachToRoot) {
                temp.setLayoutParams(params);
            }
        }

        // Inflate all children under temp against its context.
        // 4
        rInflateChildren(parser, temp, attrs, true);

        if (root != null && attachToRoot) {
            root.addView(temp, params);
        }
        if (root == null || !attachToRoot) {
            result = temp;
        }
    }
    // 5
    return result;
}

1、先通过createViewFromTag方法创建一个根View对象temp出来。

2、如果root不为空,就通过root.generateLayoutParams(attrs)方法将temp的width和height属性转化成LayoutParams设置给temp。

3、如果root为空,表示temp的父布局不确定,这里也没有必要给设置LayoutParams了,等到它添加进别的布局时,就会设置LayoutParams参数了。

4、通过rInflateChildren方法,将temp的子View都添加进来。

5、返回根view(temp是必定包含在根view中的)。

  • getWidth()与getMeasuredWidth()的区别

一般在自定义控件的时候getMeasuredWidth/getMeasuredHeight它的赋值在View的setMeasuredDimension中,所以可以在onMeasure方法中看到利用getMeasuredWidth/getMeasuredHeight初始化别的参数。而getWidth/getHeight一直在onLayout完成后才会被赋值。一般情况下,如果都完成了赋值,两者值是相同的.

  • onMeasure和onLayout为何会执行两次或多次

private void performTraversals() {
    ......
    boolean newSurface = false;
    //决定是否让newSurface为true,导致后边是否让performDraw无法被调用,而是重新scheduleTraversals
    if (!hadSurface) {
        if (mSurface.isValid()) {
            // If we are creating a new surface, then we need to
            // completely redraw it.  Also, when we get to the
            // point of drawing it we will hold off and schedule
            // a new traversal instead.  This is so we can tell the
            // window manager about all of the windows being displayed
            // before actually drawing them, so it can display then
            // all at once.
            newSurface = true;
                    .....
        }
    }
            ......
    if (!cancelDraw && !newSurface) {
        if (!skipDraw || mReportNextDraw) {
            ......
            performDraw();
        }
    } else {
        //viewVisibility是wm.add的那个View的属性,View的默认值都是可见的
        if (viewVisibility == View.VISIBLE) {
            // Try again
            //再执行一次 scheduleTraversals,也就是会再执行一次performTraversals
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }
    ......
}

View初始化的过程中,系统调用了两次performTraversals函数,第一次performTraversals函数导致了View的前两次的onMeasure函数调用和第一次的onLayout函数调用。后一次的performTraversals函数导致了最后的onMeasure,onLayoutonDraw函数的调用

  • View#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;

        // 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);

            drawAutofilledHighlight(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);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }

draw过程的大致步骤:

  1. 绘制 backgroud(drawBackground)
  2. 如果颜色变淡,保存canvas的layer,来准备fading(不是必要的步骤)
  3. 绘制view的content(onDraw方法)
  4. 绘制children(dispatchDraw方法)
  5. 如果需要的话,有绘制颜色变淡操作,还原layer(不是必要的步骤)
  6. 绘制装饰器、比如scrollBar(onDrawForeground)
  • View中onDraw是一个空实现,由各种类型的View自定义实现

  • dispatchDraw()负责控制子View绘制的,在View是空实现,但是在ViewGroup中有具体的实现;

  • 继承ViewGroup的自定义控件如何调用onDraw()

  1. 通过setWillNotDraw方法设置WILL_NOT_DRAW 为false
  2. 设置背景setBackground
  • 自定义View有哪几种方式
  1. 继承View重写onDraw方法

主要用于实线不规则的效果,即这种效果不方便通过布局的组合方式来实现。相当于就是得自己“画”了。采用这种方式需要自己支持wrap_content,padding也需要自己处理

  1. 继承ViewGroup派生特殊的Layout

主要用于实现自定义的布局,看起来很像几种View组合在一起的时候,可以使用这种方式。这种方式需要合适地处理ViewGroup的测量和布局,并同时处理子元素的测量和布局过程。比如自定义一个自动换行的LinerLayout等。

  1. 继承特定的View,比如TextView

这种方法主要是用于扩展某种已有的View,增加一些特定的功能。这种方法比较简单,也不需要自己支持wrap_content和padding。

  1. 继承特定的ViewGroup,比如LinearLayout

这种方式也比较常见,和上面的第2种方法比较类似,第2种方法更佳接近View的底层。

  • 自定义View需要注意的地方
  1. 让View支持wrap_content

直接继承View和ViewGroup的控件需要在onMeasure方法中处理wrap_content的方法。处理方法是在wrap_content的情况下设置一个固定的尺寸 2. 让View支持padding

直接继承View的控件需要在onDraw方法中处理padding,否则用户设置padding属性就不会起作用。直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效。

  1. 尽量不要在View中使用Handler

View中已经提供了post系列方法,完全可以替代Handler的作用

  1. View中如果有线程或者动画,需要及时停止

在View的onDetachedFromWindow方法可以停止线程和动画,因为当View被remove或是包含此View的Activity退出时,就会调用View的onDetachedFromWindow方法。如果不处理的话很可能会导致内存泄漏

  1. View带有滑动嵌套时,需要处理好滑动冲突问题

  2. 在View的onDraw方法中不要创建太多的临时对象,也就是new出来的对象。因为onDraw方法会被频繁调用,如果有大量的临时对象,就会引起内存抖动,影响View的效果

14. 事件分发

Touch事件相关细节被封装成MotionEvent对象

主要发生的Touch事件大致分为以下四种:

MotionEvent.ACTION_DOWN:按下事件(所有事件的开始)

MotionEvent.ACTION_MOVE:滑动事件

MotionEvent.ACTION_CANCEL:非人为原因结束本次事件

MotionEvent.ACTION_UP:抬起事件(与DOWN对应)

一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View

  • 源码分析
  1. Activity的事件分发

当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发

public boolean dispatchTouchEvent(MotionEvent ev) {
    //一般事件列开始都是DOWN,所以这里基本是true
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    //关注点1
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //关注点2
    return onTouchEvent(ev);
}

关注点1

getWindow()可以得到一个Window对象,Window类是抽象类,且PhoneWindowWindow类的唯一实现类

superDispatchTouchEvent(ev)是抽象方法,通过PhoneWindow类中看一下superDispatchTouchEvent()的作用

@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
    //mDecor是DecorView的实例
    //DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
}

接下来看mDecor.superDispatchTouchEvent(event):

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
    //DecorView继承自FrameLayout
    //那么它的父类就是ViewGroup,而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()
}

从这开始ViewGroup的事件分发了

关注点2

当viewGroup分发事件失败,Activity将会自己处理

  • 2.ViewGroup的事件分发

ViewGroupdispatchTouchEvent()源码分析,该方法比较复杂,截取几个重要的逻辑片段进行介绍,来解析整个分发流程。

// 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器

final boolean intercepted;
if(actionMasked==MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null){
    //disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
    //可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
    final boolean disallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
    //默认情况下会进入该方法
    if(!disallowIntercept){
        //调用拦截方法
        intercepted=onInterceptTouchEvent(ev);
        ev.setAction(action);
    }else{
        intercepted=false;
    }
}else{
    // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
    intercepted=true;
}

这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果不是以上两种情况,即已经是MOVEUP事件了,并且之前的事件没有对象进行处理,则设置成true,开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,那么接下来的一些列事件都不会交给他处理。如果VieGrouponInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。

当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View或ViewGroup进行处理。

/* 从最底层的父视图开始遍历, ** 找寻newTouchTarget,即上面的mFirstTouchTarget ** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。 */
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 (childWithAccessibilityFocus != null) {
        if (childWithAccessibilityFocus != child) {
            continue;
        }
    childWithAccessibilityFocus = null;
    i = childrenCount - 1;
}
//如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}
 
newTouchTarget = getTouchTarget(child);
// 已经开始接收触摸事件,并退出整个循环。
if (newTouchTarget != null) {
    newTouchTarget.pointerIdBits |= idBitsToAssign;
    break;
}
 
//如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    // 获取TouchDown的时间点
    mLastTouchDownTime = ev.getDownTime();
    // 获取TouchDown的Index
    if (preorderedList != null) {
        for (int j = 0; j < childrenCount; j++) {
            if (children[childIndex] == mChildren[j]) {
                mLastTouchDownIndex = j;
                break;
            }
        }
    } else {
        mLastTouchDownIndex = childIndex;
    }
//获取TouchDown的x,y坐标
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,则mFirstTouchTarget != null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}

dispatchTransformedTouchEvent()方法实际就是调用子元素的dispatchTouchEvent()方法。 其中dispatchTransformedTouchEvent()方法的重要逻辑如下:

if (child == null) {
    handled = super.dispatchTouchEvent(event);
} else {
    handled = child.dispatchTouchEvent(event);
}

由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()。 如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环。

//添加TouchTarget,则mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;

其中在addTouchTarget(child, idBitsToAssign);内部完成mFirstTouchTarget被赋值。 如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作。 如果遍历所有子ViewViewGroup,都没有消费事件。ViewGroup会自己处理事件。

  • 3.View的事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}

第一个条件:mOnTouchListener!= null

//mOnTouchListener是在View类下setOnTouchListener方法里赋值的
public void setOnTouchListener(OnTouchListener l) {
    //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
    mOnTouchListener = l;
}

第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED

该条件是判断当前点击的控件是否enable

由于很多View默认是enable的,因此该条件恒定为true

第三个条件:mOnTouchListener.onTouch(this, event)

如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。

如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。

public boolean onTouchEvent(MotionEvent event) {
 
    final int viewFlags = mViewFlags;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // 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;
        }
    }
 
    //如果该控件是可以点击的就会进入到下两行的switch判断中去;
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                // 在经过重重判断之后,会执行到performClick()方法。
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if (!mHasPerformedLongPress) {
                        removeLongPressCallback();
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                break;
 
             ...
        }
        //如果该控件是可以点击的,就一定会返回true
        return true;
    }
    //如果该控件是不可以点击的,就一定会返回false
    return false;
}
  • 四、总结

对于ViewGroup而言,当事件分发到当前ViewGroup上面的时候,首先会调用dispatchTouchEvent方法,里面会调用onInterceptTouchEvent来判断是否要拦截当前事件,如果要拦截的话,就会调用ViewGroup自己的onTouchEvent,如果onInterceptTouchEvent返回false的话表示不拦截当前事件,那么 事件将会继续往当前ViewGroup的子View上面传递了,如果他的子ViewViewGroup的话,则重复ViewGroup事件分发过程,如 果子View就是View的话,则转到下面的View分发过程;

View事件传递过来首先当然也是执行他的dispatchTouchEvent方法了,如果我们为当前View设置了 onTouchListener监听器的话,首先就会执行他的回调方法onTouch了,这个方法的返回值将决定事件是否要继续传递下去了,如果返回 false的话,表示事件没有被消费,还会继续传递下去,如果返回true的话,表示事件已经被消费了,不再需要向下传递了;如果返回false,那么将 会执行当前View的onTouchEvent方法,如果我们为当前View设置了onLongClickListener监听器的话,则首先会执行他的 回调方法onLongClick,和onTouch方法类似,如果该方法返回true表示事件被消费,不会继续向下传递,返回false的话,事件会继续 向下传递,为了分析,我们假定返回false,如果我们设置了onClickListener监听器的话,则会执行他的回调方法onClick,该方法是 没有返回值的,所以也是我们事件分发机制中最后执行的方法了;可以注意到的一点就是只要你的当前View是clickable或者 longclickable的,View的onTouchEvent方法默认都会返回true,也就是说对于事件传递到View上来说,系统默认是由 View来消费事件的,但是ViewGroup就不是这样了;

事件分发机制需要注意的几点:

(1):如果说除Activity之外的View都没有消费掉DOWN事件的话,那么事件将不再会传递到Activity里面的子View了,将直接由Activity自己调用自己的onTouchEvent方法来处理了;

(2):一旦一个ViewGroup决定拦截事件,那么这个事件序列剩余的部分将不再会由该ViewGroup的子View去处理了,即事件将在此 ViewGroup层停止向下传递,同时随后的事件序列将不再会调用onInterceptTouchEvent方法了;

(3):如果一个View开始处理事件但是没有消费掉DOWN事件,那么这个事件序列随后的事件将不再由该View来处理,通俗点讲就是你自己没能力就别瞎BB,要不以后的事件就都不给你了;

(4):View的onTouchEvent方法是否执行是和他的onTouchListener回调方法onTouch的返回值息息相关 的,onTouch返回true,onTouchEvent方法不执行;onTouch返回false,onTouchEvent方法执行,因为 onTouchEvent里面会执行onClick,所以造成了onClick是否执行和onTouch的返回值有了关系;

事件分发机制: www.jianshu.com/p/a4f82b7a4…
Android事件分发总结

事件传播(默认):

触摸事件按下

1 Activity->dispatchTouchEvent

2 decorView->dispatchTouchEvent 不做详细分析因为我们开发者控制不到

3 decorView->onInterceptTouchEvent

4 开发者的ViewGroup->dispatchTouchEvent

5 开发者的ViewGroup->onInterceptTouchEvent

6 View->dispatchTouchEvent

7 View->onTouchEvent

8 ViewGroup->onTouchEvent

9 decorView->onTouchEvent

10 activity->onTouchEvent

触摸事件移动(因为上面的没有消费所以直接在Activity中消费)

11 Activity->dispatchTouchEvent

12 decorView->dispatchTouchEvent

(不会触发onInterceptTouchEvent因为内部`mFirstTouchTarget·为空)

13 activity->onTouchEvent

触摸事件抬起(因为上面的没有消费所以直接在Activity中消费)

14 Activity->dispatchTouchEvent

15 decorView->dispatchTouchEvent

(不会触发onInterceptTouchEvent因为内部mFirstTouchTarget为空)

16 activity->onTouchEvent

备注:

1 如果任意ViewGroup中如果onInterceptTouchEvent返回了一次true那么在本触摸事件的后序事件不会在调用onInterceptTouchEvent进行判断。假设触摸事件为

①按下 ②移动 ③移动 ④松开

假设在①按下事件调onInterceptTouchEvent 返回了false ②移动事件调用 onInterceptTouchEvent返回了true 那么 ③移动 和 ④松开不会调用到onInterceptTouchEvent

2.如果ViewGroup在触摸按下的时候dispatchTouchEvent返回false那么dispatchTouchEvent收不到后序事件。

①按下 ②移动 ③移动 ④松开

假设上面的四个事件①返回了false ,②③④将收不到通知。

3 如果ViewGroup在触摸按下dispatchTouchEvent的时候返回true,会收到后序事件,哪怕你在移动事件返回了false依然会继续收到后序事件,只要你在按下事件返回了true

①按下 ②移动 ③移动 ④松开

假设上面的四个事件①返回了true ,②返回了false,③和 ④你依然会接受到事件通知。

4 ViewGroupdispatchTouchEvent会调用onInterceptTouchEvent 如果返回true那么会调用自身父类的ViewdispatchTouchEvent 也就是直接调用自身的onTouchEvent 。如果onInterceptTouchEvent返回false 交给子布局类的dispatchTouchEvent决定其自身的dispatchTouchEvent返回值。如果子布局类的dispatchTouchEvent返回false,那么调用ViewGroup的父类(也就是ViewGroup的父类View,简单点就是super.dispatchTouchEvent)的dispatchTouchEvent决定返回值。

5 中途拦截返回true情况。假设有如下情况

①按下 ②移动 ④松开

ViewdispatchTouchEvent直接返回了super.dispatchTouchEvent(ev) || true ViewGroup的onInterceptTouchEvent方法如果是移动事件那么返回true,其它触摸事件false

那么事件序列为

触摸事件按下

1 Activity->dispatchTouchEvent

2 decorView->dispatchTouchEvent

3 decorView->onInterceptTouchEvent

4 开发者的ViewGroup->dispatchTouchEvent 5 开发者的ViewGroup->onInterceptTouchEvent 返回false

6 View->dispatchTouchEvent 返回true

7 View->onTouchEvent

触摸事件移动

8 Activity->dispatchTouchEvent

9 decorView->dispatchTouchEvent

10 decorView->onInterceptTouchEvent

11 viewGroup->onInterceptTouchEvent 此时返回了true ,因为返回了true下次后续事件不会调用,注意dispatchTouchEvent不影响后序事件接收

12 View->dispatchTouchEvent 会受到一个cancel的触摸事件注意不是移动事件

13 View->onTouchEvent 会受到一个cancel的触摸事件注意不是移动事件。如果有监听点击事件此处也不会当抬起事件处理,也就是你设置了监听点击事件的将不会受到回调

触摸事件抬起(因为上面的没有消费所以直接在Activity中消费)

14 Activity->dispatchTouchEvent

15 decorView->dispatchTouchEvent

16 decorView->onInterceptTouchEvent

17 viewGroup->dispatchTouchEvent 不会调用拦截方法了哦。拦截方法返回了true后序事件不会在拦截判断了

18 viewGroup->onTouchEvent 看清楚了哦

6 ViewGroup 的在受到按下事件的时候dispatchTouchEvent 返回false 后序事件将接收不到。假设按下返回true 移动的事件返回false 依然能接收到后序事件

7 ViewGroup 的在受到按下事件的时候dispatchTouchEvent 返回true 且不调用super.dispatchTouchEvent 那么onInterceptTouchEventonTouchEvent不会调用

8 ViewGroup 的在受到按下事件的时候dispatchTouchEvent 返回true||super.dispatchTouchEvent 并且onInterceptTouchEvent返回了true,那么直接调用onTouchEvent且后序事件不会在调用经过onInterceptTouchEvent而是直接dispatchTouchEventonTouchEvent

9 Activity 的onTouchEvent只有在子View的按下事件dispatchTouchEvent返回false时才会调用,也就是后序事件(注意是按下之后的关联后序事件)子View.dispatchTouchEvent哪怕返回false (按下事件就是一个新的触摸事件),activityonTouchEvent也不会回调

  • 用法解析

当一个ViewGroup不想子View调用触摸事件时onInterceptTouchEvent 返回true。如果自己想消费那么onInterceptTouchEvent返回true且dispatchTouchEvent返回true,根据自身情况判断是否要super.dispatchTouchEvent

  • 滑动冲突如何解决,具体在哪个方法里面解决

滑动冲突常规的处理方法有两种

第一种是通过外层View处理拦截规则,将拦截逻辑写在外层View中。就是说如果父容器需要这个事件的时候就拦截当前事件,如果不需要就不拦截,让这个事件继续向下传递,子控件自然能接受并拦截这个事件。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (父容器需要这个点击事件)
                    intercepted = true;
                else
                    intercepted = false;
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }
        mLastXintercept = x;
        mLastYintercept = Y;
        return intercepted;
    }

第二种方法是通过requestDisallowInterceptTouchEvent(true)方法,在事件传递上忽略付容器的onInterceptTouchEvent方法,把所有事件都传递给子控件,在子控件内部进行逻辑处理。如果子控件需要这个事件就直接消耗掉,否则再交给父容器处理。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mlastX;
                int deltaY = y - mlastY;
                if (父容器需要这个点击事件)
                    getParent().requestDisallowInterceptTouchEvent(false);
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        mlastX = x;
        mlastY = Y;
        return super.dispatchTouchEvent(ev);
    }

需要注意的几个问题

  1. 虽然requestDisallowInterceptTouchEvent写在哪里都可以生效,但我们习惯写在dispatchTouchEvent方法中,毕竟它负责事件分发。
  2. 父ViewGroup不能拦截DOWN事件,至于MOVE或者UP事件的拦截状态要根据具体的情景
  • 如何判断滑动方向

可以通过 比较 X轴和Y轴的移动距离 来判断是沿哪个轴移动的,哪个轴上的移动距离大就是沿哪个轴移动

    public boolean onTouchEvent(MotionEvent event) {
        //在触发时回去到起始坐标
        float x= event.getX();
        float y = event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                //将按下时的坐标存储
                downX = x;
                downY = y;
                break;
            case MotionEvent.ACTION_UP:
                //获取到距离差
                float dx= x-downX;
                float dy = y-downY;
                //防止是按下也判断
                if (Math.abs(dx)>8&&Math.abs(dy)>8) {
                    //通过距离差判断方向
                    int orientation = getOrientation(dx, dy);
                }
                break;
        }
        return super.onTouchEvent(event);
    }

    private int getOrientation(float dx, float dy) {
        if (Math.abs(dx)>Math.abs(dy)){
            //X轴移动
            return dx>0?'r':'l';
        }else{
            //Y轴移动
            return dy>0?'b':'t';
        }
    }

15. Apk打包流程

  • 编译打包步骤
  1. 打包资源文件,生成R.java文件
  2. 处理aidl文件,生成相应的Java文件
  3. 编译项目源代码,生成class文件
  4. 转换所有的class文件,生成classes.dex文件
  5. 打包生成APK文件
  6. 对APK文件进行签名
  7. 对签名后的APK文件进行对齐处理

对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。

  • aapt的作用

aapt工具会对资源文件进行编译,并生成一个resource.arsc文件,resource.arsc文件相当于一个文件索引表,记录了很多跟资源相关的信息。

LocalBroadcastReceiver实现原理

LocalBroadcastManager从名字上看就知道这个发送的广播只在本应用内传播,官方是这么介绍LocalBroadcastManager的:

  1. 使用它发送的广播将只在自身App内传播,因此你不必担心泄漏隐私数据
  2. 其它App无法对你的App发送该广播,因为你的App根本就不可能接收到非自身应用发送的该广播,因此你不必担心有安全漏洞可以利用
  3. 比系统的全局广播更加高效

比较重要的部分:

1、它内部有两个内部类,分别为ReceiverRecord和BroadcastRecord

2、它含有三个集合来管理

3、它内部有一个Handler对象

首先先看看LocalBroadcastManager的构造函数:

private static LocalBroadcastManager mInstance;
private final Handler mHandler;
    public static LocalBroadcastManager getInstance(Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new LocalBroadcastManager(context.getApplicationContext());
            }
            return mInstance;
        }
    }

    private LocalBroadcastManager(Context context) {
        mAppContext = context;
        mHandler = new Handler(context.getMainLooper()) {

            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_EXEC_PENDING_BROADCASTS:
                        executePendingBroadcasts();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
    }

可以看到它内部是由单例实现的而私有化构造函数,而构造函数里创建了一个Handler对象,Handler对象传入的Looper是MainLooper对象,意为这个Handler的工作线程是主线程,再看看它内部的三个集合的作用:

这又涉及到两个内部类BroadcastRecord和ReceiverRecord,从名字上看就知道它们分别是广播记录实体类和接收器记录实体类:

private static class ReceiverRecord {
        final IntentFilter filter;
        final BroadcastReceiver receiver;
        boolean broadcasting;

        ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
            filter = _filter;
            receiver = _receiver;
        }
        ...
    }

    private static class BroadcastRecord {
        final Intent intent;
        final ArrayList<ReceiverRecord> receivers;

        BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
            intent = _intent;
            receivers = _receivers;
        }
    }

从这两个实体类的源码上看,ReceiverRecord是记录广播接收器(BroadcastReceiver)和它对应的过滤规则(IntentFilter),BroadcastRecord则是记录与发送的Intent匹配的ReceiverRecord的集合,因为一个Intent在它们对应的Action规则是一样的情况下它可以同时被多个广播接收器(BroadcastReceiver)接收。

  • 注册广播方法的源码:
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        synchronized (mReceivers) {
            //首先就是创建一个ReceiverRecord对象,传入的是我们的广播接收器和这个广播接收器的filter
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            //得到这个广播接收器的IntentFilter过滤规则集合
            ArrayList<IntentFilter> filters = mReceivers.get(receiver);
            if (filters == null) {
                //如果不存在的话则创建一个
                filters = new ArrayList<IntentFilter>(1);
                mReceivers.put(receiver, filters);
            }
            //把这个filter add进广播接收器对应的规则集合中去
            filters.add(filter);
            for (int i=0; i<filter.countActions(); i++) {
                //然后遍历这个filter得到它的Action
                String action = filter.getAction(i);
                ArrayList<ReceiverRecord> entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList<ReceiverRecord>(1);
                    mActions.put(action, entries);
                }
                //通过遍历得到Action后,一一分别对每个Action建立Action与ArrayList<ReceiverRecord>对应的映射表
                entries.add(entry);
            }
        }
    }

registerReceiver()方法就做了两件事:

1、为传入的广播接收器添加指定的IntentFilter过滤规则

2、把IntentFilter里面的所有Action分别建立对ArrayList的映射,也就是为相应的Action添加广播接收器,表示这个广播接收器可以接收此Action的广播

  • unregisterReceiver()方法:
public void unregisterReceiver(BroadcastReceiver receiver) {
        synchronized (mReceivers) {
            //在mReceivers表中移除key为receiver的对象
            ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
            if (filters == null) {
                return;
            }
            //取出这个广播接收器的filter中含有的所有action,然后在mActions表中,分别得到这个action对应的广播接收器集合,再判断是否含有我们需要移除的receiver,如果有则移除
            for (int i=0; i<filters.size(); i++) {
                IntentFilter filter = filters.get(i);
                for (int j=0; j<filter.countActions(); j++) {
                    String action = filter.getAction(j);
                    ArrayList<ReceiverRecord> receivers = mActions.get(action);
                    if (receivers != null) {
                        for (int k=0; k<receivers.size(); k++) {
                            if (receivers.get(k).receiver == receiver) {
                                receivers.remove(k);
                                k--;
                            }
                        }
                        if (receivers.size() <= 0) {
                            mActions.remove(action);
                        }
                    }
                }
            }
        }
    }

unregisterReceiver()这个方法也做了两件事:

1、移除mReceivers表中广播接收器

2、移除mActions表中的广播接收器

  • sendBroadcast()方法
public boolean sendBroadcast(Intent intent) {
        synchronized (mReceivers) {
            final String action = intent.getAction();
            final String type = intent.resolveTypeIfNeeded(
                    mAppContext.getContentResolver());
            final Uri data = intent.getData();
            final String scheme = intent.getScheme();
            final Set<String> categories = intent.getCategories();

            final boolean debug = DEBUG ||
                    ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
            //通过得到我们Intent的Action来获得该Action对应的所有的广播接收器的集合
            ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
            if (entries != null) {

                ArrayList<ReceiverRecord> receivers = null;
                //然后遍历该集合,进行匹配
                for (int i=0; i<entries.size(); i++) {
                    ReceiverRecord receiver = entries.get(i);

                    if (receiver.broadcasting) {
                        if (debug) {
                            Log.v(TAG, "  Filter's target already added");
                        }
                        continue;
                    }
                    //当action,type,scheme,data,categories都完全相同时,这时匹配成功
                    int match = receiver.filter.match(action, type, scheme, data,
                            categories, "LocalBroadcastManager");
                    if (match >= 0) {
                        //匹配成功
                        if (receivers == null) {
                            receivers = new ArrayList<ReceiverRecord>();
                        }
                        //然后把匹配成功的ReceiverRecord对象添加到一个集合中去
                        receivers.add(receiver);
                        receiver.broadcasting = true;
                    } else {
                    }
                }

                if (receivers != null) {
                    for (int i=0; i<receivers.size(); i++) {
                        receivers.get(i).broadcasting = false;
                    }
                    //最后再把存储匹配成功的ReceiverRecord对象集合添加到mPendingBroadcasts中去,而最终我们的广播接收是通过遍历mPendingBroadcasts这个集合来一一对这个集合里面的广播接收器进行广播的接收
                    mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));

                    if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
//这个非常重要,因为一开始我们就知道了在构造函数中会创建一个handler,而这个方法的名为sendBroadcast()其实不是真正的发送一个广播,而是通过handler来发送一个Message,然后在handlerMessage()回调方法中进行消息的处理,所以这也证实了这是一个本地广播,其它应用根本无法获取到,因为LocalBroadcastManager内部是通过Handler实现广播的发送的  
                    mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                    }
                    return true;
                }
            }
        }
        return false;
    }

sendBroadcast()方法看起来比较长,其实一半的代码都是在做匹配,这段代码也做了三件事:

1、把我们发送的Intent的规则和mActions中对应action的ArrayList集合里面的广播接收器的规则进行匹配,匹配成功则加入一个匹配成功的集合中

2、把匹配成功的集合加入到mPendingBroadcasts集合中

3、最重要的一点,它其实是通过handler发送一个Message来实现的

  • executePendingBroadcasts()方法

executePendingBroadcasts()方法,这个方法是在Handler中处理消息用的,我们在调用LocalBroadcastManagersendBroadcast()方法时,它实际上是通过handler发送一个Message,然后在executePendingBroadcasts()方法中进行广播的接收:

private void executePendingBroadcasts() {
        while (true) {
            BroadcastRecord[] brs = null;
            synchronized (mReceivers) {
                final int N = mPendingBroadcasts.size();
                if (N <= 0) {
                    return;
                }
                brs = new BroadcastRecord[N];
                //把mPendingBroadcasts集合转为数组
                mPendingBroadcasts.toArray(brs);
                //然后清空mPendingBroadcasts集合
                mPendingBroadcasts.clear();
            }
            for (int i=0; i<brs.length; i++) {
                BroadcastRecord br = brs[i];
                for (int j=0; j<br.receivers.size(); j++) {
                //然后通过循环遍历,得到mPendingBroadcasts集合中可以接收该广播的广播接收器进行广播的接收,通过调用onReceive()方法进行接收
                br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
                }
            }
        }
    }

这个方法开了一个while循环进行轮询mPendingBroadcasts集合来进行广播的接收,处理完一遍后则会清空该集合,而下一次再有广播发送则重复这些动作。

  • LocalBroadcastManager总结
  1. LocalBroadcastManager高效的原因主要是因为它内部是通过Handler实现的,它的sendBroadcast()方法含义并非和我们平时所用的一样,它的sendBroadcast()方法其实是通过handler发送一个Message实现的(当然这个方法还有规则匹配等作用)
  2. 既然是它内部是通过Handler来实现广播的发送的,那么相比与系统广播通过Binder实现那肯定是更高效了,同时使用Handler来实现,别的应用无法向我们的应用发送该广播,而我们应用内发送的广播也不会离开我们的应用
  3. LocalBroadcastManager内部协作主要是靠这两个Map集合:mReceivers和mActions,当然还有一个List集合mPendingBroadcasts,这个主要就是存储待接收的广播对象

17. RecyclerView的缓存

RecyclerViewRecyler里面实现ViewHolder的缓存,Recycler里面的实现缓存的主要包含以下5个对象:

  • ArrayList mAttachedScrap

未与RecyclerView分离的ViewHolder列表,如果仍依赖于 RecyclerView (比如已经滑动出可视范围,但还没有被移除掉),但已经被标记移除的ItemView集合会被添加到 mAttachedScrap

按照id和position来查找ViewHolder

  • ArrayList mChangedScrap

表示数据已经改变的ViewHolder列表,存储notifXXX 方法时需要改变的 ViewHolder,匹配机制按照position和id进行匹配

  • ArrayList mCachedViews

缓存ViewHolder,主要用于解决RecyclerView滑动抖动时的情况,还有用于保存PrefetchViewHoder

最大的数量为:mViewCacheMax = mRequestedCacheMax + extraCacheextraCache是由prefetch的时候计算出来)

  • ViewCacheExtension mViewCacheExtension

开发者可自定义的一层缓存,是虚拟类ViewCacheExtension的一个实例,开发者可实现方法getViewForPositionAndType(Recycler recycler, int position, int type)来实现自己的缓存。

适用场景:android.jlelse.eu/anatomy-of-…

位置固定

内容不变

数量有限

  • mRecyclerPool

ViewHolder缓存池,在有限的mCachedViews中如果存不下ViewHolder时,就会把ViewHolder存入RecyclerViewPool

按照Type来查找ViewHolder

每个Type默认最多缓存5个

RecyclerView在设计的时候讲上述5个缓存对象分为了3级。每次创建ViewHolder的时候,会按照优先级依次查询缓存创建ViewHolder。每次讲ViewHolder缓存到Recycler缓存的时候,也会按照优先级依次缓存进去。三级缓存分别是:

  • 一级缓存:返回布局和内容都都有效的ViewHolder
  1. 按照position或者id进行匹配
  2. 命中一级缓存无需onCreateViewHolderonBindViewHolder
  3. mAttachScrap在adapter.notifyXxx的时候用到
  4. mChangedScarp在每次View绘制的时候用到,因为getViewHolderForPosition非调用多次
  5. mCachedView:用来解决滑动抖动的情况,默认值为2
  • 二级缓存:返回View
  1. 按照position和type进行匹配直接返回View
  2. 需要自己继承ViewCacheExtension实现
  3. 位置固定,内容不发生改变的情况,比如说Header如果内容固定,就可以使用
  • 三级缓存:返回布局有效,内容无效的ViewHolder
  1. 按照type进行匹配,每个type缓存值默认=5
  2. layout是有效的,但是内容是无效的
  3. 多个RecycleView可共享,可用于多个RecyclerView的优化

18. LeakCanary原理解析

  • ReferenceQueue

ReferenceQueue是一种存放引用的队列,在Java中有四种引用。

  1. 强引用(当我们创建一个对象时,默认创建的就是强引用。只要强引用还存在,垃圾回收器就算抛出OOM,也不会回收强引用引用的对象。)
  2. 软引用(SoftReference,当内存不足时,垃圾回收器会回收被引用的对象。)
  3. 弱引用(WeakReference,当GC时垃圾回收器会回收掉被引用的对象。)
  4. 虚引用 (PhantomReference,基本不会用到。)

ReferenceQueue对象,会在垃圾收集器即将回收引用对象指向的对象时,将这个引用对象加入这个队列。引用指向的对象是说的我们在构造WeakReference时构造方法中传的对象,引用对象说的就是我们这个引用本身,两者的概念不要弄混淆了。

举个例子:

ReferenceQueue<Activity> mQueue = new ReferenceQueue<>();
 WeakReference<Activity> mWeakReference = new WeakReference<Activity>(mActivity,mQueue);

如果GC时将mWeakReference指向的mActivity回收的话,同时也会向我们的mQueue中加入我们的mWeakReference

  • LeakCanary原理概述

LeakCanary通过application注册了一个的lifecycleCallbacks,在activity生命周期的destory时,将activity对象通过set集合、弱引用和引用队列记录起来,五秒之后当主线程空闲时检查,循环引用队列将为空对象的keyset集合中删除,然后判断集合中是否有activity对象,有就是没被回收,如果没回收调用gc,重新检测还没被回收,就提示内存泄漏。

 /**
   * Identical to {@link #watch(Object, String)} with an empty string reference name.
   * @see #watch(Object, String)
   */
  public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }

  /**
   * Watches the provided references and checks if it can be GCed. This method is non blocking,
   * the check is done on the {@link Executor} this {@link RefWatcher} has been constructed with.
   *
   * @param referenceName An logical identifier for the watched object.
   */
  public void watch(Object watchedReference, String referenceName) {
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    if (debuggerControl.isDebuggerAttached()) {
      return;
    }
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    watchExecutor.execute(new Runnable() {
      @Override public void run() {
        ensureGone(reference, watchStartNanoTime);
      }
    });
  }

  void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();

    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    removeWeaklyReachableReferences();
    if (gone(reference) || debuggerControl.isDebuggerAttached()) {
      return;
    }
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      File heapDumpFile = heapDumper.dumpHeap();

      if (heapDumpFile == null) {
        // Could not dump the heap, abort.
        return;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs,
              heapDumpDurationMs));
    }
  }

  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

  private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

这里是核心,把destroyactivity编号(UUID.randomUUID().toString())保存到Set集合retainedKeys中,作为它的key,然后把activity也就是watchedReference放入到弱引用KeyedWeakReference中。

这里引出了第一个知识点,弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue 这个方法挺巧妙的,retainedKeys集合了所有destoryed了的但没有被回收的Activity的key,这个集合可以用来判断一个Activity有没有被回收,但是判断之前需要用removeWeaklyReachableReferences()这个方法更新一下。