Android程式啟動與Activity顯示

Jensen95發表於2019-03-04

前言

這段時間,leader安排的任務進行Android外掛化,熱修復相關的調研,對於外掛化和熱修復涉及到的核心技術點,在於對於類裝載,資源裝載的認識還有對於啟動流程的熟悉,帶著該任務,於是有了接下來,一系列的文章,從程式啟動,Activity顯示,Dex裝載,資源裝載,最後主流幾個外掛化,熱修復原始碼實現的分析。本篇先從程式的啟動,到一個Activity的顯示流程出發分析。

啟動一個程式

在Anroid中,程式是一個執行元件的容器,系統執行一個元件的時候,啟動包含它的程式,當元件不再使用,程式會被關閉。AMS負責對應應用程式的啟動。

開啟一個新的程式,在AMS中首先呼叫addAppLocked

final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
        String abiOverride) {
    ProcessRecord app;
    if (!isolated) {
        //從已經開啟記錄下的程式中查詢程式記錄
        app = getProcessRecordLocked(info.processName, info.uid, true);
    } else {
        app = null;
    }
    //app為空的時候,建立一個新的程式,同時更新內部程式管理結構
    if (app == null) {
        app = newProcessRecordLocked(info, null, isolated, 0);
        updateLruProcessLocked(app, false, null);
        updateOomAdjLocked();
    }

    .....
    if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
        app.persistent = true;
        app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
    }
    if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
        mPersistentStartingProcesses.add(app);
      //呼叫程式的啟動方法,啟動一個新的程式
        startProcessLocked(app, "added application", app.processName, abiOverride,
                null /* entryPoint */, null /* entryPointArgs */);
    }

    return app;
}
複製程式碼

首先會從已經啟動的程式中查詢相應的程式資訊,ProcessRecord,如果不存在則會建立一個出來,然後呼叫startProcessLocked方法,來開啟一個新的程式。


