全网最全,精心整理系列(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();
}
preformLayout
和performDraw
的传递流程和performMeasure
是类似的,唯一不同的是,performDraw
的传递过程是在draw方法中通过dispatchDraw
来实现的
- 理解MeasureSpec
MeasureSpec
表示的是一个32位的整形值,它的高2位表示测量模式SpecMode
,低30位表示某种测量模式下的规格大小SpecSize
。MeasureSpec
是View
类的一个静态内部类,用来说明应该如何测量这个View
MeasureSpec
通过将SpecMode
和SpecSize
打包成一个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的setMeasuredDimensio
n中,所以可以在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
,onLayout
和onDraw
函数的调用
- 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过程的大致步骤:
- 绘制 backgroud(drawBackground)
- 如果颜色变淡,保存canvas的layer,来准备fading(不是必要的步骤)
- 绘制view的content(onDraw方法)
- 绘制children(dispatchDraw方法)
- 如果需要的话,有绘制颜色变淡操作,还原layer(不是必要的步骤)
- 绘制装饰器、比如scrollBar(onDrawForeground)
-
View中
onDraw
是一个空实现,由各种类型的View自定义实现 -
dispatchDraw()
负责控制子View绘制的,在View是空实现,但是在ViewGroup中有具体的实现; -
继承
ViewGroup
的自定义控件如何调用onDraw()
- 通过setWillNotDraw方法设置WILL_NOT_DRAW 为false
- 设置背景setBackground
- 自定义View有哪几种方式
- 继承View重写onDraw方法
主要用于实线不规则的效果,即这种效果不方便通过布局的组合方式来实现。相当于就是得自己“画”了。采用这种方式需要自己支持wrap_content,padding也需要自己处理
- 继承ViewGroup派生特殊的Layout
主要用于实现自定义的布局,看起来很像几种View组合在一起的时候,可以使用这种方式。这种方式需要合适地处理ViewGroup的测量和布局,并同时处理子元素的测量和布局过程。比如自定义一个自动换行的LinerLayout等。
- 继承特定的View,比如TextView
这种方法主要是用于扩展某种已有的View,增加一些特定的功能。这种方法比较简单,也不需要自己支持wrap_content和padding。
- 继承特定的ViewGroup,比如LinearLayout
这种方式也比较常见,和上面的第2种方法比较类似,第2种方法更佳接近View的底层。
- 自定义View需要注意的地方
- 让View支持wrap_content
直接继承View和ViewGroup的控件需要在onMeasure
方法中处理wrap_content
的方法。处理方法是在wrap_content
的情况下设置一个固定的尺寸
2. 让View支持padding
直接继承View的控件需要在onDraw方法中处理padding,否则用户设置padding属性就不会起作用。直接继承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效。
- 尽量不要在View中使用Handler
View中已经提供了post系列方法,完全可以替代Handler的作用
- View中如果有线程或者动画,需要及时停止
在View的onDetachedFromWindow方法可以停止线程和动画,因为当View被remove或是包含此View的Activity退出时,就会调用View的onDetachedFromWindow方法。如果不处理的话很可能会导致内存泄漏
-
View带有滑动嵌套时,需要处理好滑动冲突问题
-
在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
- 源码分析
- 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类是抽象类,且PhoneWindow
是Window
类的唯一实现类
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的事件分发
ViewGroup
的dispatchTouchEvent()
源码分析,该方法比较复杂,截取几个重要的逻辑片段进行介绍,来解析整个分发流程。
// 发生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()
方法的值,判断是否拦截。如果不是以上两种情况,即已经是MOVE
或UP
事件了,并且之前的事件没有对象进行处理,则设置成true,开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()
方法返回false,那么接下来的一些列事件都不会交给他处理。如果VieGroup
的onInterceptTouchEvent()
第一次执行为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
默认拦截所有操作。 如果遍历所有子View
或ViewGroup
,都没有消费事件。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
上面传递了,如果他的子View
是ViewGroup
的话,则重复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 ViewGroup
的dispatchTouchEvent
会调用onInterceptTouchEvent
如果返回true
那么会调用自身父类的View
的dispatchTouchEvent
也就是直接调用自身的onTouchEvent
。如果onInterceptTouchEvent
返回false
交给子布局类的dispatchTouchEvent
决定其自身的dispatchTouchEvent
返回值。如果子布局类的dispatchTouchEvent
返回false
,那么调用ViewGroup
的父类(也就是ViewGroup的父类View,简单点就是super.dispatchTouchEvent
)的dispatchTouchEvent
决定返回值。
5 中途拦截返回true情况。假设有如下情况
①按下 ②移动 ④松开
View
在dispatchTouchEvent
直接返回了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
那么onInterceptTouchEvent
和onTouchEvent
不会调用
8 ViewGroup 的在受到按下事件的时候dispatchTouchEvent
返回true||super.dispatchTouchEvent
并且onInterceptTouchEvent
返回了true,那么直接调用onTouchEvent
且后序事件不会在调用经过onInterceptTouchEvent
而是直接dispatchTouchEvent
到onTouchEvent
9 Activity 的onTouchEvent
只有在子View的按下事件dispatchTouchEvent
返回false时才会调用,也就是后序事件(注意是按下之后的关联后序事件)子View.dispatchTouchEvent
哪怕返回false
(按下事件就是一个新的触摸事件),activity
的 onTouchEvent
也不会回调
- 用法解析
当一个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);
}
需要注意的几个问题
- 虽然
requestDisallowInterceptTouchEvent
写在哪里都可以生效,但我们习惯写在dispatchTouchEvent
方法中,毕竟它负责事件分发。 - 父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打包流程
- 编译打包步骤
- 打包资源文件,生成R.java文件
- 处理aidl文件,生成相应的Java文件
- 编译项目源代码,生成class文件
- 转换所有的class文件,生成classes.dex文件
- 打包生成APK文件
- 对APK文件进行签名
- 对签名后的APK文件进行对齐处理
对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。
- aapt的作用
aapt工具会对资源文件进行编译,并生成一个resource.arsc文件,resource.arsc文件相当于一个文件索引表,记录了很多跟资源相关的信息。
LocalBroadcastReceiver实现原理
LocalBroadcastManager从名字上看就知道这个发送的广播只在本应用内传播,官方是这么介绍LocalBroadcastManager的:
- 使用它发送的广播将只在自身App内传播,因此你不必担心泄漏隐私数据
- 其它App无法对你的App发送该广播,因为你的App根本就不可能接收到非自身应用发送的该广播,因此你不必担心有安全漏洞可以利用
- 比系统的全局广播更加高效
比较重要的部分:
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中处理消息用的,我们在调用LocalBroadcastManager
的sendBroadcast()
方法时,它实际上是通过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总结
- LocalBroadcastManager高效的原因主要是因为它内部是通过Handler实现的,它的sendBroadcast()方法含义并非和我们平时所用的一样,它的sendBroadcast()方法其实是通过handler发送一个Message实现的(当然这个方法还有规则匹配等作用)
- 既然是它内部是通过Handler来实现广播的发送的,那么相比与系统广播通过Binder实现那肯定是更高效了,同时使用Handler来实现,别的应用无法向我们的应用发送该广播,而我们应用内发送的广播也不会离开我们的应用
- LocalBroadcastManager内部协作主要是靠这两个Map集合:mReceivers和mActions,当然还有一个List集合mPendingBroadcasts,这个主要就是存储待接收的广播对象
17. RecyclerView的缓存
RecyclerView
在Recyler
里面实现ViewHolder
的缓存,Recycler
里面的实现缓存的主要包含以下5个对象:
ArrayList mAttachedScrap
未与RecyclerView
分离的ViewHolder
列表,如果仍依赖于 RecyclerView
(比如已经滑动出可视范围,但还没有被移除掉),但已经被标记移除的ItemView
集合会被添加到 mAttachedScrap
中
按照id和position来查找
ViewHolder
ArrayList mChangedScrap
表示数据已经改变的ViewHolder
列表,存储notifXXX 方法时需要改变的 ViewHolder
,匹配机制按照position和id进行匹配
ArrayList mCachedViews
缓存ViewHolder
,主要用于解决RecyclerView
滑动抖动时的情况,还有用于保存Prefetch
的ViewHoder
最大的数量为:
mViewCacheMax = mRequestedCacheMax + extraCache
(extraCache
是由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
- 按照position或者id进行匹配
- 命中一级缓存无需
onCreateViewHolder
和onBindViewHolder
- mAttachScrap在
adapter.notifyXxx
的时候用到 - mChangedScarp在每次View绘制的时候用到,因为
getViewHolderForPosition
非调用多次 - mCachedView:用来解决滑动抖动的情况,默认值为2
- 二级缓存:返回View
- 按照position和type进行匹配直接返回View
- 需要自己继承
ViewCacheExtension
实现 - 位置固定,内容不发生改变的情况,比如说Header如果内容固定,就可以使用
- 三级缓存:返回布局有效,内容无效的
ViewHolder
- 按照type进行匹配,每个type缓存值默认=5
- layout是有效的,但是内容是无效的
- 多个
RecycleView
可共享,可用于多个RecyclerView
的优化
18. LeakCanary原理解析
- ReferenceQueue
ReferenceQueue
是一种存放引用的队列,在Java中有四种引用。
- 强引用(当我们创建一个对象时,默认创建的就是强引用。只要强引用还存在,垃圾回收器就算抛出OOM,也不会回收强引用引用的对象。)
- 软引用(SoftReference,当内存不足时,垃圾回收器会回收被引用的对象。)
- 弱引用(WeakReference,当GC时垃圾回收器会回收掉被引用的对象。)
- 虚引用 (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集合、弱引用和引用队列记录起来,五秒之后当主线程空闲时检查,循环引用队列将为空对象的key
从set
集合中删除,然后判断集合中是否有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);
}
}
这里是核心,把destroy
的activity
编号(UUID.randomUUID().toString()
)保存到Set集合retainedKeys
中,作为它的key,然后把activity
也就是watchedReference
放入到弱引用KeyedWeakReference
中。
这里引出了第一个知识点,弱引用和引用队列ReferenceQueue
联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference
持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue
这个方法挺巧妙的,retainedKeys
集合了所有destoryed
了的但没有被回收的Activity的key,这个集合可以用来判断一个Activity有没有被回收,但是判断之前需要用removeWeaklyReachableReferences()
这个方法更新一下。
转载自:https://juejin.cn/post/6913559050114301966