Android View的绘制机制

大家好!我 胡汉三 又回来啦!由于之前的一段时间公司的项目比较忙碌,再加上我又全身心的投入到了 PMP 考证队伍中,导致博客停更了好久。
不得不说考取 PMP证书,真的需要付出很大的精力;首先是因为心疼钱,因为考一次就得付出3900大洋,着实心疼啊!幸好付出和收获成了正比,顺利的拿到了 5A,结束了考试之旅。
好了废话说完了,我们接下来就来上干货啦!

View的绘制机制,究竟是怎么回事?

源码啊!源码,你能否一股脑的钻进我的脑子啊!这样我就不用翻你千百遍,一回头你还你我还我了=_=!!

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ...
        //初始化WindowManageService 即初始化WMS
        WindowManagerGlobal.initialize();
        //创建Activity,并执行onCreate生命周期方法
        Activity a = performLaunchActivity(r, customIntent);
         
        if (a != null) { //证明Activity被正常启动
            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) {
                
                performPauseActivityIfNeeded(r, reason);
                ...
            }
        } else {
            //若执行出错告诉ActivityManager,结束当前Activity
            try {
                ActivityManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }

小结

    1. 初始化WindowManagerService
    1. 生成Activity,并执行Activity$onCreate(...)
    1. Activity被正常启动 则 执行 Activity$onResume(...)
    1. Activity未能正常启动 则 结束当前Activity即通过AMS执行finishActivity(...)

分块

  • [ 1 ] ActivityThread $performLaunchActivity(...)是整个Activity创建的入口
  • [ 2 ] 在ActivityThread $handleResumeActivity(...)方法执行过程中传递给View类中常量mParent的属性值类型

[ 1 ] ActivityThread $performLaunchActivity(...)

ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        Activity activity = null;
          //通过类加载的方式创建Activity
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            ...
        }

        try {
          ...
           // [1.1] 通过Instrumentation调取Activity的onCretae
          if (r.isPersistable()) {
               mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
           } else {
               mInstrumentation.callActivityOnCreate(activity, r.state);
           }
           ...
        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            ...
        }
        return activity;
    }

[ 1.1 ] Instrumentation$callActivityOnCreate

Instrumentation.java

 public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        //至此进入到`Activity$onCreate(...)`
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }

至此进入到Activity$onCreate(...)并在onCreate(...)中调用setContentView(int layoutResID)进行View的绘制,至此我们才开始正式流程:

[ 1.2 ] Activity$setContentView(int layoutResID)

Activity.java

 public void setContentView(@LayoutRes int layoutResID) {
        //[1.3] 获取Window对象,调取其setContentView设置视图
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

[ 1.3 ] PhoneWindow$setContentView(int layoutResID)

PhoneWindow.java 由于Window是一个抽象类,而PhoneWindow是它唯一儿子,所谓 父债子还 嘛 !所以找它没错的

@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
           //安装DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
           ...
        } else {
            //解析Layout XML文件
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
       //请求视图更新
        mContentParent.requestApplyInsets();
        ...
    }

小结

    1. mContentParentNull,表示当前视图无盛放的容器,即需要安装顶级视图DecorView
    1. 解析XML布局文件
    1. 请求视图更新操作

[ 1.4 ] View$requestApplyInsets()

View.java

    /**
     * The parent this view is attached to.
     * {@hide}
     *
     * @see #getParent()
     */
    protected ViewParent mParent;
    //为mParent变量赋值
    void assignParent(ViewParent parent) {
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    }
   /*
    * 这是重点:
    *        判定mParent是否为Null
    *        不为Null时,调取mParent的requestFitSystemWindows()
    */
    @Deprecated
    public void requestFitSystemWindows() {
        if (mParent != null) {
            mParent.requestFitSystemWindows();
        }
    }

   /**
     * 在PhoneWindow中调取的此方法,进行视图更新
     * 此方法无任何操作,而是直接调取了requestFitSystemWindows()
     */
    public void requestApplyInsets() {
        requestFitSystemWindows();
    }

小结

    1. PhoneWindow中调取了View$requestApplyInsets(),由上述代码可知在此方法中没有做任何操作,而是直接调取了View$requestFitSystemWindows()
    1. 而在View$requestFitSystemWindows()方法中它判定了mParent在不为Null的情况下,调取了mParent$requestFitSystemWindows()
    1. 我们发现assignParent(ViewParent parent)是为mParent赋值的地方,而mParent的类型为ViewParent,但可惜的是ViewParent是一个接口,并没有实现代码
public interface ViewParent {}

