Activity、View、Window之間的關系可以用以下的簡要UML關系圖表示,在這里貼出來,比較能夠幫組后面流程分析部分的閱讀。
一、Activity的啟動流程
在startActivity()后,經過一些邏輯流程會通知到ActivityManagerService(后面以AMS簡稱),AMS接收到啟動acitivty的請求后,會通過跨進程通信調用AcitivtyThread.handleLauncherActivity()方法,我們從這里開始分析,首先來看handleLauncherActivity()方法。
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...// Initialize before creating the activity WindowManagerGlobal.initialize(); Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
//該方法會調用到Activity的onResume()方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
...
}
... }
這里重點關注三個方法(加粗的地方),首先來看WindowManagerGlobal.initialize(),WindowManagerGlobal是單例模式的,一個進程內只有一個,這里調用該類的初始化方法,后續我們再對該類的作用和相關方法進行分析;第三個是在創建好Activity后調用Acitivty的onResume()方法。這里我們來看需關注的第二個方法performLaunchActivity(),代碼如下。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
//通過反射方式創建Activity Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.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) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { ...
if (activity != null) { Context appContext = createBaseContextForActivity(r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (r.overrideConfig != null) { config.updateFrom(r.overrideConfig); } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); 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);
...
//調用acitivity的onCreate()方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
return activity; }
這個方法主要是讀取Acitivity這里利用反射創建出ActivityClientRecord所要求的Activity對象,然后調用了acitivity.attach()方法。注意attach()傳入的參數有很多,在performLacunchActivity()方法流程中,調用attach()方前,我們省略掉的步驟基本都在為這些參數做准備,attach()方法的作用其實就是將這些參數配置到新創建的Activity對象中;而在attach之后則會回調到acitivity的onCreate()方法。我們進入Activity.java類詳細來看下attach方法。
此外,在attach之前會初始化一個Window對象,Window.java是一個抽象類,代表了一個矩形不可見的容器,主要負責加載顯示界面,每個Activity都會對應了一個Window對象。如果ActivityClientRecord.mPendingRevomeWindow變量中已經保存了一個Window對象,則會在后面的attach方法中被使用,具體使用的場景會在后面中介紹。
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) { attachBaseContext(context); mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window);//(1) mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); }
... //初始化Acitity相關屬性 mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//(2) if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }
重點關注初始化window對象的操作,首先創建了PhoneWindow對象為activity的mWindow變量,在創建時傳入了上一個activity對應的window對象,之后又將這個acitivity設置為window對象的回調。Activity中很多操作view相關的方法,例如setContentView()、findViewById()、getLayoutInflater()等,實際上都是直接調用到PhoneWindow里面的相關方法。創建完acitivty對應的PhoneWindow之后便會調用setWindowManager()方法。首先來看PhonewWindow構造方法。
public PhoneWindow(Context context, Window preservedWindow) { this(context); // Only main activity windows use decor context, all the other windows depend on whatever // context that was given to them. mUseDecorContext = true;if (preservedWindow != null) { //快速重啟activity機制 mDecor = (DecorView) preservedWindow.getDecorView(); mElevation = preservedWindow.getElevation(); mLoadElevation = false; mForceDecorInstall = true; // If we're preserving window, carry over the app token from the preserved // window, as we'll be skipping the addView in handleResumeActivity(), and // the token will not be updated as for a new window. getAttributes().token = preservedWindow.getAttributes().token; } // Even though the device doesn't support picture-in-picture mode, // an user can force using it through developer options. boolean forceResizable = Settings.Global.getInt(context.getContentResolver(), DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0; mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_PICTURE_IN_PICTURE); }
首先要關注的就是preserviedWindow參數,這個參數就是上一段中提到的mPendingRevomeWindow變量,這個參數在什么時候會不為空呢?其實這里的邏輯是用來快速重啟acitivity的,比如你的一個activity已經啟動了,但是主題換了或者configuration變了,這里只需要重新加載資源和view,沒必重新再執行DecorView的創建工作。
另一個要關注的就是mDecor變量,這個變量是DecorView類型的,如果這里沒有初始化的話,則會在調用setContentView方法中new一個DecorView對象出來。DecorView對象繼承自FrameLayout,所以他本質上還是一個view,只是對FrameLayout做了一定的包裝,例如添加了一些與window需要調用的方法setWindowBackground()、setWindowFrame()等。我們知道,acitivty界面的view是呈樹狀結構的,而mDecor變量在這里作為activity的界面的根view存在。這三個點關系就比如,PhoneWindow是一塊手機電子屏,DecorView就是電子屏要顯示的內容,Activity就是手機電子屏安裝位置。
再來看創建PhonewWindow之后調用的setWindowManager()方法的邏輯,這段代碼是在PhonewWindow.java的父類Window.java中代碼如下。
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }
對於wWindowManager變量,實際上這里是創建了一個WindowManagerImpl對象。首先是這種首先獲取系統服務的代理到wm上,然后強制轉換為WindowManagerImpl調用createLocalWindowManager(),在createLocalWindowManager()實際是執行了一個new WindowManagerImpl()到方法來創建。關於這部分代碼看了很久很困惑的一個點,就是為啥要弄個這么復雜的邏輯,直接把上面加粗的代碼改為new WindowManagerImpl(...),這養會有什么區別嗎?如果有大蝦看到這里,希望能幫我解答。
在WindowManager中保存了對於單例對象WindowManagerGloble的引用,即mGlobal變量。此外,WindowManager.java實現了WindowManager又,而WindowManager繼承自ViewManager接口,ViewManager接口方法如下方代碼。
public interface ViewManager { /** * Assign the passed LayoutParams to the passed View and add the view to the window. * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
在WindowManager對於addView()、updateViewLayout()和removeView()的實現,都是調用到mGlobal變量對應的addView()、updateViewLayout()和removeView()方法來實現的。這里我們
這樣我們分析完activity以及對應的window對象的創建,回到performLauncerActivity()方法中Activity a = performLaunchActivity(r, customIntent)這一步驟,之后便回調activity方法的onCreate(),在onCreate()的setContentView方法會初始化DecorView,並根據傳入參數加載布局,詳細步驟在下一節介紹。
再回到最初的handlerLaunchActivity()方法中,通過調用performLauncerActivity()創建出一個Acitivty對象后,如果創建成功則執行handleResumeActivity(),便執行到了Acitivity的onResume()方法,即是完成了acitivty的啟動。
二、setContentView()流程
首先,我們一般在onCreate()里調用setContentView()的方法。
@override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }
這里實際調用到到地方是Acitivity.java類中的setContentView()方法,如下。
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
這里getWindow()返回的是Acitivity.java類中的mWindow變量,就是Activity創建時一起創建的PhoneWindow對象,進入到
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews();//如果多次調用setContentView則會執行removeAllView操作 } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { //過渡動畫機制相關 final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }
代碼里涉及到FEATURE_CONTENT_TRANSITIONS的屬性,這里是Android的過渡動畫相關機制,這里我們不再展開詳述。一般的Acitivty啟動時,會進入mContentParent為null的邏輯,首先調用的是installDecor()方法,完成DecorView的創建工作;之后調用mLayoutInflater.inflate()方法將我們傳入的資源文件轉換為view樹,裝載到mContentParent中。首先來看installDecor()代碼。
private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { //創建DecorView 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()方法創建了DecorView對象,generateDecor()方法比較簡單,主要就是執行new DecorView(context, featureId, this, getAttributes())方法,這里不再貼出代碼;重點來看generateLayout()方法,這個方法生成的mContentParent是作為來我們后續加載加載的用戶的布局的父布局存在的。
protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. //獲取當前主題的相關屬性 TypedArray a = getWindowStyle(); ... //一大段的根據獲取到到主題屬性,解析保存到PhonwWindow的相關參數的變量中 int layoutResource; int features = getLocalFeatures();
... //一大段根據PhoneWindow的設定好的屬性(features和mIsFloating)的判斷,為layoutResource進行賦值,
//值可以為R.layout.screen_custom_title、R.layout.screen_action_bar等 mDecor.startChanging();
//將layoutRsourece值對應的布局文件加載到DecorView中 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//在加載給DecorView的布局文件中有一塊id為ID_ANDROID_CONTENT的區域是用於用戶顯示自己布局的,也是setContextView傳入的布局顯示的地方
//這塊區域會以ViewGroup的形式賦值給mContentParent變量,這個ViewGroup即是用戶布局的父布局節點 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
... //繼續一大段的屬性配置 mDecor.finishChanging(); return contentParent; }
generateLayout方法實際就是解析出主題的相關屬性,根據不同的主題樣式的屬性值選擇不同的布局文件設置到DecorView中(DecorView本事就是FrameLayout)。在view的樹狀結構下,DecorView即是整個Window顯示的視圖的根節點,在DecorView的子節點中又有一塊id為ID_ANDROID_CONTENT的區域有一塊區域作為mContentParent變量用於加載用戶的布局,與mContentParent平級的視圖有ActionBar視圖和Title的視圖。總結來說,installDecor()方法實質就是產生mDecor和mContentParent對象。在installDecor之后,會執行到mLayoutInflater.inflate(layoutResID, mContentParent)方法將用戶傳入的布局轉化為view再加入到mContentParent上。這樣就完成了setContentView()流程。
三、handleResumeActivity()流程
在文章開頭貼出的第一段AcitityThread.handleLauncherActivity()方法的代碼中,執行完performLaunchAcitity()創建好Acitivity后,便會執行到handleResumeActivity()方法,該方法代碼如下。
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ...// TODO Push resumeArgs into the activity for consideration // 該方法執行過程中會調用到Acitity的onResume()方法,返回的ActivityClientRecord對象描述的即是創建好的Activity
r = performResumeActivity(token, clearHide, reason); if (r != null) { final Activity a = r.activity;//返回之前創建的Acitivty if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window.
// 判斷該Acitivity是否可見,mStartedAcitity記錄的是一個Activity是否還處於啟動狀態
// 如果還處於啟動狀態則mStartedAcitity為true,表示該activity還未啟動好,則該Activity還不可見
boolean willBeVisible = !a.mStartedActivity;
// 如果啟動的組建不是全屏的,mStartedActivity也會是true,此時依然需要willBeVisible為true以下的if邏輯就是針對這種情況的校正 if (!willBeVisible) { try { willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } 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;
//PreserverWindow,一般指主題換了或者configuration變了情況下的Acitity快速重啟機制 if (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; // Normally the ViewRoot sets up callbacks with the Activity // in addView->ViewRootImpl#setView. If we are instead reusing // the decor view we have to notify the view root that the // callbacks may have changed. ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient && !a.mWindowAdded) { a.mWindowAdded = true;
//調用了WindowManagerImpl的addView方法 wm.addView(decor, l); } ... }
重點來看wm.addView()方法,該方法中的decor參數為Acitity對應的Window中的視圖DecorView,wm為在創建PhoneWindow是創建的WindowManagerImpl對象,該對象的addView方法實際調用到到是單例對象WindowManagerGlobal的addView方法(前文有提到)。在看addView代碼前,我先來看看WindowManagerGlobal對象成員變量。
private static WindowManagerGlobal sDefaultWindowManager; private static IWindowManager sWindowManagerService; private static IWindowSession sWindowSession; private final Object mLock = new Object(); private final ArrayList<View> mViews = new ArrayList<View>(); private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>(); private final ArraySet<View> mDyingViews = new ArraySet<View>();
三個成員變量mViews、mRoots和mParams分別是類型為View、ViewRootImpl和WindowManager.LayoutParams的數組。這里有這樣的邏輯關系,每個View都對應着唯一的一個ViewRootImpl和WindowManager.LayoutRarams,即是1:1:1的關系。這三個數組長度始終保持一致,並且在同一個位置上存放的是互相關聯的View、ViewRootImpl和WindowManager.LayoutParams對象。此外還有一個成員變量mDyView,保存的則是已經不需要但還未被系統會收到View。
View與LayoutParams比較好理解,那ViewRootImpl對象的作用是什么呢?首先WindowManagerImpl是作為管理類,就像主管一樣,根據Acitity和Window的調用請求,找到合適的做事的人;DecorView本身是FrameworkLayout,本事是一個View,所表示的是一種靜態的結構;所以這里就需要一個真正做事的人,那就是ViewRootImpl類的工作。總結來講ViewRootImpl的功能如下
1. 完成了繪制過程。在ViewRootImpl類中,實現了perfromMeasure()、performDraw()、performLayout()等繪制相關的方法。
2. 與系統服務進行交互,例如與AcitityManagerSerivice,DisplayService、AudioService等進行通信,保證了Acitity相關功能等正常運轉。
3. 觸屏事件等分發邏輯的實現
接下來我們進入WindowManagerGlobal.addView()方法的代碼。
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; View panelParentView = null; synchronized (mLock) {
...
// If this is a panel window, then find the window it is being // attached to for future reference.
// 如果當前添加的是一個子視圖,則還需要找他他的父視圖
//這里我們分析的是添加DecorView的邏輯,沒有父視圖,故不會走到這里,panelParentView為null
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews.size(); for (int i = 0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } }
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams);
//保存互相對應的View、ViewRootImpl、WindowManager.LayoutParams到數組中 mViews.add(view); mRoots.add(root); mParams.add(wparams); // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } } }
關注代碼中加粗的兩個方法,首先會創建一個ViewRootImpl對象,然后調用ViewRootImpl.setView方法,其中panelParentView在addView參數為DecorView是為null。進入ViewRootImpl.setView()代碼。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) {
//初始化成員變量mView、mWindowAttraibutes
//mAttachInfo是View類的一個內部類AttachInfo類的對象
//該類的主要作用就是儲存一組當View attach給它的父Window的時候Activity各種屬性的信息
mView = view; mAttachInfo.mDisplayState = mDisplay.getState(); mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); mViewLayoutDirectionInitial = mView.getRawLayoutDirection(); mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs); ... //繼續初始化一些變量,包含針對panelParentView不為null時的父窗口的一些處理
mAdded = true; // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system.
// 這里調用異步刷新請求,最終會調用performTraversals方法來完成View的繪制
requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } mForceDecorViewVisibility = (mWindowAttributes.privateFlags & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0; try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } ... } } }
相關變量初始化完成后,便會將mAdded設置為true,表示ViewRootImpl與setView傳入的View參數已經做好了關聯。之后便會調用requestLayout()方法來請求一次異步刷新,該方法后來又會調用到performTraversals()方法來完成view到繪制工作。注意到這里雖然完成了繪制的工作,但是我們創建Activity的源頭是AMS中發起的,我們從一開始創建Acitivity到相對應的Window、DecorView這一大套對象時,還並未與AMS進程進行反饋。所以之后便會調用mWindowSession.addToDisplay()方法會執行IPC的跨進程通信,最終調用到AMS中的addWindow方法來在系統進程中執行相關加載Window的操作。