由GridView(RecyclerView、ListView)首项重复绘制引起的探究

来源:互联网 发布:华微大数据营销平台 编辑:程序博客网 时间:2024/05/22 01:32

如果Item布局包含异步加载的ImageView,每次ImageView显示都会请求界面重绘,此时仍会调用N次OnMeasure方法。

每次调用GridView的OnMeasure方法都会调用Adapter的getView方法一次,其中position = 0。

父视图可能在它的子视图上调用一次以上的measure(int,int )方法。 例如,父视图可以使用unspecified dimensions来将它的每个子视图都测量一次来算出它们到底需要多大尺寸,如果所有这些子视图没被限制的尺寸的和太大或太小,那么它会用精确数值再次调用measure()(也就是说,如果子视图不满意它们获得的区域大小,那么父视图将会干涉并设置第二次测量规则)。

我们都知道一个布局要显示屏幕要经过测量(onMeasure)、布局(onLayout)、绘制(onDraw)这三个步骤。但是究竟是谁规定的这三个步骤的定义和顺序?在什么条件下会引起布局重绘呢?

看完ViewRootImpl这个类就恍然大悟了。

The top of a view hierarchy, implementing the needed protocol between View and the WindowManager. This is for the most part an internal implementation detail of {@link WindowManagerGlobal}.

这是ViewRootImpl类的注释。
ViewRootImpl是Android中视图层级的顶层,同时实现了View和WindowManager之间必要的协议,是两者之间的桥梁。ViewRootImpl是WindowManagerGlobal大部分内部细节的实现。

一、Activity创建
我们知道,每当通过Intent启动一个ActivityA时,这其实是一个进程间通信的过程,基本都是ActivityThread类和远程的ActivityManagerService进行交流。通过Binder机制,告诉服务进程ActivityManger要启动的ActivityA的信息,ActivityManager会查找已经注册过的Activity列表中有没有这个将要启动的ActivityA。如果找到的话,会新建一个ActivityA的对象,并回调ActivityA生命周期方法。这个Activity就展示到我们眼前了。

