对Android中View的post方法进行探索

ps:源码是基于 android api 27 来分析的,demo 是用 kotlin 语言写的。

Android 中,如果在ActivityonCreate 方法直接获取View 的宽高是获取不到的,但如果是调用 Viewpost方法通过它(post方法)参数Runnable 接口的回调却能够获取到 View 的宽高,Viewpost 方法是马上执行的吗?它的执行时机又是什么时候呢?下面我们举个例子验证一下以下几点:

  1. post方法中的Runnable接口的回调能否直接获取View的宽;
  2. post方法中Runnable接口的回调和ActivityonResume方法的先后顺序;
  3. ActivityonResume方法能否直接获取View的宽;
  4. View没有被添加到Window里的时候,执行post方法,Runnable接口能否被回调。

PostDemo

(1)新建一个 Kotlin 类型的类MyView 并继承于View

class MyView: View {
    constructor(context: Context): super(context) {
    }

    constructor(context: Context,@Nullable attrs: AttributeSet): super(context,attrs) {}

    constructor(context: Context,@Nullable attrs: AttributeSet,defStyleAttr: Int): super(context,attrs,defStyleAttr) { }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        Log.d(MainActivity.TAG,"------onMeasure--")
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        Log.d(MainActivity.TAG,"------onLayout--")
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        Log.d(MainActivity.TAG,"------onDraw--")
    }
}

(2)新建一个 Kotlin 语言类型的Activity,名叫MainActivity

class MainActivity: AppCompatActivity() {
    companion object {
        var TAG: String = "MainActivity"
    }
    var mView: View? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mView = findViewById(R.id.my_view);
        mView?.post(Runnable {
             Log.d(TAG,"在 post 方法中获取 View 的宽--" + mView?.getWidth())
        })
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG,"----onResume---")
        Log.d(TAG,"在 onResume 方法中获取 View 的宽--" + mView?.getWidth())
    }
}

(3)MainActivity的布局界面activity_main.xml如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.xe.postdemo.MyView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/my_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FF0000"
    tools:context="com.xe.postdemo.MainActivity">
</com.xe.postdemo.MyView>

把程序运行一下,日志打印如下所示:

08-28 08:54:24.364 10133-10133/com.xe.postdemo D/MainActivity: ----onResume---
08-28 08:54:24.364 10133-10133/com.xe.postdemo D/MainActivity: 在 onResume 方法中获取 View 的宽--0
08-28 08:54:24.435 10133-10133/com.xe.postdemo D/MainActivity: ------onMeasure--
08-28 08:54:24.487 10133-10133/com.xe.postdemo D/MainActivity: ------onMeasure--
08-28 08:54:24.490 10133-10133/com.xe.postdemo D/MainActivity: ------onLayout--
08-28 08:54:24.501 2285-2738/? D/Launcher.AllAppsList: updatePackage, find appInfo=AppInfo, id=-1, itemType=0, user=UserHandle{0}, mIconType=0, pkgName=com.xe.postdemo, className=com.xe.postdemo.MainActivity, screenId=-1, container=-1, cellX=-1, cellY=-1, spanX=1, spanY=1, isLandscapePos=false from ComponentInfo{com.xe.postdemo/com.xe.postdemo.MainActivity}
08-28 08:54:24.505 2285-2738/? D/Launcher.Model: onReceiveBackground, mAllAppsList=add=[], remove=[], modified=[(0, AppInfo, id=-1, itemType=0, user=UserHandle{0}, mIconType=0, pkgName=com.xe.postdemo, className=com.xe.postdemo.MainActivity, screenId=-1, container=-1, cellX=-1, cellY=-1, spanX=1, spanY=1, isLandscapePos=false)]
08-28 08:54:24.542 10133-10133/com.xe.postdemo D/MainActivity: ------onDraw--
08-28 08:54:24.714 1472-1555/? I/Timeline: Timeline: Activity_windows_visible id: ActivityRecord{afbc1c9 u0 com.xe.postdemo/.MainActivity t7087} time:4018603
08-28 08:54:24.715 1472-1530/? I/ActivityManager: Displayed com.xe.postdemo/.MainActivity: +4s904ms
08-28 08:54:24.762 10133-10133/com.xe.postdemo D/MainActivity: 在 post 方法中获取 View 的宽--720

从日志可以看出:
post方法中Runnable 接口的回调中是可以直接获取到View 的;
ActivityonResume 方法比post 方法中Runnable 接口的回调先执行,post方法中Runnable 接口是在View 的绘制(主要是ViewonMeasureonLayoutonDraw 方法)之后才会被回调;
ActivityonResume 方法不能直接获取View 的宽,因为ActivityonResume 方法比ViewonMeasureonLayout 方法先执行。

