Carson带你学Android:你真的了解view.post()吗?

本文主要讲解view.post() 的四大常见疑问

  1. 为什么view.post()能保证获取到view的宽高?
  2. 为什么onCreate()使用view.post()无法立刻执行任务(如获取宽高)
  3. 若只是创建一个 View & 调用view.post()传入要执行的任务,为什么该任务不会被执行?
  4. view.pos()传入的任务被执行的有效期是什么时间节点?

常见疑问1

a. 描述

为什么view.post()能保证获取到view的宽高?

.b 原因

View.post()的原理:以Handler为基础,View.post() 将传入任务添加到 View绘制任务所在的消息队列尾部,从而保证View.post() 任务的执行时机是在View 绘制任务完成之后的。 其中,几个关键点:

  1. View.post()实际操作:将view.post()传入的任务保存到一个数组里

  2. View.post()添加的任务 添加到 View绘制任务所在的消息队列尾部的时机:View 绘制流程的开始阶段,即 ViewRootImpl.performTraversals()

  3. View.post()添加的任务执行时机:在View绘制任务之后

所以:

  • 通过View.post()添加的任务是在View绘制任务里 - 开始绘制阶段时添加到消息队列尾部的;

  • 所以,View.post() 添加的任务的执行是在View绘制任务后才执行,即在View绘制流程结束之后执行。

  • 即View.post() 添加的任务能够保证在所有 View绘制流程结束之后才被执行,所以 执行View.post() 添加的任务时可以正确获取到 View 的宽高。

具体源码分析请看:Android:为什么view.post()能保证获取到view的宽高?


常见疑问2

a. 描述

为什么onCreate()使用view.post()无法立刻执行任务(如获取宽高),需要在onResume()后才可获取?

.b 原因

在onCreate()时,AttachInfo还没被赋值(为null)(是在view.dispatchAttachedToWindow()才被赋值),所以会走下述源码的过程2;通过上面分析,此过程的作用仅是:保存了通过post()添加的任务,并没执行。

public boolean post(Runnable action) {
    
    // ...
    
    // 判断AttachInfo是否为null
    final AttachInfo attachInfo = mAttachInfo;

    // 过程1:若不为null,直接调用其内部Handler的post ->>分析1
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // 过程2:若为null,则加入当前View的等待队列
    getRunQueue().post(action); 
    return true;
}

c. 实例代码演示

@Override
public void onCreate(Bundle savedInstanceState) {
// 执行日志1:carsonhe oncreate()

view.post(new Runnable() {
        @Override
        public void run() {

            // 执行日志2:carsonhe view.post() do something

        }
    });
}

@Override
protected void onResume() {
    // 执行日志3:carsonhe onresume()
}

// 输出日志展示
日志1:carsonhe oncreate()
日志3:carsonhe onresume()
日志2:carsonhe view.post() do something

常见疑问3

a. 问题描述

若只是创建一个 View & 调用它的post(),那么post的任务会不会被执行?

final View view = new View(this);

    view.post(new Runnable() {
        @Override
        public void run() {
            // ...
        }
    });

b. 答案

不会。主要原因是:
每个View中post() 需执行的任务,必须得添加到窗口视图-执行绘制流程 - 任务才会被post到消息队列里去等待执行,即依赖于dispatchAttachedToWindow ();

若View未添加到窗口视图,那么就不会走绘制流程,post() 添加的任务最终不会被post到消息队列里,即得不到执行。(但会保存到HandlerAction数组里)

上述例子,因为它没有被添加到窗口视图,所以不会走绘制流程,所以该任务最终不会被post到消息队列里 & 执行

c. 解决方案

此时只需要添加将View添加到窗口,那么post()的任务即可被执行

// 因为此时会重新发起绘制流程,post的任务会被放到消息队列里,所以会被执行
contentView.addView(view);

常见疑问4

a. 描述

view.pos()传入的任务被执行的有效期是多久?

b. 结论

在整个 Activity 的生命周期内都可以正常使用 View.post() 任务

c.原因

任务被执行是构造AttachInfo,所以任务释放即时释放AttachInfo (置为null)。而AttachInfo 的释放操作(置为null)是在 Activity 生命周期 onDestory 方法之后

.d 原因分析

  • 目标
    跟踪 AttachInfo 的释放过程(即何时置为null)

  • 方向
    AttachInfo的赋值依赖于DecorView.dispatchAttachedToWindow(),那么释放过程,容易联想到是对应的:DecorView.dispatchDetachedFromWindow()

  • 具体源码分析
/**
  * 入口分析:DecorView.dispatchDetachedFromWindow()
  * 实际上是调用父类ViewGroup.dispatchDetachedFromWindow()
  */

  void dispatchDetachedFromWindow() {

    // ... 

    final int count = mChildrenCount;
    final View[] children = mChildren;

    // 遍历所有childView
    for (int i = 0; i < count; i++) {
        // 遍历所有childView & dispatchDetachedFromWindow()
        // 分析1
        children[i].dispatchDetachedFromWindow();
    }
}