private final void startProcessLocked(ProcessRecord app, String hostingType,
        String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
    //啟動應用
    ....
    Process.ProcessStartResult startResult = Process.start(entryPoint,
            app.processName, uid, uid, gids, debugFlags, mountExternal,
            app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
            app.info.dataDir, entryPointArgs);
      ....
  //傳送定時訊息,如果App的啟動超時,則會ANR
    synchronized (mPidsSelfLocked) {
        this.mPidsSelfLocked.put(startResult.pid, app);
        if (isActivityProcess) {
            Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
            msg.obj = app;
            mHandler.sendMessageDelayed(msg, startResult.usingWrapper
                    ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
        }
    }
    ....

}
複製程式碼

程式的開啟呼叫的是Process的start方法。

public static final ProcessStartResult start(final String processClass,
                              final String niceName,
                              int uid, int gid, int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String[] zygoteArgs) {
    try {
        return startViaZygote(processClass, niceName, uid, gid, gids,
                debugFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, zygoteArgs);
    } catch (ZygoteStartFailedEx ex) {
      
    }
}
複製程式碼

start方法中,通過呼叫startViaZygote,通過zygote程式來開啟一個新的程式。

private static ProcessStartResult startViaZygote(final String processClass, ...){

    //配置通過Zygote啟動的引數,最終通過socket寫入到Zygote程式,來開啟一個新的程式

}
複製程式碼

方法的具體執行是通過socket將程式的啟動資訊,寫入到zygote中,然後通過其啟動一個新的程式,同時對於該程式,也指定了一個類作為執行的入口類,這個類就是ActivityThread。

entryPoint = "android.app.ActivityThread";
複製程式碼

start方法中的entryPoint,這個就是程式啟動後要執行的Java類,程式啟動後,所有操作就轉交到ActivityThread的執行,因此,這個類也是整個應用執行的核心。這個類首先被執行的是其main函式。

image.png
public static void main(String[] args) {
      ...
      Looper.prepareMainLooper();
     ActivityThread thread = new ActivityThread();
    thread.attach(false);
     Looper.loop();
    ....
}
複製程式碼

ActivityThread的attach方法。

private void attach(boolean system) {
      ......
final IActivityManager mgr = ActivityManagerNative.getDefault();
    try {
        //呼叫AMS的attachApplication方法,傳遞ApplicationThread進去
        mgr.attachApplication(mAppThread);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
      .....
}
複製程式碼

Ams的attachApplication方法會呼叫到Ams的attachApplicationLocked方法,

thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,...)
複製程式碼

這個方法的主要功能是建立出應用程式中的各種物件,是比較核心的方法。

private void handleBindApplication(AppBindData data) {
    // 註冊當前UI執行緒到Runtime作為一個敏感執行緒
    VMRuntime.registerSensitiveThread();

    // 設定程式的啟動時間
    Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());

    // 建立程式的配置物件
    mBoundApplication = data;
    mConfiguration = new Configuration(data.config);
    mCompatConfiguration = new Configuration(data.config);


    //當版本低於Honeycomb MR1,將AsyncTask的實現通過使用執行緒池
    if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
        AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    //設定應用的時區和應用的地區
    TimeZone.setDefault(null);
    LocaleList.setDefault(data.config.getLocales());

    synchronized (mResourcesManager) {
        mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
        mCurDefaultDisplayDpi = data.config.densityDpi;
        applyCompatConfiguration(mCurDefaultDisplayDpi);
    }


    // 建立Instrumentation
    final InstrumentationInfo ii;
    if (data.instrumentationName != null) {
        try {
            ii = new ApplicationPackageManager(null, getPackageManager())
                    .getInstrumentationInfo(data.instrumentationName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(
                    "Unable to find instrumentation info for: " + data.instrumentationName);
        }

        mInstrumentationPackageName = ii.packageName;
        mInstrumentationAppDir = ii.sourceDir;
        mInstrumentationSplitAppDirs = ii.splitSourceDirs;
        mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii);
        mInstrumentedAppDir = data.info.getAppDir();
        mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
        mInstrumentedLibDir = data.info.getLibDir();
    } else {
        ii = null;
    }

    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    updateLocaleListFromAppContext(appContext,
            mResourcesManager.getConfiguration().getLocales());


    // Continue loading instrumentation.
    if (ii != null) {
        final ApplicationInfo instrApp = new ApplicationInfo();
        ii.copyTo(instrApp);
        instrApp.initForUser(UserHandle.myUserId());
        final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                appContext.getClassLoader(), false, true, false);
        final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
        //
        try {
            final ClassLoader cl = instrContext.getClassLoader();
            mInstrumentation = (Instrumentation)
                cl.loadClass(data.instrumentationName.getClassName()).newInstance();
        } catch (Exception e) {
            throw new RuntimeException(
                "Unable to instantiate instrumentation "
                + data.instrumentationName + ": " + e.toString(), e);
        }

        final ComponentName component = new ComponentName(ii.packageName, ii.name);
        mInstrumentation.init(this, instrContext, appContext, component,
                data.instrumentationWatcher, data.instrumentationUiAutomationConnection);

        if (mProfiler.profileFile != null && !ii.handleProfiling
                && mProfiler.profileFd == null) {
            mProfiler.handlingProfiling = true;
            final File file = new File(mProfiler.profileFile);
            file.getParentFile().mkdirs();
            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
        }
    } else {
        mInstrumentation = new Instrumentation();
    }


    //Application中指定了big heap,清除限制
    if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
        dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
    } else {
        dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
    }


    final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
    try {
        //生成Application物件
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
            .....

        try {
            //呼叫Instrumentation的onCreate方法
            mInstrumentation.onCreate(data.instrumentationArgs);
        }
        catch (Exception e) {
            
        }

        try {
            //呼叫Application的onCreate方法
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
           
        }
    } finally {
        StrictMode.setThreadPolicy(savedPolicy);
    }
}
複製程式碼

至此一個應用程式被開啟,同時其Instrumentation和Application的onCreate方法也被呼叫了,接下來就是Activity的執行。

ActivityThread mian函式執行

Activity啟動到顯示

從上面的程式啟動可以得知每一個程式對應一個Application,對應一個ActivityThread,也對應這一個Instrumentation。對於Activity的啟動會呼叫到其handleLaunchActivity方法。

handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
		....
     WindowManagerGlobal.initialize();
     Activity a = performLaunchActivity(r, customIntent);
     if (a != null) {
     	handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, 			r.lastProcessedSeq, reason);
                    ...
     } else {
        ActivityManagerNative.getDefault()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
    }
}
複製程式碼

該方法首先對WindowManagerGlobal做了初始化操作,然後呼叫了performLaunchActivity方法,返回一個Activity物件後,返回物件偽非空,則呼叫handleResumeActivity。如果為空呼叫ActivityManager的finishActivity方法。對於啟動,這裡performLaunchActivityhandleResumeActivity兩個方法是核心。接下來將針對這兩個方法來進行分析。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

   //獲取該Activity的包資訊,這裡為LoadedApk型別
   ActivityInfo aInfo = r.activityInfo;
   if (r.packageInfo == null) {
       r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
   }
		...
      //建立Activity例項
	java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(    
    cl, component.getClassName(), r.intent);
    //獲取Application例項
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);

   //建立視窗例項,並呼叫activity的attch方法,attach該視窗    
    Window window = null;
    if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
       window = r.mPendingRemoveWindow;
       r.mPendingRemoveWindow = null;
       r.mPendingRemoveWindowManager = null;
     }

     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);

                    ....
     //為Activity設定主題
    int theme = r.activityInfo.getThemeResource();
    if (theme != 0) {
         activity.setTheme(theme);
     }
     ....
   //呼叫該Activity的onCreate方法
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    ....
    mActivities.put(r.token, r);
    ...
}
複製程式碼

首先根據Activity的資訊來獲取相關的包資訊,這裡呼叫了getPackInfo來獲得相關的包資訊。得到一個LoadedApk型別來表示當前的Activity的包資訊。

public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
            int flags) {
    return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId());
}
複製程式碼

其中包含了Activity相關的資訊Application資訊,資源目錄等等。在獲得了LoadedApk例項之後,呼叫其makApplication方法,我們會疑問,在啟動一個Activity的時候,難道每次都要建立一個Application物件嗎?跟進原始碼。

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
  if (mApplication != null) {
      return mApplication;
  }
    ....
 //建立Application例項
  java.lang.ClassLoader cl = getClassLoader();
  if (!mPackageName.equals("android")) {
       initializeJavaContextClassLoader();
   }
   ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
   app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
   appContext.setOuterContext(app);

   mActivityThread.mAllApplications.add(app);
   mApplication = app;
    //呼叫Application的onCreate方法
   instrumentation.callApplicationOnCreate(app);
}
複製程式碼

通過makeApplication的方法實現,我們可以看到其首先判斷Application物件是否建立,如果沒有建立,則初始化類裝載器,然後建立Application物件,建立完成,則呼叫Application物件的onCreate方法。這裡也就是我們所熟知的在我們自定義Application的時候重寫的onCreate方法將會被呼叫。
繼續回到上面程式碼的分析,這裡的Activity通過類裝載器被裝載出來,然後例項化出一個物件,然後呼叫了其attach方法,進行了一系列資訊的配置。然後呼叫了mInstrumentation,呼叫了callActivityOnCreate。同時也會將我們的Activity新增到mActivitys中,這裡其定義如下。

final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
複製程式碼

通過這段程式碼可以看到,呼叫了acticity的attach方法,跟進attach方法。

final void attach(Context context, ....,Window window) {
     mWindow = new PhoneWindow(this, window);
     mWindow.setWindowControllerCallback(this);
     mWindow.setCallback(this);
     mWindow.setOnWindowDismissedCallback(this);
       
        .....
     mWindow.setWindowManager(                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
          .....);
       .....
    mWindowManager = mWindow.getWindowManager();
}
複製程式碼

至此,我們Activity中的Window已經被建立出來了。Window實際型別為PhoneWindow。同時為該Window設定了WindowManager。至此,我們雖然不瞭解Window是個什麼東西,但是至少,我們可以知道的一點就是每一個Activity的建立是會有持有一個Window物件的。然後Instrumentation的callActivityOnCreate方法被呼叫。

