android源码--view创建原理总结

一、android界面构成

android窗口组成.png

Activity:控制模型,控制Window。
Window:承载模型,负责承载视图(View)。
View:显示模型,用于显示。
ViewRoot(ViewRootImpl):是连接WindowManager和DecorView的纽带。

二、View创建整体流程总结

1、在ActivityThread的performLaunchActivity中调用Activity的attach方法,在attach方法中完成PhoneWindow的创建。
2、在Activity的onCreate方法,setContentView方法,PhoneWindow中创建了一个DecorView,并在DecorView中填充了Activity中传入的layoutId布局。通过LayoutInflater进行布局文件的解析。DecorView,它实际是一个FrameLayout,并通过各种主题样式选择加载不同的视图来填充DecorView。
LayoutInflater一定要做成全局单例的方式,原因很简单就是为了加速view实例化的过程,共用反射的构造函数的缓存。
3、在ActivityThread的handleResumeActivity中,会调用WindowManager的addView方法将DecorView添加到WMS(WindowManagerService)上。addView找那个创建ViewRootImpl,并通过ViewRootImpl的setView方法将view添加到WMS中。
4、 在使用requestWindowFeature来设置样式时,实际上是调用了PhoneWindow的requestFeature方法,会将样式存储在Window的mLocalFeatures变量中,当installDecor时,会应用这些样式。也就是说,当需要通过requestWindowFeature来请求样式时,应该在setContentView方法之前调用,因为setContentView方法的调用会导致DecorView的创建并应用样式,如果在之后调用则会导致不会生效,因为此时DecorView已经创建完成了。

ViewRootImpl的setView方法中主要完成:View渲染(requestLayout)、(performTraservals)以及接收触摸屏幕事件。设置了一系列输入管道,将触摸事件分发给DecorView。

三、View具体创建流程

1、PhoneWindow的创建

ActivityThread.java

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
···
Activity activity = null;
        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);
            }
        } 
···
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                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, r.configCallback);
···
}

Activity.java

    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,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
···
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
    }

Activity在其attach方法中创建了Window,实际上创建的是PhoneWindow,并通过各种回调等建立起与Activity的联系,我们在Activity中使用getWindow()所得到的即是这个创建的Window。

2、setContentView (DecorView创建)

通常情况下,一个Activity的界面的创建是通过setContentView来引入布局。

mWindow = new PhoneWindow(this, window);

public void setContentView(@LayoutRes int layoutResID) {
   getWindow().setContentView(layoutResID);
   initWindowDecorActionBar();
}

而Activity的setContentView方法是通过调用Window的setContentView方法,而Window是一个抽象对象,它的具体实现类就是PhoneWindow。在PhoneWindow中找到setContentView方法

@Override
public void setContentView(int layoutResID) {
     // 如果mContentParent为空时先构建
   if (mContentParent == null) {
       installDecor();
   } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
       mContentParent.removeAllViews();
   }

   if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
       final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
               getContext());
       transitionTo(newScene);
   } else {
            // 通过LayoutInflater解析布局
       mLayoutInflater.inflate(layoutResID, mContentParent);
   }
   mContentParent.requestApplyInsets();
   final Callback cb = getCallback();
   if (cb != null && !isDestroyed()) {
       cb.onContentChanged();
   }
}

至此完成了在PhoneWindow中创建了一个DecorView,并在DecorView中填充了Activity中传入的layoutId布局。但还未被绘制到屏幕上,这一步要到ActivityThread的handleResumeActivity时才会真正执行。

通过LayoutInflater解析布局。主要通过inflate方法解析布局

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 

上面的inflate方法所做的操作主要有以下几步:

解析xml的根标签
如果根标签是merge,那么调用rInflate解析,将merge标签下的所有子View直接添加到根标签中
如果不是merge,调用createViewFromTag解析该元素
调用rInflate解析temp中的子View,并将这些子View添加到temp中
通过attachToRoot,返回对应解析的根视图

解析View的时候是通过.来判断是内置的View还是自定义的View的,那么我们就能知道为什么在写布局文件中自定义的View需要完整路径了。

