美文网首页
WMS-01-setContentView的实例化与PhoneW

WMS-01-setContentView的实例化与PhoneW

作者: 天上飘的是浮云 | 来源:发表于2022-02-03 20:38 被阅读0次
    image.png

    一、我们要知道的几个点

      1. 桌面点击某个应用后的启动流程?
      1. setContentView流程,系统的布局,应用内的布局view如何准备和实例化?
      1. 2中的View数据实例化好之后交给谁?

    二、 首先我们知道了App启动流程

    1. Launcher.app --> 点击应用图标 --> 与AMS进行binder通信,告知请求 --> AMS与Zygote通过Socket通信 --> Zygote进程fork出一个App进程

    2. ActivityThread.main方法中thread.attach 来通过和AMS通信实例化和启动Application实例。

    3. 启动Application实例后,紧接着开始实例和启动main Activity。最终会调到ActivityStackSupervisor.realStartActivityLocked方法。它通过添加ClientTransaction事务的方式回调ActivityThread。ClientTransaction中会添加Item:它们会分别处理Activity不同生命周期回调(LaunchActivityItem、StartActivityItem、ResumeActivityItem、PauseActivityItem、StopActivityItem)。它们分别对应ActivityThread中的handleLaunchActivity、handleStartActivity、handleResumeActivity、handlePauseActivity、handleStopActivity。跟踪它们就能看到Activity中不同生命周期方法是如何调起的。

    4. TransactionExecutor中的execute方法中 executeCallbacks(transaction);和executeLifecycleState(transaction);就是上一个生命周期调用下一个生命周期的逻辑。

    三、setContentView流程,系统的布局,应用内的布局view如何准备和实例化?

    看setContentView有两种,一种MainActivity继承至Activity;一种是MainActivity继承至AppCompatActivity;

    3.1 AppCompatActivity这种的话,最终是到AppCompatDelegateImpl的setContentView方法
        @Override
        public void setContentView(View v) {
            ensureSubDecor();
            ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            contentParent.addView(v);
            mAppCompatWindowCallback.getWrapped().onContentChanged();
        }
    

    在setContentView里,有ensureSubDecor方法,因为AppCompatActivity是androidx包下的,在ensureSubDecor中就开始了狸猫换太子之旅,它将自己的xml布局换成了系统的android.R.id.content。

    ...
            final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                    R.id.action_bar_activity_content);
    
            final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
            if (windowContentView != null) {
                // There might be Views already added to the Window's content view so we need to
                // migrate them to our content view
                while (windowContentView.getChildCount() > 0) {
                    final View child = windowContentView.getChildAt(0);
                    windowContentView.removeViewAt(0);
                    contentView.addView(child);
                }
    
                // Change our content FrameLayout to use the android.R.id.content id.
                // Useful for fragments.
                windowContentView.setId(View.NO_ID);
                contentView.setId(android.R.id.content);
    
                // The decorContent may have a foreground drawable set (windowContentOverlay).
                // Remove this as we handle it ourselves
                if (windowContentView instanceof FrameLayout) {
                    ((FrameLayout) windowContentView).setForeground(null);
                }
            }
    
            // Now set the Window's content view with the decor
            mWindow.setContentView(subDecor);
    ...
    
    3.2 上面的AppCompatActivity就可以结束了,接着着重看Activity中的setContentView()
    • 3.2.1 Activity.setContentView实际调用的是PhoneWindow的setContentView
        public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    
    • 3.2.2 在PhoneWindow中的setContentView就是根据App所选择的系统样式获得系统的根布局,然后在把传过来的App自己的View解析反射实例化,add到mContentParent中去,下面分别说明
        public void setContentView(int layoutResID) {
           
            if (mContentParent == null) {
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
            ...
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    
    • 3.2.3 installDecor()方法先获取系统配置的布局样式,得到App能显示的mContentParent区域。
    image.png
    • 3.2.3.1 首先,mDecor为空的话,则创建一个DecorView。mDecorView实际上就是一个FrameLayout
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            ...
        } 
        
       if (mContentParent == null) {
           mContentParent = generateLayout(mDecor);
       }
    
       ...
       //后面没啥了,基本就是mContentParent一些属性相关的设置
    }
    
    • 3.2.3.2 然后,就开始通过generateLayout方法来给mContentParent赋值。它根据不同的Feature来选择不同的xml布局,如你选择的Activity是有title的、无title等。这里我们看到R.layout.screent_simple。(一般在sdk目录/platforms/选择不同的SDK版本/data/res/layout里以screen为头的xml)。在screen_simple.xm中我们可以看到熟悉的“@android:id/content”,这里就是我们自己Activity布局所要显示的父控件。
    protected ViewGroup generateLayout(DecorView decor) {
        ...
        //直接跳到根据Feature来选择不同xml布局来
        int layoutResource;
        int features = getLocalFeatures();
        
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
              
            layoutResource = R.layout.screen_title_icons;
            ...
        } else {
            layoutResource = R.layout.screen_simple;
        }
    
        //将选择好的layout的id交由DecorView去按层级解析,反射实例化并一层层addView绑定
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
        ...
        //找到id为android.R.id.content的控件
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    }
    
        ...
        //将contentParent返回
        return contentParent;
    

    screen_simple.xml布局

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:orientation="vertical">
        <ViewStub android:id="@+id/action_mode_bar_stub"
                  android:inflatedId="@+id/action_mode_bar"
                  android:layout="@layout/action_mode_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:theme="?attr/actionBarTheme" />
        <FrameLayout
             android:id="@android:id/content"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:foregroundInsidePadding="false"
             android:foregroundGravity="fill_horizontal|top"
             android:foreground="?android:attr/windowContentOverlay" />
    </LinearLayout>
    
    • 3.2.4 PhoneWindow的setContentView方法,初始化DecorView和mContentParent之后,就会将传入的App的Activity的layoutResID,使用LayoutInflater.inflate()来进行解析,解析XML、反射实例化View、并与mContentLayout按层级进行addView.
        public void setContentView(int layoutResID) {
          
            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 {
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            ...
        }
    

    四、setContentView基本先找到系统的XML布局,实例化。然后在将自己的xml实例化绑定到mContentLayout中,但是到这一步貌似onCreate已经J结束了。那下一步去哪呢?我们下一步根据Activit生命周期方法,应该到了onResume了。

    4.1 在App启动流程中的TransactionExcuter.execute中我们提过,Activity上一个生命周期方法执行完,就调用下一个生命周期方法。如下executeLifecycleState()方法。onCreate执行完后,其实是执行ResumeActivityItem中的execute方法。它会回调ActivityThread中的handleResumeActivity方法。
    public void execute(ClientTransaction transaction) {
            ...
            executeCallbacks(transaction);
    
            executeLifecycleState(transaction);
           
        }
    
    4.2 在handleResumeActivity方法中有一个地方值得注意wm.addView(decor, l),它将我们初始化好的DecorView加入到了wm中,这个wm就是performLaunchActivity方法activity.attach()里实例化的mWindowManager,它实际上是WindowManagerImpl对象。
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                String reason) {
            ...
            
            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;
                ...
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    } else {
                       ...
                    }
                }
    
                ...
        }
    
    4.3 在WindowManagerImpl.addView方法中,实际上调用的是mGlobal.addView方法,而mGlobal实际上是WindowManagerGlobal对象。
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                    mContext.getUserId());
        }
    
    4.4 进入到WindowManagerGlobal的addView方法,它会实例化一个ViewRootImpl对象,然后调用它的setView()方法。(ViewRootImpl用来管理所有的view的绘制策略,你怎么绘制由这个类管理)
    public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow, int userId) {
        ViewRootImpl root;
        synchronized (mLock) {
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
    
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                    
            }
        }
    }
    

    五、后续在跟进看编舞者类如何对view进行绘制

    相关文章

      网友评论

          本文标题:WMS-01-setContentView的实例化与PhoneW

          本文链接:https://www.haomeiwen.com/subject/doeaaltx.html