所謂的窗口(Window)就是一個顯示在手機屏幕上可視化視圖的一片區域。在Android中窗口是一個抽象的概念,每一個Activity就對應着一個窗口,而所有的窗口都是由視圖(View)來呈現,而我們知道View構成的一個樹形結構的視圖就組成了一個Activity的界面了。在android系統中窗口分為三個類型:
- 應用窗口:所謂應用窗口指的就是該窗口對應一個Activity,因此,要創建應用窗口就必須在Activity中完成了。
- 子窗口:所謂子窗口指的是必須依附在某個父窗口之上。
- 系統窗口:所謂系統窗口指的是由系統進程創建,不依賴於任何應用或者不依附在任何父窗口之上。
android是如何創建應用窗口的呢,下面來逐步分析:
startActivty的啟動過程這里就不分析了,具體可以參考http://gityuan.com/2016/03/12/start-activity/,
這里從創建Activity來分析。
1.每個應用窗口都對應一個Activity對象,因此,要創建一個應用窗口,都必須創建一個activity對象。當ActivityManagerService(下面簡稱AMS)准備啟動一個Activty時,會去通知app進程,每個app進程都會對應一個ActivtyThread類,任何Activty都隸屬於App進程的,於是,啟動Activty的任務就交給了ActivtyThread,
啟動一個Activty,首先得創建一個Activty的對象,這個工作是在ActivtyThread.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);
}
}
...
return activity;
}
刪除了一些我不是很關注的代碼。可以看出android是通過ClassLoader來加載所要啟動的Activty類的,這就是為什么我們要在manifest文件中配置Activty的原因(ClassLoader加載class時,必須要知道class文件的名字)。下面是newActivity的實現,通過classname去程序文件中加載對應的activty類。
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
2.得到activty對象后,接着調研activity的attach方法,這個方法還是在ActivtyThread.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);
...
return activity;
}
attach的作用是為創建好的activity設置內部變量,這些變量是以后對activity調度所必需的。
3.在attach內部除了設置變量外,還有另一個任務,就是創建一個PhoneWindow對象,
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*/);
//創建PhoneWindow
mWindow = new PhoneWindow(this, window);
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);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
創建PhoneWindow后調運 mWindow.setCallback(this),將window的callback設置為當前activty,這就是為什么用戶操作能傳給activty。
4.創建完Window后,就是給Window對象中的mWindowManager賦值。mWindowManager是Window類中一個類型為WindowManager的對象,WindowManager是一個接口,它的實現類有WindowManagerImpl。通過(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)來設置這個對象。
5.配置好activty和window后,就開始給窗口添加view了,還是在ActivtyThread.performLaunchActivity中,在以上步驟完成之后,調運callActivityOnCreate方法,
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
//Instrumentation.java
public void callActivityOnCreate(Activity activity, Bundle icicle,
PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
//Activty.java
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
可以看出這里就會調用Activty的onCreate,這個我們應該很熟悉了。后面會接着調:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
setContenView實際上會調運
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
其實質上就是調運PhoneWindow的setContentView。
6.接着分析PhoneWindow的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;
}
首先執行的是installDecor(),installDecor()為Window創建一個窗口修飾,所謂的窗口修飾就是界面上常見的標題欄,代碼指點的layout.xml將被包含在窗口修飾中,稱之為窗口內容。窗口修飾及其內部的窗口內容就是我們通常說的窗口。如圖:
創建完后,就是把layout.xml設置個mContentParent,即id=content的FrameLayout。最后,調運cb.onContentChanged()來通知Activty窗口的內容發生了變化。cb是在之前提到的給Window設置的callback。
7.接下來ActivityThread調用了handleResumeActivity(),首先調用performResumeActivity(),輾轉調用了Activity的onResume(),接着會調用Activity的makeVisible()。該方法及后續的各種調用將完成真正的把窗口添加進WmS之中。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
8.之前說到WindowManager只是一個接口類,真正的實現是WindowManagerImpl,因此真正的addView操作就在此類中。然而它又交給了WindowManagerGlobal處理。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Overridepublic
void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
9.WindowManagerGlobal的addView過程,一個應用程序內部無論有多少個Activity, 但只有一個WindowManagerGlobal對象在 WindowManagerGlobal類中維護三個集合,用於保存該應用程序中所擁有的窗口的狀態。而后調用ViewRootImpl.setView(),完成最后的、真正意義上的添加工作。
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>();
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) {
...
throw e;
}
}
10.ViewRootImpl.setView方法篇幅相對較長,其主要執行的操作有
- 給 ViewRoot的重要變量賦值;
- 調用requestLayout(),發出界面重繪請求;進而執行scheduleTraversals(),進行View的繪制工作。
- 調用sWindowSession.addToDisplay(),通知WmS顯示View。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
mWindowSession的類型是IWindowSession,它是一個Binder對象,真正的實現類是Session,也就是說Window的添加過程是一次IPC調用。添加請求交給WmS去處理了。
到此為止,從客戶端的角度來講,已經完成了窗口創建的全部工作。從而界面才會呈現到用戶面前。可見在onCreate的時候View並沒有做測量、布局、繪制操作,因此無法獲取到寬高數據。
ViewRootImpl
- 鏈接WindowManager和DecorView的紐帶,更廣一點可以說是Window和View之間的紐帶。
- 完成View的繪制過程,包括measure、layout、draw過程。
- 向DecorView分發收到的用戶發起的event事件,如按鍵,觸屏等事件