从setContentView到onResume应用显示过程分析

之前总体笼统地分析了Acitivity从启动到显示的过程(Activty启动到显示的过程[一]Activty启动到显示的过程[二]),发现很多细节没有注意到,后续挑些过程中比较重要的部分重点分析。

在上一篇文章分析了一个app从zygote到onCreate的过程(从zygote到onCreate应用启动过程分析),onCreate方法中不能少的就是setContentView,这次就分析Activity从setContentView到onResume的过程,顺便理清楚以下类的关系:

Activity, Window, DecorView, ViewRootImpl, IWindowSession, IWindow, WindowState, SurfaceSession.

先看setContentView部分:

在这里插入图片描述

Activity在应用程序中表示显示界面,持有Window对象mWindow。

一、Activity初始化

Activity在ActivityThread的performLaunchActivity()方法中被实例化,接下来就调用attach()方法,初始化一些必要信息,Activity的Window变量就是在这里被初始化的:

    // ActivityThread.java
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    
    
      Activity activity = null;
      activity = mInstrumentation.newActivity(
                        cl, component.getClassName(), r.intent);
      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.activityConfigCallback,
                            r.assistToken, r.shareableActivityToken);
      return activity;
    }

查看Activity->attach()方法:

    // 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, IBinder assistToken,
            IBinder shareableActivityToken) {
    
    
      //.......
      mWindow = new PhoneWindow(this, window, activityConfigCallback);
      mToken = token;
      mWindow.setWindowManager(
                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                    mToken, mComponent.flattenToString(),
                    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        // mWindow 拥有mToken引用。
      mWindowManager = mWindow.getWindowManager();			
        // mWindowManager拥有mWindow引用, 后续getSystemService(Context.WINDOW_SERVICE)返回此对象。
    }

mWindow变量在这里被初始化为PhoneWindow对象,mToken也被赋值,同样被传递给mWindow变量,最终赋值给PhoneWindow的父类Window.mAppToken变量。

    // Window.java
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
    
    
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
    
    
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

mWindowManager通过createLocalWindowManager(Window parentWindow)方法被实例化,持有Window对象的引用,并赋值给Activity对象,也就是说Activity对象在attach()后,便拥有了一个本地的WindowManager对象,它持有window对象,window对象持有token,后续在此Activity中addView()都是通过这个本地的WindowManager获取window.mAppToken来进行验证。

这个token是在Activity启动一开始时被ActivityStater实例化ActivityRecord对象时创建的,通过Intent作为参数实例化ActivityRecord.Token, “new Token(_intent).asBinder()”:

    // ActivityRecord.java
    ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller,
            int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage,
            @Nullable String _launchedFromFeature, Intent _intent, String _resolvedType,
            ActivityInfo aInfo, Configuration _configuration, ActivityRecord _resultTo,
            String _resultWho, int _reqCode, boolean _componentSpecified,
            boolean _rootVoiceInteraction, ActivityStackSupervisor supervisor,
            ActivityOptions options, ActivityRecord sourceRecord) {
    
    
                
        super(_service.mWindowManager, new Token(_intent).asBinder(), TYPE_APPLICATION, true,
                    null /* displayContent */, false /* ownerCanManageAppTokens */);
        appToken = (Token) token;
    }
    
    static class Token extends IApplicationToken.Stub {
    
    }

ActivityRecord作为在Framework层(system_server进程)用于管理应用层(app进程)Activity的对象,其嵌套类Token的作用就是将两者一一对应关联起来,ClientTransaction对象在两者沟通中扮演了一个重要的角色,要实例化ClientTransaction对象需要提供IApplicationThread和ActivityRecord.Token参数:

    // ActivityStackSupervisor.java
    final ClientTransaction clientTransaction = ClientTransaction.obtain(
            proc.getThread(), r.appToken);

值得注意的是,token是IBinder对象,支持Parcelble()序列化接口,在app进程(客户端)和system_server(服务端)通过log打印token对象会得到不同的结果,app进程(客户端)是一个BinderProxy对象。

在这里插入图片描述

关于ActivityRecord.Token和更多Activity启动细节可以查看之前的文章(Activty启动到显示的过程[一])。

Activity初始化后,onCreate()生命周期方法被调用,调用setContentView()。

二、setContentView

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

setContentView就两行代码,第一行是调用PhoneWindow处理setContentView,第二行是处理ActionBar。

需要注意的是一般的安卓应用程序继承的是AndroidX扩展包的AppCompatActivity,AppCompatActivity->setContentView()和Activity有些差异,所以也能从界面上看出继承AppCompatActivity类的Activity默认有ActionBar,而继承安卓原生Framework中的android.app.Activity类的Activity是没有ActionBar的。

    // PhoneWindow.java
    @Override
    public void setContentView(int layoutResID) {
    
    
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        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);			//将应用程序的layout添加为mContentParent子view
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
    
    
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