那这事就郁闷了,无法确定mParent真实的类型,那么就无法找到 ViewParent$requestFitSystemWindows()真实实现的地方。那么我们就得再次回归源码了,继续 Read The Fucking Source Code . . . .

[ 2 ] ActivityThread$handleResumeActivity(...)

Activity被正常创建粗来了,也走完了ActivityonCreate生命周期,那么接下来就该走onResume生命周期了,我来看看在这里我们能不能找到想要的答案~

 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
        r = performResumeActivity(token, clearHide, reason);
        if (r != null) {
            final Activity a = r.activity;
            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 (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        //重点来啦:这里将DecorView添加到WindowManager中
                        wm.addView(decor, l);
                    } else {
                        a.onWindowAttributesChanged(l);
                    }
                }
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }
            ...

        } else {
            ...
        }
    }

[ 2.1 ]WinodwManagerImpl$addView(...)

WindowManager是一个接口,而他的实现类是WindowManagerImpl,所以我们就 单刀直入啦!

WindowManagerImpl.java

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
  @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        //不做过多论述,调取WindowManagerGlobal$addView(...)
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

[ 2.2 ] WindowManagerGlobal$addView(...)

WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {

            ....
           //创建ViewRootImpl对象
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            try {
                //重点:
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                ...
            }
        }
    }

[ 2.3 ] ViewRootImpl$setView(...)

ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                //我们要的就是它,原来是在这里为 View中mParent赋值的,
               //为什么是 this 呢? 难道。。。
                view.assignParent(this);
               ...
            }
        }
    }

看看的类描述

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {...}

原来如此,ViewRootImpl原来也是ViewParent接口的实现类之一啊~
那么至此我们就搞清楚ViewmParent的具体类型了,就是ViewRootImpl类型!

是时候解答疑惑了,让我回过头看看[ 1.3 ]中的代码
   /*
    * 这是重点:
    *        判定mParent是否为Null
    *        不为Null时,调取mParent的requestFitSystemWindows()
    */
    @Deprecated
    public void requestFitSystemWindows() {
        if (mParent != null) {
            mParent.requestFitSystemWindows();
        }
    }

我们已经确认当前的mParent是类型了,那么我们直接去ViewRootImpl中寻找这个方法

[ 1.4 ] ViewRootImpl$requestFitSystemWindows()

ViewRootImpl.java

    @Override
    public void requestFitSystemWindows() {
         //检查线程
        checkThread();
        mApplyInsetsRequested = true;
        //开始遍历view
        scheduleTraversals();
    }

    void scheduleTraversals() {
        //当绘制任务执行期间,不允许有其他绘制任务执行
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //向Looper的消息队列发布一个同步障碍
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
           //重点来了: post一个消息,执行mTraversalRunnable中任务
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

[ 1.5 ] mTraversalRunnable

 //它实现了Runnable接口
  final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            //执行遍历任务
            doTraversal();
        }
    }
   final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
   //执行遍历任务
   void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            //重点中的最后一击
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

当我们看到performTraversals(),我们看到了黎明的曙光。。。没错就是它~~

[ 1.6 ] performTraversals()

 private void performTraversals() {
  ...
  //测量
  performMeasure();
  ...
  //布局
  performLayout();
  ...
  //绘制
  perfromDraw
  ...
}
执行完此方法后,我们就完成了对View测量 ==> 布局 ==> 绘制 ,也就完成了我们的整个流程的剖析!!

........................................................................................................................................................................................................................................................................................................................................................................................................

问题

  • 经过上述文章论述,我们知道了DecorView.mParentViewRootImpl类型,那么DecorView儿子的mParent是谁呢?它儿子的儿子的mParent又是谁呢?总不能都是ViewRootImpl类型吧!!

答案肯定是否定的。
mParent的类型永远同当前View的父级一个类型。

来源码走一圈,让我们见真知!!

ViewGroup.java

  public void addView(View child, int index, LayoutParams params) {
        ...
        //设置新LayoutParams时,addViewInner()将调用child.requestLayout()
       //因此,我们之前调用requestLayout()在我们自己,以便孩子的请求
        requestLayout();
        invalidate(true);
         //添加内部View
        addViewInner(child, index, params, false);
    }
  private void addViewInner(View child, int index, LayoutParams params,boolean preventRequestLayout) {
        ...
        // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }
        ...
    }

通过以上的分析,我们表述已经很简明了,父View将自己赋予了子ViewmParent

那么我可以清晰的总结一下:

  • DecorViewmParent的类型是ViewRootImpl
  • 其他ViewmParent的类型是Ta的父View,即 Ta的老子!! 哈哈

That's all ! Thanks Everybody!

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