好,我们把MainActivityonCreate 方法做一下修改,其他不变:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        setContentView(R.layout.activity_main)
//        mView = findViewById(R.id.my_view);
        mView = MyView(this)
        mView?.post(Runnable {
             Log.d(TAG,"在 post 方法中获取 View 的宽--" + mView?.getWidth())
        })
}

运行程序,日志打印如下所示:

08-31 13:24:02.481 10794-10794/com.xe.postdemo D/MainActivity: ----onResume---
08-31 13:24:02.481 10794-10794/com.xe.postdemo D/MainActivity: 在 onResume 方法中获取 View 的宽--0

从日志可以看出:
View没有被添加到Window里的时候,执行post方法,Runnable接口不会被回调。

好了,以上验证的4点以及Viewpost方法执行时机可以从源码中找到原因,我们先从Viewpost方法看起;

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
}

这里attachInfo 是为空的,所以不会执行 attachInfo.mHandler.post(action)这行代码,我们看 getRunQueue().post(action)这行代码,getRunQueue() 方法得到的是一个HandlerActionQueue 类型的对象,我们点击HandlerActionQueuepost方法查看;

public void post(Runnable action) {
    postDelayed(action, 0);
}

HandlerActionQueuepost方法又调用了自己的postDelayed方法,这里的参数0表示延时所有的事件,我们往下看HandlerActionQueuepostDelayed方法;

public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
}

从这个方法看出:HandlerAction表示要执行的任务,要执行的任务HandlerAction保存在数组长度为4的mActions数组中,mCount表示数组mActions的下标,每次都加1;这个postDelayd方法并没有马上执行任务,而是保存了任务,那么执行任务的语句在哪里呢?

有时候我们会说看到Activity的界面后 onResume方法就会被回调,所以我们从调用ActivityonResume方法的AcitivityThread.handleResumeActivity方法说起;

final void handleResumeActivity(IBinder token,
                                    boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ......
        //1、
        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            ......
            if (!r.activity.mFinished && willBeVisible
                 ......
                if (r.activity.mVisibleFromClient) {
                    //2、
                    r.activity.makeVisible();
                }
            }
            ......
        } else {
            ......
        }
 }

这里的注释1 最终会调用ActivityonResume方法,
我们往下看注释2 的代码,它是ActivitymakeVisible方法:

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            
            //3、
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
}

注释3 表示将View视图添加到ViewManager中,我们点击注释3 的代码进去看看,ViewManager的实现类是WindowManagerImpl,所以我们看的是WindowManagerImpladdView方法:

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        //4、
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
 }

注释4 处调用了WindowManagerGlobaladdView方法,我们往下看;

public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {
        ......
        synchronized (mLock) {
            ......
            //5、
            root = new ViewRootImpl(view.getContext(), display);
            ......
            try {
                //6、
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
               ......
            }
        }
}

我们点击注释5 的代码,看看ViewRootImpl的构造方法;

public ViewRootImpl(Context context, Display display) {
        ......
        //7、
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);
        ......
 }

一刚开始的时候,我们在ActivityonCreate方法中先执行View.post方法,但是ViewRootImplActivityonResume方法之后才会被初始化,还顺便在ViewRootImpl的构造方法中初始化AttachInfo,所以说一开始View.post方法中的attachInfo就为null,从而执行getRunQueue().post(action)语句;
我们往下看注释6 的代码,也就是ViewRootImplsetView方法;

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                //8、
                mView = view;
                ......
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                //9、
                requestLayout();
                ......
            }
        }
}

这里注释8 的View是底部容器DecorView
我们继续往下看注释9 的方法,也就是ViewRootImplrequestLayout方法;

@Override
public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
 }

requestLayout方法调用了ViewRootImplscheduleTraversals方法,我们且看scheduleTraversals方法;

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            ......
            //10、
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //11、
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ......
        }
}

注释10 这里先不说,后面再说,
注释11 的代码会回调mTraversalRunnable对象,而mTraversalRunnable对象是一个Runnable接口的实现类TraversalRunnable具体的对象,mTraversalRunnable对象又调用了ViewRootImpldoTraversal方法,doTraversal方法又调用了ViewRootImplperformTraversals方法,我们来看看performTraversals方法:

private void performTraversals() {
        //12
        // cache mView since it is used so much below...
        final View host = mView;
        ......
        if (mFirst) {
            ......
            //13、
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            ......
        } else {
            ......
        }
        ......
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;
            ......
            if (!mStopped || mReportNextDraw) {
                    ......
                    //14、
                    // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    ......
            }
        } else {
            ......
        }
        ......
        if (didLayout) {
            //15、
            performLayout(lp, mWidth, mHeight);
            ......
        }
        ......
        if (!cancelDraw && !newSurface) {
            ......
            //16、
            performDraw();
        } else {
            ......
        }
        ......
}