callActivityOnCreate

public void callActivityOnCreate(Activity activity, Bundle icicle) {
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);
}
複製程式碼

這裡對於Activity的具體啟動細節,我們不做關心,具體細節,接下來的原始碼分析會做介紹,這裡先看一下Activity的performCreate方法。

final void performCreate(Bundle icicle) {
   restoreHasCurrentPermissionRequest(icicle);
   onCreate(icicle);
   mActivityTransitionState.readState(icicle);
   performCreateCommon();
}
複製程式碼

這個時候呼叫了Activity的performCreate函式,呼叫了Activity的onCreate,我們一般會在onCreate中呼叫setContentView,進行我們佈局檔案的設定。這也是比較奇怪的一點,為什麼,我們呼叫了該方法,傳遞一個xml佈局檔案,我們的View就顯示出來了呢?這便是這次程式碼分析的核心,所有圍繞的相關知識點也會在此被引出。接下來,讓我們剝繭抽絲,逐層遞進。

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
複製程式碼

此處的Window即為在attach中得到的PhoneWindow的例項。

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);
    }
        ...
}
複製程式碼

首先判斷mContentParent是否為空,mContentParent是用來放置contentView的,如果不為空則要清理掉所有的view,如果為null,呼叫installDecor(),其中,我們可以看到有呼叫對於transitions這個feature的判斷,這個是在Android系統5.0之後新增的一個功能,可以用來實現Activity的過渡,同時還是實現Activity之間的元素共享,使得Activity間切換更加的絲滑流暢。這裡對於該場景不做分析,我們跳過看其具體的View裝載,然後呼叫了

mLayoutInflater.inflate(layoutResID, mContentParent);

private void installDecor() {
	mDecor = generateDecor(-1);
	....
    mContentParent = generateLayout(mDecor);
	...
     //過渡動畫,標題,logo,UI選項的顯示處理
}
複製程式碼

在installDecor中,首先呼叫了generateDecor方法,然後根據建立的DecorView,來生成ContentView。

protected DecorView generateDecor(int featureId) {
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
          if (applicationContext == null) {
              context = getContext();
          } else {
            context = new DecorContext(applicationContext, getContext().getResources());
             if (mTheme != -1) {
                context.setTheme(mTheme);
             }
         }
     } else {
         context = getContext();
    }
     return new DecorView(context, featureId, this, getAttributes());
    }
複製程式碼

根據是否使用DecorContext來建立相應的context,然後利用該Context來建立DecorView。那麼這個DecorView到底是個什麼呢?

public class DecorView extends FrameLayout
複製程式碼

DecorView其實就是一個FrameLayout。這個FrameLayout則是我們所看到的Activity試圖的根View,在建立了一個DecorView之後,又根據這個DecorView例項來建立了ContentView。

這裡建立的DecorView其實是一個FrameLayout,

由上面函式可以看出,mContentParent是和mDecor有關的,下面來看一下ContentParent的建立過程。

protected ViewGroup generateLayout(DecorView decor) {
...	
//根據樣式,選擇相應的資原始檔,進行相應的資源裝載
	mDecor.startChanging();
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	...
        return contentParent;
}
複製程式碼

開始之前呼叫了DecorView的onResourcesLoaded,然後通過findViewById的方式,返回一個VieGroup作為contentParent。這裡的findViewById的實現在基類Window中。

public View findViewById(@IdRes int id) {
   return getDecorView().findViewById(id);
}
複製程式碼

onResourcesLoaded的方法如下。

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ....
    mDecorCaptionView = createDecorCaptionView(inflater);
    final View root = inflater.inflate(layoutResource, null);
    if (mDecorCaptionView != null) {
         if (mDecorCaptionView.getParent() == null) {
             addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         }
         mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
          addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}
複製程式碼

根據相應的UI樣式配置,選擇合適的佈局資原始檔,然後通過inflate裝載相應的資原始檔,建立ContentView,同時將其新增到DecorView中。
在建立了DecorView和ContentParent之後,接下來,則利用了我們傳遞的xml佈局檔案id。