二、创建Window
每创建一个Activity,都会对应创建一个PhoneWindow对象,由它来创建DecorView和ContentView。我们在Activity的OnCreate方法中设置的布局,也是通过PhoneWindow处理的。
PhoneWindow的创建时机是在Activity中的attach方法中的。

 final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {        attachBaseContext(context);        mFragments.attachHost(null /*parent*/);        //这里创建了PhoneWindow对象        mWindow = new PhoneWindow(this);        mWindow.setCallback(this);        mWindow.setOnWindowDismissedCallback(this);        mWindow.getLayoutInflater().setPrivateFactory(this);

三、将View添加到Window中
PhoneWindow创建了页面视图树种最顶层的View和ViewGroup,包括:DecorView、最外层布局mContentRoot、内容布局父布局mContentParent

//创建DecorViewprotected DecorView generateDecor() {        return new DecorView(getContext(), -1);    }//创建mContentRootView in = mLayoutInflater.inflate(layoutResource, null);        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));        mContentRoot = (ViewGroup) in;//mContentParent其实是mContentRoot的一部分ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

那是在什么时候把DecorView添加到Window上的呢?
是在onResume的时候
我们先看一个ActivityThread中handleResumeActivity方法。

//这是Activity第一次可见的时候走的逻辑。//此时Activity所属的Window还没有添加到WindowManager中if (r.window == null && !a.mFinished && willBeVisible) {                r.window = r.activity.getWindow();                View decor = r.window.getDecorView();                decor.setVisibility(View.INVISIBLE);                ViewManager wm = a.getWindowManager();                WindowManager.LayoutParams l = r.window.getAttributes();                a.mDecor = decor;                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;                l.softInputMode |= forwardBit;                if (a.mVisibleFromClient) {                    a.mWindowAdded = true;                    //这里将decorview添加到了windowManager中。WindowManager是一个接口,它有一个实现类WindowManagerImpl,但是addView方法其实是委托给了WindowManagerGlobal去实现的。                    wm.addView(decor, l);                }

上面是Activity第一OnResume的时候,当Activity从暂停状态恢复时是怎么把View添加到Window上的呢?还是看handleResumeActivity方法。

//这部分就是处理Activity由于页面更新而发生的变化if (!r.activity.mFinished && willBeVisible                    && r.activity.mDecor != null && !r.hideForNow) {                if (r.newConfig != null) {                    r.tmpConfig.setTo(r.newConfig);                    if (r.overrideConfig != null) {                        r.tmpConfig.updateFrom(r.overrideConfig);                    }                    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "                            + r.activityInfo.name + " with newConfig " + r.tmpConfig);//Activity配置变化引起的界面更新                    performConfigurationChanged(r.activity, r.tmpConfig);                    freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));                    r.newConfig = null;                }                if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="                        + isForward);                WindowManager.LayoutParams l = r.window.getAttributes();                //由于软键盘变化引起的界面更新                if ((l.softInputMode                        & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)                        != forwardBit) {                    l.softInputMode = (l.softInputMode                            & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))                            | forwardBit;                    if (r.activity.mVisibleFromClient) {                        ViewManager wm = a.getWindowManager();                        View decor = r.window.getDecorView();                        wm.updateViewLayout(decor, l);                    }                }                r.activity.mVisibleFromServer = true;                mNumVisibleActivities++;                //上述变化都会通过Activity的makeVisible方法,将更改后View添加到Widow中。                if (r.activity.mVisibleFromClient) {                    r.activity.makeVisible();                }            }
//Activity的makeVisible()方法,可见在这个方法再次调用了windowmanager的addView方法void makeVisible() {        if (!mWindowAdded) {            ViewManager wm = getWindowManager();            wm.addView(mDecor, getWindow().getAttributes());            mWindowAdded = true;        }        mDecor.setVisibility(View.VISIBLE);    }

四、addView的细节
上面说到在Activity中onResume时会通过WindowManager将准备好的View添加到Window中,

WindowManagerImpl中的addView方法,委托给了WindowManagerGlobal的addView方法@Override    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {        applyDefaultToken(params);        mGlobal.addView(view, params, mDisplay, mParentWindow);    }
//WindowManagerGlobal的addView方法public void addView(View view, ViewGroup.LayoutParams params,            Display display, Window parentWindow) {            ...            ...             // If this is a panel window, then find the window it is being            // attached to for future reference.            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {                final int count = mViews.size();                for (int i = 0; i < count; i++) {                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {                        panelParentView = mViews.get(i);                    }                }            }            root = new ViewRootImpl(view.getContext(), display);            view.setLayoutParams(wparams);            mViews.add(view);            mRoots.add(root);            mParams.add(wparams);            //最终调用了ViewRootImpl的setView方法             root.setView(view, wparams, panelParentView);
//ViewRootImpl的setView方法,public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {        synchronized (this) {            if (mView == null) {            //将view赋值给全局变量mView                mView = view;                mAttachInfo.mDisplayState = mDisplay.getState();                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();                mFallbackEventHandler.setView(view);                mWindowAttributes.copyFrom(attrs);                if (mWindowAttributes.packageName == null) {                    mWindowAttributes.packageName = mBasePackageName;                }                ...                代码省略                ...                // 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.                //这里调用了requestLayout()方法                requestLayout();
//ViewRootImpl的requestLayout()方法@Override    public void requestLayout() {        if (!mHandlingLayoutInLayoutRequest) {            //首先检查当前线程是否是主线程            checkThread();            mLayoutRequested = true;            //开始安排遍历            scheduleTraversals();        }    }
void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();            //使用choreographer发送异步消息mTraversalRunnable            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            if (!mUnbufferedInputDispatch) {                scheduleConsumeBatchedInput();            }            notifyRendererOfFramePending();            pokeDrawLockIfNeeded();        }    }

最终会走到PerformTraversals(),这是一个巨长的方法,除了会走performMeasure、performLayout、performDraw方法外,还会有其他的一些非常重要的逻辑判断。今天我们只探究上述方法的调用,所以就不再讨论额外的逻辑了。

private void performTraversals() {    ...    if (!mStopped || mReportNextDraw) {                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);                    if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed!  mWidth="                            + mWidth + " measuredWidth=" + host.getMeasuredWidth()                            + " mHeight=" + mHeight                            + " measuredHeight=" + host.getMeasuredHeight()                            + " coveredInsetsChanged=" + contentInsetsChanged);                     // Ask host how big it wants to be                    //这里调用了performMeasure方法                    //然后会调用mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);方法                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                    // Implementation of weights from WindowManager.LayoutParams                    // We just grow the dimensions as needed and re-measure if                    // needs be                    int width = host.getMeasuredWidth();                    int height = host.getMeasuredHeight();                    boolean measureAgain = false;                    if (lp.horizontalWeight > 0.0f) {                        width += (int) ((mWidth - width) * lp.horizontalWeight);                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,                                MeasureSpec.EXACTLY);                        measureAgain = true;                    }                    if (lp.verticalWeight > 0.0f) {                        height += (int) ((mHeight - height) * lp.verticalWeight);                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,                                MeasureSpec.EXACTLY);                        measureAgain = true;                    }                    if (measureAgain) {                        if (DEBUG_LAYOUT) Log.v(TAG,                                "And hey let's measure once more: width=" + width                                + " height=" + height);                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                    }                    layoutRequested = true;                }            }        ....        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);        boolean triggerGlobalLayoutListener = didLayout                || mAttachInfo.mRecomputeGlobalAttributes;        if (didLayout) {        //这里执行了performLayout方法        //然后会调用View的layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())方法            performLayout(lp, desiredWindowWidth, desiredWindowHeight);            // By this point all views have been sized and positioned            // We can compute the transparent area    ......    if (!cancelDraw && !newSurface) {            if (!skipDraw || mReportNextDraw) {                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {                    for (int i = 0; i < mPendingTransitions.size(); ++i) {                        mPendingTransitions.get(i).startChangingAnimations();                    }                    mPendingTransitions.clear();                }                //这里执行了perfromDraw方法,然后会调用View的mView.draw(canvas);方法                performDraw();            }

至此,我们终于知道onMeasure、onLayout、onDraw这个三种绘制流程中关键方法的调用顺序是什么地方定义的了。
当然,在绘制过程中,这三个方法并不是简单的调用一次,从源码中可以看出,不同的条件情况下这三个方法会重复调用。

0 0