Android學習筆記-Activity視窗的建立過程
Window表示一個視窗的概念,android中所有的檢視都是通過Window來呈現的,Activity,Dialog,Toast他們的檢視都是附加到Window上的。這篇部落格討論的是Activity中的檢視如何附加到window上的,其實也就是為什麼activity中設定的佈局我們能夠在手機螢幕上可以看到。
我們都知道當Activty物件建立成功之後會回撥onCreate()方法,而當onResume()方法執行完之後,我們設定的佈局檔案就會在螢幕上呈現,基於上面倆點來分析Activity視窗的建立過程
一.當建立完Activity物件之後所做的事情
Activity的建立都在對應應用程式程序ActivityThread類中完成。Activity類的建立完成之後會呼叫Activity#attach方法,程式碼如下:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
private Window mWindow;
private WindowManager mWindowManager;
........
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) {
attachBaseContext(context);
//----- 1 ------
mWindow = new PhoneWindow(this);
//----- 2 ------
mWindow.setCallback(this);
//----- 3 ------
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//----- 4 ------
mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
//----- 5 ------
mWindowManager = mWindow.getWindowManager();
.........
}
........
}
從上面的程式碼中可以看到:
1.新建了一個PhoneWindow的物件賦給了Activity的欄位mWindow.在該方法中為Activty建立了一個Window物件,讓Activty持有他的基礎上,並讓Activity實現了Window中的CallBack介面。
2.CallBack介面中定義了大量與使用者操作有關的方法,這種方式下,就把使用者的操作從Window中,傳遞給了Activity來處理。其中的一些常用的方法有dispathKeyEvent,dispathTouchEvent()等
3.,還有一個setOnWindowDismissedCallback回撥。可以理解為什麼當我們按下手機back鍵時,Activity銷燬之後,我們的檢視也會不見,其實的銷燬了Window的原因而已。
4.接下來的程式碼就是為window設定了一個WindowManager,在把這個WindowManager設定給Activity中的WindowManager,也就是Activty和Window中持有的是同一個WindowManager。
5.WindowManager是用來幹啥的,他是用來把檢視新增到window裡面的。
總結一下上面的過程:Window其實只是一個抽象的概念,可以理解為容器,裡面的View才是真正顯示在螢幕上的東西。而Activty又持有Window這個物件,這樣當我們啟動Activity時,會建立Window,並將View加入到Window中。
二.onCreate方法中的setContentView方法內部實現
activity的setContentView方法為:
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
getWindow中通過上面attach方法知為PhoneWindow,所以看PhoneWindow中的setContentView的實現為:
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
上面程式碼判斷邏輯為:
如果mContentParent為空,則首先建立DecorView,mContentParent為我們在Activity中設定佈局的父佈局,installDecorView方法為
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
........
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
........
}
}
generateLayout()方法內部會根據Activity不同主題,去解析不同的佈局檔案加入到DecorView中,一般的佈局檔案由倆部分組成,上面為一個ActionBar,下面為一個FrameLayout的佈局,layout的內容就是加入到這裡面的。
通過上面的幾步,已經把layout中的內容設定到DecorView中了,也就是onCreate()到此的任務就結束了,下面繼續向下分析
三.將DecorView加入到Window中
雖然Window有DecorView的欄位,但是檢視還沒有繪製。這些工作將有WindowManager來完成,將View加入到Window中。
Activity建立完成之後,Ams利用Bindler程序間的通訊機制通知客戶端呼叫ActivityThread類中的handleResumeActivity方法來啟動Activity
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
........
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
//客戶端Activity的視窗Window物件
r.window = r.activity.getWindow();
//視窗的頂層檢視DecorView物件
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//客戶端Activity視窗的視窗管理器物件WindowManager
ViewManager wm = a.getWindowManager();
//視窗的引數
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//視窗的型別為基礎應用視窗
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
//視窗的軟輸入法模式
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
//標記客戶端Activity新增視窗成功
a.mWindowAdded = true;
//新增視窗
wm.addView(decor, l);
}
........
}
}
分析:最後一行有wm.addView(decor,l); 到這裡DecorView已經被加入到Window裡面了,該方法內部會將DecorView中的檢視繪製出來。
WindowManager為一個介面,該介面繼承自ViewManager,WindowManager的實現類為WindowManagerImpl,
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManagerImpl內部的addView方法為
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
可以看到,真正起作用的還是mGlobal,該物件為WindowManagerGlobal的例項。
WindowManagerGlobal物件的獲取為單例模式,而WindowManagerImpl物件,對於每個應用程式來說也只有一個。也就是說一個應用程式的WindowManager的例項是唯一的。
WindowManagerGlobal的addView方法為:
//視窗的新增過程
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
final Context context = view.getContext();
if (context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
}
//不能重複新增視窗
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
//判斷當前視窗是否為子視窗,如果是則獲得其父視窗並儲存在panelParentView變數中
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);
}
}
}
//每一個視窗對應著一個ViewRootImpl物件
root = new ViewRootImpl(view.getContext(), display);
//給當前視窗檢視設定引數
view.setLayoutParams(wparams);
//儲存三個陣列
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
//真正執行視窗的檢視View繪製工作的方法
root.setView(view, wparams, panelParentView);
}
}
在該方法內:
root = new ViewRootImpl(view.getContext(), display);
//給當前視窗檢視設定引數
view.setLayoutParams(wparams);
//儲存三個陣列
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//真正執行視窗的檢視View繪製工作的方法
root.setView(view, wparams, panelParentView);
}
新建了ViewRootImpl物件,並將view加入到list中。然後呼叫root.setView方法。
該方法為真正的繪製圖形的方法。那麼接下來要做的就是利用ViewRootImpl對DecorView的繪製了,到此方法結束為止,onResume()結束了,檢視會顯示在螢幕上。