public final View createView(String name, String prefix, AttributeSet attrs)
       throws ClassNotFoundException, InflateException {
   // 从缓存中获取view的构造函数
   Constructor<? extends View> constructor = sConstructorMap.get(name);
   if (constructor != null && !verifyClassLoader(constructor)) {
       constructor = null;
       sConstructorMap.remove(name);
   }
   Class<? extends View> clazz = null;

   try {
       Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
            // 如果没有缓存
       if (constructor == null) {
           // 如果前缀不为空构造完整的View路径并加载该类
           clazz = mContext.getClassLoader().loadClass(
                   prefix != null ? (prefix + name) : name).asSubclass(View.class);
           // 获取该类的构造函数
           constructor = clazz.getConstructor(mConstructorSignature);
           constructor.setAccessible(true);
           // 将构造函数加入缓存中
           sConstructorMap.put(name, constructor);
       } else {
       }

       Object[] args = mConstructorArgs;
       args[1] = attrs;
            // 通过反射构建View
       final View view = constructor.newInstance(args);
       if (view instanceof ViewStub) {
           // Use the same context when inflating ViewStub later.
           final ViewStub viewStub = (ViewStub) view;
           viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
       }
       return view;

   }
}

createView相对简单,通过判断前缀,来构建View的完整路径,并将该类加载到虚拟机中,获取构造函数并缓存,再通过构造函数创建该View对象,并返回。这个时候我们就获得了根视图。接着调用rInflateChildren方法解析子View,并最终调用rInflate方法:

void rInflate(XmlPullParser parser, View parent, Context context,
       AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    // 获取树的深度,通过深度优先遍历
   final int depth = parser.getDepth();
   int type;

   while (((type = parser.next()) != XmlPullParser.END_TAG ||
           parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

       if (type != XmlPullParser.START_TAG) {
           continue;
       }

       final String name = parser.getName();
       
       if (TAG_REQUEST_FOCUS.equals(name)) {
           parseRequestFocus(parser, parent);
       } else if (TAG_TAG.equals(name)) {// 解析tag标签
           parseViewTag(parser, parent, attrs);
       } else if (TAG_INCLUDE.equals(name)) {// 解析include标签
           if (parser.getDepth() == 0) {
               throw new InflateException("<include /> cannot be the root element");
           }
           parseInclude(parser, context, parent, attrs);
       } else if (TAG_MERGE.equals(name)) {// 解析到merge标签,并报错
           throw new InflateException("<merge /> must be the root element");
       } else {
            // 解析到普通的子View,并调用createViewFromTag获得View对象
           final View view = createViewFromTag(parent, name, context, attrs);
           final ViewGroup viewGroup = (ViewGroup) parent;
           final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
           // 递归解析
           rInflateChildren(parser, view, attrs, true);
           // 将View加入到父视图中
           viewGroup.addView(view, params);
       }
   }

   if (finishInflate) {
       parent.onFinishInflate();
   }
}

//www.greatytc.com/p/c228e65ea1d9
//www.greatytc.com/p/427c59a70da6
https://blog.csdn.net/qq_28261343/article/details/78817184

3、View的显示绘制

在ActivityThread的handleResumeActivity中,会调用WindowManager的addView方法将DecorView添加到WMS(WindowManagerService)上。
ActivityThread.java

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
        // 会调用activity的onResume方法
        r = performResumeActivity(token, clearHide, reason);

            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
//setContentView中完成的DecorView的创建
                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 && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }

通过WindowManager的addView方法添加DecorView。WindowManager是一个接口,实际通过WindowManagerImpl进行添加。
WindowManagerImpl.java

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

WindowManagerGlobal.java

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ViewRootImpl root;
        View panelParentView = null;
···
            //!!!创建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);
        }
}

通过ViewRootImpl的setView方法将view添加到WMS中

ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     //  会调用scheduleTraversals()方法,后续会执行measure-layout-draw?确保View在添加到Window上显示到屏幕之前,已完成测量和绘制操作。
                  requestLayout();
        ···
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } 
}

requestLayout执行布局刷新,调用mWindowSession的方法将View添加到WMS中。
最终通过Binder,远程调用WMS中的addToDisplay完成View的添加。

ViewRootImpl的setView方法中主要完成:View渲染(requestLayout)以及接收触摸屏幕事件。设置了一系列输入管道,将触摸事件分发给DecorView。

ViewRootImpl.java

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

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//mChoreographer.postCallback主要逻辑是向Handler发送一个异步消息,然后在mTraversalRunnable(是Runnable对象)中处理          
mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

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

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        ...
            performTraversals();//执行真正的view绘制流程measure-->layout-->draw
      ...
        }
    }

四、ViewRootImpl