从上面的UML图中可以看到PhoneWindow有两个比较重要的类变量,mDecor,mContentParent。在安卓的设计中,mContentParent是DecorView的子view,应用程序通过setContentView(R.layout.xml)添加的布局都被添加到mContentParent中,又是mContentParent的子view:

    mLayoutInflater.inflate(layoutResID, mContentParent);//layoutResID为应用程序setContenView(R.layout)传递的参数

一开始mContentParent变量为null,installDecor()查看DecorView的初始化流程:

    // PhoneWindow.java  
    		private void installDecor() {
    
    
            if (mDecor == null) {
    
    
                mDecor = generateDecor(-1);
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
    
    
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
    
    
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
    
    
                mContentParent = generateLayout(mDecor);
              	//......
            }
        }

通过generateDecor(-1)实例化DecorView对象:

    // PhoneWindow.java  
    protected DecorView generateDecor(int featureId) {
    
    
      //......
      return new DecorView(context, featureId, this, getAttributes());
    }

三、DecorView初始化

和我们熟悉的View实例化不同的是,Decorview的实例化比较简单,并没有指定layout布局文件,从上面的UML图可以看到Decorview继承了FrameLayout,mContentParent是它的子view,mContentParent是怎么成为DecorView的子view的,把应用程序的布局添加到DecorView中呢?接着查看generateLayout()方法:

    protected ViewGroup generateLayout(DecorView decor) {
    
    
      //......
      int layoutResource;
      int features = getLocalFeatures();
      if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
    
    
        layoutResource = res.resourceId;
      } else if(...) {
    
    
        layoutResource = R.layout.screen_progress;
      } else if(...) {
    
    
        layoutResource = R.layout.screen_custom_title;
      } else {
    
    
        layoutResource = R.layout.screen_simple;
      }
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      return contentParent;
    }

generateLayout()先定义了layoutResource变量,判断当前features指定合适的布局文件,在确定layout布局文件后,调用DecorView的onResourcesLoaded(mLayoutInflater, layoutResource)方法:

    // DecorView.java
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    
    
      //......
        final View root = inflater.inflate(layoutResource, null);
      	addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
      	mContentRoot = (ViewGroup) root;
      	initializeElevation();
    }

onResourcesLoaded为我们解答了DecorView布局的问题,DecorView继承了FrameLayout,它将layoutResource指定的布局文件实例化为一个view添加到自己内部,宽高都为“MATCH_PARENT”,并把它赋值给mContentRoot变量,自此DecorView才有了布局,而不是一个FrameLayout空壳,完成了真正的初始化。

我们查看DecorView常用的布局文件R.layout.screen_simple:

    <!-- 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>

接着看generateLayout()方法中mContentParent变量的初始化,contentParent被赋值给id为“ID_ANDROID_CONTENT”的view,并返回赋值给mContentParent。

"ID_ANDROID_CONTENT"在Window.java文件中定义,值为“com.android.internal.R.id.content”。

    // Window.java
    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

对,就是上面布局文件R.layout.screen_simple中 id 为 “content” 的view。

说明了mContentParent是DecorView的子view,其在DecorView中的布局位置。

使用UI检查工具查看继承android.app.Activity类Activity的布局解构,可以看到"id/content"的mContentParent。

在这里插入图片描述

作为对比,看下继承AppCompatActivity类Activity的布局解构,可以看到“id/content”和“id/action_bar_container”处于平级,顶部蓝色的ActionBar就处于“id/action_bar_container”了,中间大片的白色控件就是“id/content”留给应用显示区域,它们都是“id/decor_content_parent”的子view。
在这里插入图片描述

四、handleResumeActivity

onResume负责Activity的显示,ActivityThread->handleResumeActivity()方法负责处理onResume。

    // ActivityThread.java
    @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
    
    
      //......
            final Activity a = r.activity;
                View decor = r.window.getDecorView();
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                
      					wm.addView(decor, l);
    }

可以看到,显示Activity就是通过ViewManager.addView(),addView的对象就是刚才创建的DecorView。

五、addView

    // WindowManagerGlobal.java
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
    
    
      		//......
      					ViewRootImpl root;
                if (windowlessSession == null) {
    
    
                    root = new ViewRootImpl(view.getContext(), display);
                } else {
    
    
                    root = new ViewRootImpl(view.getContext(), display,
                            windowlessSession, new WindowlessWindowLayout());
                }
      
                view.setLayoutParams(wparams);
    
                mViews.add(view);
                mRoots.add(root);
                mParams.add(wparams);
      
      					root.setView(view, wparams, panelParentView, userId);
    }