/**
  * 分析1:childView.dispatchDetachedFromWindow()
  */
  void dispatchDetachedFromWindow() {

    // ... 

    AttachInfo info = mAttachInfo;
   
    // 1. 回调View.onDetachedFromWindow()
    onDetachedFromWindow();
    
    // 2. 通知所有监听View.onAttachToWindow的监听者回调onViewDetachedFromWindow()
    ListenerInfo li = mListenerInfo;
    final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li != null ? li.mOnAttachStateChangeListeners : null;
    if (listeners != null && listeners.size() > 0) {
        for (OnAttachStateChangeListener listener : listeners) {
            listener.onViewDetachedFromWindow(this);
        }
    }

    // 3. 将AttachInfo置为null
    mAttachInfo = null;
}

下面,我们将分析,什么时候调用上述入口,即DecorView.dispatchDetachedFromWindow();

此时需从 将DecorView从WindowManager中移除 开始讲起:移除 Window 窗口任务是通过 ActivityThread.handleDestoryActivity()完成。

/**
 * 入口
 */
private void handleDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance) {

    // 关注1:回调 Activity.onDestory()
    ActivityClientRecord r = performDestroyActivity(token, finishing,
            configChanges, getNonConfigInstance);

    // 获取当前Window的WindowManager
    WindowManager wm = r.activity.getWindowManager();
    // 当前Window的DecorView
    View v = r.activity.mDecor;
       
    // 关注2:通知WindowManager,移除当前 Window窗口
    wm.removeViewImmediate(v);
    // 此处即会释放AttachInfo
    // 因为在关注1处是在回调 Activity.onDestory()后,故在整个Activity的生命周期内都可以正常使用 View.post() 任务
    // 下面继续分析如何移除 ->> 分析1
                
}

/**
 * 分析1:WindowManager.removeViewImmediate()
 */
public void removeViewImmediate(View view) {
    
    mGlobal.removeView(view, true);
    // 调用WindowManagerGlobal的removeView()
    // ->> 分析2
}

/**
 * 分析2:WindowManagerGlobal.removeView()
 */
public void removeView(View view, boolean immediate) {
    // ...
   
    // 找到保存该DecorView的下标
    int index = findViewLocked(view, true);

    // 找到对应的ViewRootImpl,内部的DecorView
    View curView = mRoots.get(index).getView();

    // 从WindowManager中移除该DecorView
    // immediate 表示是否立即移除
    removeViewLocked(index, immediate);
    // ->> 分析3

}

/**
 * 分析3
 */
private void removeViewLocked(int index, boolean immediate) {

    // 找到对应的ViewRootImpl
    ViewRootImpl root = mRoots.get(index);

    // 该View是DecorView
    View view = root.getView();

    // ... 

    // 调用ViewRootImpl的die
    // 并且将当前ViewRootImpl在WindowManagerGlobal中移除
    boolean deferred = root.die(immediate);
    // ->> 分析4
}

/**
 * 分析4
 */
boolean die(boolean immediate) {

    // immediate 表示立即执行
    // mIsInTraversal 表示是否正在执行绘制任务
    if (immediate && !mIsInTraversal) {
        
        doDie();
        // ->> 分析5
    }

    // ...
}

/**
  * 分析5
  */
void doDie() {

    // ...
    if (mAdded) {
        
        dispatchDetachedFromWindow();
        // 回调View的dispatchDetachedFromWindow
        // ->> 即一开始分析的DecorView.dispatchAttachedToWindow()
    }

    // 将其从WindowManagerGlobal中移除DecorView
    WindowManagerGlobal.getInstance().doRemoveView(this);
}

.d 最终原因 & 结论

View.post() 任务被执行的有效期是在 Activity 生命周期 onDestory()后。本质是追踪AttachInfo的释放过程(置为null)

AttachInfo的释放过程是在 将DecorView从WindowManager中移除时:回调DecorView.dispatchDetachedFromWindow(),其具体行为是:

  1. 回调View.onDetachedFromWindow()
  2. 通知所有监听View.onAttachToWindow的监听者回调onViewDetachedFromWindow()
  3. 将AttachInfo置为null

而上述过程是在ActivityThread.handleDestoryActivity()中回调 Activity.onDestory()之后。

至此,关于view.post()的四大常见疑问 (坑)内容讲解完毕。


总结

  • 本文主要总结了常用的view.post() 的四大常见疑问
  • 接下来推出的文章,我将继续讲解Android的相关知识,感兴趣的读者可以继续关注我的博客哦:Carson_Ho的Android博客

相关系列文章阅读
Carson带你学Android:学习方法
Carson带你学Android:四大组件
Carson带你学Android:自定义View
Carson带你学Android:异步-多线程
Carson带你学Android:性能优化
Carson带你学Android:动画


欢迎关注Carson_Ho的简书

不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度


请点赞!因为你的鼓励是我写作的最大动力!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容