美文网首页Android进阶之路
Android:你真的了解view.post()吗?

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

作者: Carson带你学安卓 | 来源:发表于2020-12-07 07:54 被阅读0次

    本文主要讲解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博客

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

    相关文章阅读
    Android开发:最全面、最易懂的Android屏幕适配解决方案
    Android开发:史上最全的Android消息推送解决方案
    Android开发:最全面、最易懂的Webview详解
    Android开发:JSON简介及最全面解析方法!
    Android四大组件:Service服务史上最全面解析
    Android四大组件:BroadcastReceiver史上最全面解析


    欢迎关注Carson_Ho的简书!

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

    image

    相关文章

      网友评论

        本文标题:Android:你真的了解view.post()吗?

        本文链接:https://www.haomeiwen.com/subject/ygmmpktx.html