创建ViewRootImpl对象,调用其setView()方法。ViewRootImpl 是 View 的最高层级, 实现了 View 和 WindowManager 之间所需要的协议。

    // ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
            AttachedSurfaceControl {
    
    
    
        final IWindowSession mWindowSession;
    		final W mWindow;
        
        public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            WindowLayout windowLayout) {
    
    
      			//......
            mWindowSession = session;
            mDisplay = display;
            mThread = Thread.currentThread();
            mWindow = new W(this);
            mChoreographer = Choreographer.getInstance();
    		}
        
        static class W extends IWindow.Stub {
    
    
          //......
        }
    }

这里不会对View的显示过程有细致的分析,不过可以通过ViewRootImpl的构造方法查看几个关键类之间的关系。

从WindowManagerGlobal->addView()可以看出,每次一个新的Activity被创建显示,都会创建一个新的ViewRootImpl对象,把Activity中的显示内容DecorView交给它去处理。

  • IWindowSession

ViewRootImpl关联了IWindowSession对象,在addView()方法中,如果windowlessSession==null,就会通过WindowManagerGlobal.getWindowSession()方法获取sWindowSession,这是一个静态变量,使用了单例设计模式,确保一个进程中只有一个IWindowSession对象,IWindowSession是app和WindowManger沟通的桥梁。

  • IWindow

IWindow在ViewRootImpl中被嵌套类W实现,和上一篇文件中(https://blog.csdn.net/qq_36063677/article/details/129756237)分析的ActivityThread内部类ApplicationThread一样,都使用了静态代理设计模式,用于WMS(system_server进程)管理ViewRootImpl,或者说Activity(Activity和ViewRootImpl是一一对应的关系)。和IWindowSession进程单例不一样的是,每次创建新的ViewRootImpl对象,构造方法也会实例化新的IWindow对象mWindow = new W(this),IWindowSession对应每个app应用程序,ViewRootImpl和IWindow对应每个Activity。

六、setView

接着往下看setView()方法:

    // ViewRootImpl.java
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
    
    
      //......
          mView = view;
      
      	  requestLayout();
      
    	  res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(), userId,
                                mInsetsController.getRequestedVisibilities(), 				inputChannel, mTempInsets,
                                mTempControls, attachedFrame, sizeCompatScale);  
    }

这里先不关注requestLayout();,查看mWindowSession.addToDisplayAsUser()方法,Session类是IWindowSession的实现类,addToDisplayAsUser将逻辑交给WMS处理,在这里开始初始化WindowState对象:

七、WindowState

    // WindowManagerService.java
    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
    
    
      //......
      
      					// 1.获取DisplayContent
      					final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
      					// 2.获取token(ActivityRecord)
      					WindowToken token = displayContent.getWindowToken(
                        hasParent ? parentWindow.mAttrs.token : attrs.token);
      
      					// 3.初始化WindowState
               			 final WindowState win = new WindowState(this, session, client, token, parentWindow,
                        appOp[0], attrs, viewVisibility, session.mUid, userId,
                        session.mCanAddInternalSystemWindow);
      					// 4.给这个窗体描述打开一个输入通道, 用于接收屏幕的点击事件(事件分发)
               		 	final boolean openInputChannels = (outInputChannel != null
		                        && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
		                if  (openInputChannels) {
    
    
		                    win.openInputChannel(outInputChannel);
		                }
               			 // 5.初始化SurfaceSession
      					win.attach();
      					// 6.添加到mWindowMap用于WMS管理
                		mWindowMap.put(client.asBinder(), win);
      
      					// 7.维护WindowContainer.mChildren变量
      					win.mToken.addWindow(win);
      					// 8.更新接收消费输入事件的Window
                		displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
    }
  1. 创建DisplayContent,InputMonitor在DisplayContent构造方法中被初始化。
  2. 既然是在Activity中addView,首先要做的就是获取代表Activity的token,验证token合法性。
  3. 对于WMS,每一个View都需要一个WindowState对象用来管理,传递给构造方法的参数有 WMS, session(IWindowSession),client(IWindow),token(ActivityRecord)等。
  4. 给这个窗体描述打开一个输入通道InputChannle, 用于接收屏幕的点击事件(事件分发)。关于InputChannel参考:Android InputChannel事件发送接收系统分析
  5. 接下来又是attach()方法,每个命名为attach的方法都不简单,判断刚在构造方法传递的IWindowSession对象是否初始化了SurfaceSession,如果没有开始初始化SurfaceSession与surfaceflinger建立连接,更多关于SurfaceSession细节(https://blog.csdn.net/qq_36063677/article/details/129369308)。
  6. 然后把创建的WindowState添加到mWindowMap用于后续管理:
    /** Mapping from an IWindow IBinder to the server's Window object. */
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
    
  7. mToken继承了WindowContainer类,在这里是ActivityRecord对象,mParent是Task对象,mChildren是WindowState,将创建的win对象添加到mChildren中管理。
  8. 更新接收消费输入事件的Window。

关于WindowContainer结构,参考(https://juejin.cn/post/7140286041121882126)

猜你喜欢

转载自blog.csdn.net/qq_36063677/article/details/129908973