mLayoutInflater.inflate(layoutResID, mContentParent);
複製程式碼

LayoutInflater中inflate的實現如下

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
     return inflate(resource, root, root != null);
}
複製程式碼
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
       
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
複製程式碼
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
   ....
   final View temp = createViewFromTag(root, name, inflaterContext, attrs);
   ....
   if (root != null && attachToRoot) {
      root.addView(temp, params);
   }
   ...
}
複製程式碼

根據我們的佈局檔案ID,建立出一個View,然後將該View新增到我們的contentView之中。在setContentView中,當我們結束了instalDecor方法之後,會呼叫initWindowDecorActionBar來進行ActionBar的初始化操作,建立ActionBar。

private void initWindowDecorActionBar() {
     Window window = getWindow();
     window.getDecorView();
     if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
         return;
     }
    mActionBar = new WindowDecorActionBar(this);
    mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
    mWindow.setDefaultIcon(mActivityInfo.getIconResource());
    mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
複製程式碼

handleResumeActivity

通過performLaunchActivity,我們已經裝載出了資源,同時建立了DecorView和contentParentView,同時也完成了Window的建立,同時也將我們設定的資原始檔,裝載出來成為了View。但是我們知道一個Activity可見時,我們的onResume方法是被呼叫的了,在performLaunchActivity被呼叫之後又呼叫了handleResumeActivity()

final void handleResumeActivity(IBinder token,
            boolean clearHide, ....) {
   ActivityClientRecord r = mActivities.get(token);
	...
   r = performResumeActivity(token, clearHide, 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;
      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);
      }
}
複製程式碼

該方法首先呼叫了performResumeActivity函式。performResumeActivity中進行了大量的狀態相關的判斷,而對於首次啟動的分析,我們所關心的核心就是其呼叫了Activity的performResume,也就是Activity的onResume函式被呼叫了。

public final ActivityClientRecord performResumeActivity(IBinder token,boolean clearHide, String reason) {
    ...
    r.activity.performResume();
    ...
}
複製程式碼

最開始呼叫了Activity的Resume 函式,然後進行了後續的呼叫。這裡看到一個ViewRootImpl中,通過decor呼叫getViewRootImpl()來獲得

public ViewRootImpl getViewRootImpl() {
   if (mAttachInfo != null)  {
      return mAttachInfo.mViewRootImpl;
   }
    return null;
}
複製程式碼

該方法中核心程式碼為

wm.addView(decor, l);
複製程式碼

呼叫了WindowManager的addView方法,來新增當前的DecorView。

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

複製程式碼
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
     ....
    ViewRootImpl root;
    View panelParentView = null;
     ....
     root = new ViewRootImpl(view.getContext(), display);
     view.setLayoutParams(wparams);
      ....
      mViews.add(view);
      mRoots.add(root);
      mParams.add(wparams);
      ....
       // do this last because it fires off messages to start doing things
     root.setView(view, wparams, panelParentView);
}
複製程式碼

在WindowManager中的addView呼叫了WindowManagerGlobal的addView方法,在最開始的時候,我們呼叫過

WindowManagerGlobal.initialize();
複製程式碼

WindowManagerGlobal是個單例類,其保證了對於整個應用層,只具備一個WindowManagerService。同時其具備三個ArryList,分別儲存一個應用的根View,ViewRootImpl和LayoutParams,該方法,建立了一個ViewRootImpl例項,然後將View,ViewRoot,LayoutParams新增到相應的ArrayList中,最後呼叫ViewRoot的setView方法。這裡的setView方法會將其設定為自身的View,以後的繪製等事件都交給ViewRootImpl來實現。繪製涉及到佈局,測量,繪製三個環節,具體的過程此處不再展開,本篇主要目的是為了接下來的外掛化和熱修復做一個基礎。

總結

View建立流程

至此,我們已經知道了從一個Activity的啟動到我們的View逐步被建立的過程,但是這裡並沒有涉及到繪製相關的內容,那麼這個View最終如何繪製出來的呢?接下來,我們首先從ViewRootImpl來切入做分析,逐步理清楚接下來做的事情。

相關文章