在很早之前,樓主簡單的學習過Activity
的結構,可是當時介於各類緣由,只是淺嘗輒止,並無進行深刻的學習。同時,我發現本身今年在畢業以後有點頹廢,再也不有去年那股學習勁兒。通過屢次的自省,發現本身是由於找不到學習的方向而頹廢的。bash
通過深入反思自我以後,爲了改變如今的情況,也爲了彌補彌補當初學習Activity
的遺憾,還爲了提高自身的技術水平,我決定從Activity
開始入手,學習並記錄Android framework的知識。app
本文打算介紹並分析Activity
的結構設計,包含:Activity
、Window
和View
相關知識。本文參考資料:異步
大夥兒應該都知道,Activity的結構分爲三層,分別是:Activity
、Window
和View
,不一樣層承擔着不一樣的責任。 ide
Activity
整個結構的構建流程。這裏我再簡單的介紹一下這幾個部分的做用。
- ActivityThread:每一個流程調用起始地點,至於
ActivityThread
的內容不是本文的重點,因此本文不會過多介紹,後續有相關的文章來介紹。- Activity:至關因而一個管理者,負責建立
WindowManager
和Window
,同時初始化View
。- Window:承載着
View
,同時代Activity
處理一切View
的事務。- WindowManager:從字面意思來理解是
Window
的管理,實際上是管理Window
上的View
,包括addView
和remove
。
而這幾個角色的對應關係是:ActivityThread
全局惟一,整個App進程中只一個實例。ActivityThread
能夠對應多個Activity
,一個Activity
對應一個Window
(這裏不考慮Dialog
之類的),一個Window
對應一個WindowManager
。oop
本文打算參考上面的流程圖,分別分析Activity的attach過程、Activity的onCreateView過程和WindowManager的addView過程。佈局
Activity的attach過程就是一個初始化的過程,分別對Window
進行初始化、WindowManager
進行初始化。咱們先來看看ActivityThread
的handleLaunchActivity
方法:post
public Activity handleLaunchActivity(ActivityClientRecord r,
// ······
final Activity a = performLaunchActivity(r, customIntent);
// ······
}
複製代碼
handleLaunchActivity
方法主要調用performLaunchActivity
來啓動一個Activity
,再來看看performLaunchActivity
方法:學習
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ·······
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
的attach
方法的調用,咱們來看看attach
方法裏面作了哪些事情。ui
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) {
// ······
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// ······
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
// ······
}
複製代碼
attach
方法一共作了兩件事情:this
- 初始化
Window
,其中Window的構造方法還作了一些事,好比說,咱們比較關心的LayoutInflater
的初始化。- 初始化
WindowManager
,而且set到Window
裏面去。
當初,咱們剛入門Android的時候就知道,在Activity
的onCreate
方法調用setContentView
能夠給當前頁面設置一個佈局。當時有沒有以爲這個很是的神奇,而且對其充滿了好奇,惋惜的是當時本身對整個Android的設計還很陌生,不敢去探究。今天咱們能夠正式進入setContentView的內部了,去一探究竟🤪。
其實Activity
的onCreate
過程不只作setContentView
操做,其實還作了其餘的事情。從源碼中咱們也能夠看到,好比說,初始化AppCompatDelegate
(這裏以AppCompatActivity
爲例),設置Theme等。不過這些不是本文的重點,後面我會專門的文章來分析這些過程,本文的重點是setContentView
方法。
Activity
的setContentView
沒有作啥事,實際作事的是它的Window。咱們來看看Window
的setContentView
方法:
@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);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
複製代碼
setContentView
方法裏面主要是作了以下幾件事:
- 經過
installDecor
方法建立DecorView
和mContentParent
。DecorView
的建立在generateDecor
方法;mContentParent
的建立在generateLayout
方法裏面,generateLayout
方法裏面有一個重要地方根據屬性的配置設置了Window
的flags
和features
。這裏flags
的生效是在DecorView
的updateColorViews
方法,會根據flags
來計算DecorView
的大小;features
將那種佈局加載到DecorView
,這將影響到Activity
默認的佈局樣式。不過我有一點疑惑至今無解,從mContentParent
的註釋瞭解到,mContentParent
也有多是DecorView
,可是我看了installDecor
以及內部調用的generateLayout
方法,都沒有找到相關可能性。- 將
ContentView
加載到mContentParent
上去。
針對上面設置flag生效的解釋我還想補充一下,大夥兒應該都知道,在Android中,咱們能夠經過setFlags
或者addFlags
來改變一些屬性,可是有沒有想過是怎麼生效的呢?其實過程是很是的簡單,咱們就不一一的追蹤源碼了,直接來看一下調用流程:
Window#setFlags
-> PhoneWindow#dispatchWindowAttributesChanged
-> DecorView#updateColorViews
。
最終,會根據設置好的Flag來計算DecorView
的位置和大小,例如說,咱們設置了FLAG_FULLSCREEN
讓界面全屏,最終在updateColorViews
方法這麼來計算DecorView
的高度:
// If we didn't request fullscreen layout, but we still got it because of the // mForceWindowDrawsStatusBarBackground flag, also consume top inset. boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 && mForceWindowDrawsStatusBarBackground && mLastTopInset != 0; 複製代碼
上面的代碼表達的意思很是簡單,就是判斷是否消費狀態欄的高度。至於其餘Flag也是如此,有興趣的同窗能夠看一看。
咱們知道,DecrorView
的ViewParent
是ViewRootImpl
,而View
最重要的三大流程就是由ViewRootImpl
觸發的。在正式介紹這一塊的知識以前,咱們先來看一個簡單的結構圖:
DecorView
部分建立完成,如今還須要兩件事須要作:
- 初始化
ViewRootImpl
,而且將ViewRootImpl
與Window
綁定。- 將以前建立好的
DecorView
添加到ViewRootImpl
裏面去。
咱們先來看看ActivityThread
的handleResumeActivity
方法:
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// ······
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, 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);
}
// ·······
}
}
// ······
}
複製代碼
handleResumeActivity
方法裏面主要作了兩件事:
- 調用
performResumeActivity
進而回調Activity
的onResume
方法。- 調用
WindowManager
的addView
方法,將DecorView
添加到ViewRootImpl
中去。
調用的addView
方法最終是進入WindowManager
的實現類WindowManagerImpl
中去,而在WindowManagerImpl
的addView
方法中調用了WindowManagerGlobal
的addView
方法。 從名字中,咱們就能夠看出來,WindowManagerGlobal
是屬於全局的。咱們再來看看WindowManagerGlobal
的addView
方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// ······
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// ·······
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
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
複製代碼
addView
方法主要是作了兩件事:
- 初始化
ViewRootImpl
,而且將該Window
相關信息保存起來,包括:ViewRootImpl
、DecorView
和LayoutParams
。- 調用
ViewRootImpl
的setView
方法,將DecorView
添加到ViewRooImpl
,而且觸發View
的三大流程。
咱們都知道,每一個Window
都對應着一個DecorView
,而從這裏咱們能夠發現,每一個DecorView
都對應着一個ViewRootImpl
,從而得知,若是是一個Dialog或者其餘新Window
的界面,一定有一個新的ViewRootImpl
來觸發View
的三大流程,而不是由宿主Window
的ViwRootImpl
觸發的。
在回答這個問題以前,咱們應該須要注意的是,這裏是Handler的post方法,而不是View的post方法。ps:View的post方法能拿到View的寬高。
咱們知道Activity
的onResume
方法的執行是在ViewRootImpl
觸發測量過程以前,同時ViewRootImpl
是經過以下的方式來觸發測量過程的:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
複製代碼
咱們發現這裏經過Handler post了一個異步消息來進行測量。但是,儘管post的是異步消息,在onResume
方法post的消息也先於它執行,由於它在其後post的。因此,在Activity的onResume方法調用Handler的post不能獲取View的寬高。
到此位置,對Activity
的結構分析也差很少,在這裏,咱們作一個簡單的總結。
- Activity的結構分爲三層,分別是:
Activity
、Window
和View
。- 結構的建立過程分爲三步:1. 建立Activity,而且建立與其相關的
WindowManager
和Window
,對應着是Activity
的attach
方法的調用;2. 初始化DecorView
和ContentView
,對應着的是Activity
的onCreate
方法;3. 建立ViewRootImpl
,而且將DecorView
添加到ViewRootImpl
中,同時觸發View
樹的三大流程。- 在
generateLayout
方法裏面,會根據設置不一樣的flags
來計算DecorView
的大小和位置,計算邏輯在updateColorViews
方法裏面;仍是根據設置不一樣的features
方法來選擇默認加載到DecorView
中,好比說設置了NO_ACTION_BAR
的features
,就會加載不帶ActionBar
的佈局。