ViewRoot是一个ViewTree的管理者,而不是ViewTree的根节点。
严格意义上说,ViewTree的根节点只有DecorView。
ViewRoot将DecorView和PhoneWindow(Activity创建的Window实例)“组合”起来。

View的绘制三大流程(measure,layout,draw)均是通过ViewRootImpl来完成的。
在ActivityThread中,当Activity对象被创建完毕后,会将DecorVidew添加到Window中,同时会创建ViewRootImpl对象,利用ViewRootImpl对象来建立Window对象和DecorView之间的关系。

View的绘制是从ViewRootImlp的performTraversals开始的,经过measure.layout.draw三方法最终将一个View绘制出来。measure测量View宽高。layout确定View位置,draw负责绘制。

window持有DecorView,DecorView在被添加到window到时候,会产生一个ViewRootImlp,ViewRootImlp在调用performTraversals(遍历表演)的时候会调用顶级view的测量,布局和绘制,然后顶级view会挨个调用自己子布局的测量布局和绘制,这样一个页面就展现出来了。

五、 attachWindow和 detachWindow

attach流程
当在ActivityThread.handleResumeActivity()方法中调用WindowManager.addView()方法时,最终是调去了

WindowManagerImpl.addView() -->
WindowManagerGlobal.addView()
ViewRootImpl.setView()
ViewRootImpl.performTraversals()
host.dispatchAttachedToWindow(mAttachInfo, 0)
//ViewGroup和View有不同实现,ViewGroup依次分发到子View

detach流程

ActivityThread.handleDestroyActivity() -->
WindowManager.removeViewImmediate() -->
WindowManagerGlobal.removeViewLocked()方法 —>
ViewRootImpl.die() --> doDie() -->
ViewRootImpl.dispatchDetachedFromWindow()

1、onAttachedToWindow方法是在Activity resume的时候被调用的,也就是act对应的window被添加的时候,且每个view只会被调用一次,父view的调用在前,不论view的visibility状态都会被调用,适合做些view特定的初始化操作;
2、onDetachedFromWindow方法是在Activity destroy的时候被调用的,也就是act对应的window被删除的时候,且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作;
3、这些结论也正好解释了方法名里带有window的原因,有些人可能会想,那为啥不叫onAttachedToActivity/onDetachedFromActivity,因为在Android里不止是Activity,这里说的内容同样适用于Dialog/Toast,Window只是个虚的概念,是Android抽象出来的,最终操作的实体还是View,这也说明了前面的WindowManager接口为啥是从ViewManager接口派生的,因为所有一切的基石归根结底还是对View的操作。

//www.greatytc.com/p/e7b6fa788ae6

六、Token及其他窗口

1、Token是什么?
类型为IBinder,是一个Binder对象。
主要分两种Token:
指向Window的token: 主要是实现WmS和应用所在进程通信。
指向ActivityRecord的token: 主要是实现WMS和AMS通信的。

2、Token的使用场景?
Activity创建时,AMS中需要根据Token去找到对应的ActivityRecord。
Popupwindow的showAtLocation第一个参数需要传入View,这个View就是用来获取Token的。

3、WindowSession是什么
在WindowManager的addView中会创建ViewRootImpl,内部会通过WMS去获取WindowSession
WindowSession的类型是IWindowSession,本身是Binder对象,真正实现类是Session。

WindowManager.LayoutParams上有三种窗口类型type,对应为:
应用程序窗口:type值在 FIRST_APPLICATION_WINDOW LAST_APPLICATION_WINDOW 须将token设置成Activity的token。
eg: Activity窗口,Dialog

子窗口: type值在 FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW SubWindows与顶层窗口相关联,需将token设置成它所附着宿主窗口的token。
eg: PopupWindow(想要依附在Activity上需要将token设置成Activity的token)

系统窗口 : type值在 FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW SystemWindows不能用于应用程序,使用时需要有特殊权限,它是特定的系统功能才能使用。
eg: Toast,输入法等

token是用来表示窗口的一个令牌,只有符合条件的token才能被WMS通过添加到应用上。
//www.greatytc.com/p/bac61386d9bf

参考

//www.greatytc.com/p/c228e65ea1d9
//www.greatytc.com/p/427c59a70da6
https://blog.csdn.net/qq_28261343/article/details/78817184
//www.greatytc.com/p/9da7bfe18374
//www.greatytc.com/p/e7b6fa788ae6
//www.greatytc.com/p/bac61386d9bf

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

推荐阅读更多精彩内容