注释12 就是上面注释8 中说的底部容器DecorView
注释13 表示DecorView关联AttachInfodispatchAttachedToWindow方法是在ViewGroup 里实现,该方法会遍历DecorView 的子元素进行 关联AttachInfo,我们看一下ViewGroupdispatchAttachedToWindow 方法:

    @Override
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ......
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            
            //17、
            child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
        }
        ......
    }

这个方法是很重要的:
子元素关联了AttachInfo,然后将之前 View.post 保存的任务添加到AttachInfo 内部的Handler,所以View 没有被添加到Window 里的时候,执行post 方法,Runnable接口没有被回调;
我们看注释17 的代码,它是ViewdispatchAttachedToWindow 方法;

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ......
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            //18、
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        ......
 }

注释18 的代码表示调用了HandlerActionQueueexecuteActions 方法,我们来看看executeActions 方法;

public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];

                //19、
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            //20、
            mActions = null;
            mCount = 0;
        }
}

注释19 行的代码我们先留下悬念;
注释20 是将mActions 置空,从第二次调用 View.post 开始,Runnable会被添加到AttachInfo 内部的Handler,而不是HandlerActionViewonMeasureonLayoutonDraw 方法也不会被调用;
我们回过头来看注释14 的代码,也就是ViewRootImplperformMeasure 方法;

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        ......
        try {
            
            //21、
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}

注释21 的mView 表示DecorView,它的measure 方法是在View 里,我们来看一下Viewmeasure 方法;

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        if (forceLayout || needsLayout) {
           ......
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                //22、
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                ......
            }
            ......
        }
        ......
}

注释22 的代码是调用的是DecorViewonMeasure 方法,而不是ViewonMeasure 方法,我们往下看DecorViewonMeasure 方法;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......
    }

DecorViewonMeasure 方法调用了 父类的onMeasure 方法,最终的实现是在FrameLayoutmeasure 方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                
                //23、
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ......
            }
        }
        ......
    }

注释23 的代码会对所有子View 进行了一遍测量,并计算出所有子 View 的最大宽度和最大高度,我们往下看FrameLayoutmeasureChildWithMargins 方法;

    protected void measureChildWithMargins(View child,
                                           int parentWidthMeasureSpec, int widthUsed,
                                           int parentHeightMeasureSpec, int heightUsed) {
        ......
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

我们假设child 是具体的View,而不是ViewGroupViewmeasure 方法最终调用了ViewonMeasure 方法,onMeasure方法最终是测量View 的宽高;
我们回过头来看:注释15 的代码最终会调用 View.onLayout 方法,它的调用过程是:
ViewRootImpl.performLayout-> host.layout(ViewGroup.layout)->super.layout(View.layout)-> View.onLayout
最终完成View 位置的确定;

注释16 的代码最终会调用 View.onDraw 方法,它的调用过程是 :ViewRootImpl.performDraw -> ViewRootImpl.draw->ViewRootImpl.drawSoftware-> mView.draw(DecorView.draw)->super.draw(View.draw)-> View.onDraw
最终完成 View 的绘画出来;

好了,现在我们可以解答上面剩下的疑问了,AcitivityThread.handleResumeActivity方法 先调用自己的performResumeActivity 方法,而该方法最终调用ActivityonResume 方法,而后再调用ActivitymakeVisible 方法,ActivitymakeVisible 方法最终完成View 的测量宽高、位置确定和绘画,所以ActivityonResume 方法不能直接获取View 的宽。

我们在回过头来看,注释19 的代码,它的延时时间为0啊,而且比 View 的onMeasureonLayoutonDraw 方法先被调用啊,为什么从上面的demo 日志看出最终Runnable 接口等ViewonMeasureonLayoutonDraw 方法调用完之后再调用?
是因为注释10 的代码先比注释19 的代码先被调用,注释10 表示开启了同步消息屏障,Android中它有一个异步消息优先级比较高的权利,保障 View 绘制完后再给其他消息执行,所以在 View.post方法中的Runnable 接口的回调能直接获取View 的宽。

ActivityonResume 方法是在ActivitymakeVisible 方法先被调用的,而ViewpostRunnable 接口是在View 绘制完才会被回调的,所以ActivityonResume 方法先比 View.post 方法中Runnable 接口被调用。

本文转载于:https://mp.weixin.qq.com/s/8LnmMAc1NnClqHAZGYwQtg

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容