Android高级UI系列(2)-DecorView

上一章我们讲解了我们的视图是如何显示到界面上的,这一章我们来讲讲View是如何添加到window上的,换句话说就是我们的DecorView是如何添加到Window上的。

同样,我们先带着疑问:
我们的DecorView是怎么添加到Window上的呢?

这个问题我们要先从Activity的启动开始讲起。我们这里从Java层面开始,不考虑其他层面。

Activity的启动时通过ActivityThread来进行发起的。它会调用自己的
handleLaunchActivity()方法,

 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        if (r.profilerInfo != null) {
            mProfiler.setProfiler(r.profilerInfo);
            mProfiler.startProfiling();
        }

        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);

        if (localLOGV) Slog.v(
            TAG, "Handling launch of " + r);

        // Initialize before creating the activity
        WindowManagerGlobal.initialize();

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

            if (!r.activity.mFinished && r.startsNotResumed) {
                // The activity manager actually wants this one to start out paused, because it
                // needs to be visible but isn't in the foreground. We accomplish this by going
                // through the normal startup (because activities expect to go through onResume()
                // the first time they run, before their window is displayed), and then pausing it.
                // However, in this case we do -not- need to do the full pause cycle (of freezing
                // and such) because the activity manager assumes it can just retain the current
                // state it has.
                performPauseActivityIfNeeded(r, reason);

                // We need to keep around the original state, in case we need to be created again.
                // But we only do this for pre-Honeycomb apps, which always save their state when
                // pausing, so we can not have them save their state when restarting from a paused
                // state. For HC and later, we want to (and can) let the state be saved as the
                // normal part of stopping the activity.
                if (r.isPreHoneycomb()) {
                    r.state = oldState;
                }
            }
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

在这个方法中它首先会调用performLaunchActivity()。好, 我们看这个方法:

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         //省略部分代码
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
                    ...
                    } catch (Exception e) {
            ...
        }

        try {
            ...
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);

               ...
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.activity = activity;
                r.stopped = true;
                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
                if (!r.activity.mFinished) {
                    if (r.isPersistable()) {
                        if (r.state != null || r.persistentState != null) {
                            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
                        }
                    } else if (r.state != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                    }
                }
                if (!r.activity.mFinished) {
                    activity.mCalled = false;
                    if (r.isPersistable()) {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state,
                                r.persistentState);
                    } else {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state);
                    }
                    if (!activity.mCalled) {
                        throw new SuperNotCalledException(
                            "Activity " + r.intent.getComponent().toShortString() +
                            " did not call through to super.onPostCreate()");
                    }
                }
            }
            r.paused = true;

            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }

首先,它会通过反射获取Activity,然后他会调用我们Activity的attach()方法,而我们的PhoneWindow是在Activity的attach()方法中初始化的,大家可以自己查看源码。因此,初始化的过程都是在Activity中发起调用的。初始化完成之后, 它会调用callActivityOnCreate(),这里就是会调用activity的,也就是说我们Activity的声明周期,执行过程,都是在我们ActivityThread中一次执行的,大家可以自己往后看,生命周期次序都有。

看完了performLaunchActivity ()方法,我们回到handleLaunchActivity (),接着往下看,它会执行handleResumeActivity(),我们看方法名,就可以猜到它肯定会去调用resume方法吧。

它的流程大概就是handleResumeActivity()->performResumeActivity()->activity.performResume()

  final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
            ...
            //第一步            
        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);
        
        ...
        //第二步
          r.window = r.activity.getWindow();
          View decor = r.window.getDecorView();
          ViewManager wm = a.getWindowManager();
          WindowManager.LayoutParams l = r.window.getAttributes();
          //第三步
          wm.addView(decor, l);  
          ...
            }

好,我们继续看handleResumeActivity (),他会去拿window,decor,还有WindowManager,然后把我们Decor添加到我们WindowManager里面去。那这个windowManager是什么呢,我们点进去一看是个接口,那我们要找它的实现类,它是实现类是WindowManagerImpl。所以我们要看它的addView()方法。

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

它调用的是WindowManagerGlobal的addView()方法,我们只能再点进去看。代码都很长,我都贴关键的,其他的就省略了。

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
                //绘制流程真正发起点
              ViewRootImpl root;
            ...
            root = new ViewRootImpl(view.getContext(), display);
            
             mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            ...
             root.setView(view, wparams, panelParentView);
            }

它首先创建一个ViewRootImpl,然后把我们的Decor,viewrootImp,和一些参数都保存到了这个Global类中,也就是说,我们的WindowManagerGlobal是用来管理ViewRoot和DecorView的。接下来他会把我们的DecorView设置给ViewRoot,这就是这篇文章最关键的一个方法了。我们点进去看ViewRoot的setView()方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//将我们的DecorView赋值给mView,因此下面的mView就是DecorView
            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.
         requestLayout();
         ...
         //发起了跨进程消息
          res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
          ...
          //给DecorView设置parent,我们view调用requsetLayout的时候,其实调用的都是viewrootImp的requestLayout
          view.assignParent(this);
}

首先会将我们的DecorView赋值给mView,因此下面的mView就是DecorView,这个mView这里没用到,下面的UI遍历会用到。我们先来看viewRoot的requestLayout()中的scheduleTraversals();

 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
    
  void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里开启了一个runnable,我们点进去看

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

而doTraversal()中有一个方法performTraversals(),这个相信只要是看过UI绘制的博客的人应该都很熟悉这个方法吧,这个方法就是真正执行整个UI绘制的遍历过程。

private void performTraversals() {
        ...
// cache mView since it is used so much below...
        final View host = mView;
        ...
 // Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
         performLayout(lp, mWidth, mHeight);
        ...
         performDraw();

}

这篇文章不仅仅讲了DecorView是如何添加到Window的,还梳理了一遍View的绘制发起点,虽然篇幅较短,但也费了些心思。

下一篇章我们真正开始DecorView的绘制流程讲解。下篇文章计划将结合《Android开发艺术探索》和其他相关博客,并加上个人的理解,总结出一篇希望能帮助大家理解UI绘制流程的文章,尽请期待!

由于个人水平有限,文章中可能会出现错误,如果你觉得哪一部分有错误,或者发现了错别字等内容,欢迎在评论区告诉我。

望共勉。

参考

腾讯课堂

本文为猫舍原创作品,转载请注明原文出处: http://imlerry.com/2017/05/11/03-Android%E9%AB%98%E7%BA%A7UI%E7%B3%BB%E5%88%97%EF%BC%882%EF%BC%89-DecorView/

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

推荐阅读更多精彩内容