Android Window,View,Activity之間關係
在Android一個View是怎麼展示在螢幕上?過程是什麼樣的呢?現提出問題再帶著問題去檢視原始碼。
Android所有展示的頁面所有的View都被裝載在Window中,Window是一個抽象類,它的唯一實現類是PhoneWindow,而Activity中有一個window的物件這點從Activity的原始碼就可以看到,如下:
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); } 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);//註釋:2 if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }
如果對attach()方法不是很瞭解可以查詢下Android Activity的啟動流程 。
註釋1:new 了一個PhoneWindow的物件,這樣activity就擁有了window物件,
但是這樣遠遠不夠將View展示出來,因為這只是有了一個視窗真正的繪製工作並沒有開始。
再看註釋2:Activity中window呼叫了setWindowManager方法,WindowManager又是幹什麼用的呢?這裡引用皇叔的部落格中的一段話來下個定義:
WindowManager對Window進行管理,說到管理那就離不開對Window的新增、更新和刪除的操作,在這裡我們把它們統稱為Window的操作。對於Window的操作,最終都是交由WMS來進行處理。
如果大家熟悉AMS的話對這種方式應該不會陌生,它們其實相似。接著檢視setWindowManager,他是Window的一個方法,
/** * Set the window manager for use by this Window to, for example, * display panels.This is <em>not</em> used for displaying the * Window itself -- that must be done by the client. * * @param wm The window manager for adding new windows. */ 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); }//註釋3 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }
看註釋3的位置,先判斷傳入的WM是否為空,如果為空則再次執行(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)
下面看getSystemService方法,這個方法是通過Binder機制來獲取WMS,再看getSystemService返回的物件是什麼?具體的實現是在ContextImpl中,
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
SystemServiceRegistry又是個什麼東東呢?看一下:
static{ ... registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher<WindowManager>() { @Override public WindowManager createService(ContextImpl ctx) { return new WindowManagerImpl(ctx); }}); ... }
SystemServiceRegistry 的靜態程式碼塊中會呼叫多個registerService方法,這裡只列舉了和本文有關的一個。registerService方法會將傳入的服務的名稱存入到SYSTEM_SERVICE_NAMES中。從上面程式碼可以看出,傳入的Context.WINDOW_SERVICE對應的就是WindowManagerImpl例項,因此得出結論,Context的getSystemService方法得到的是WindowManagerImpl例項。
在返回到註釋3的位置的下一行程式碼呼叫了WindowManagerImpl物件的createLocalWindowManager方法,這個方法又是幹嘛的呢?
public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow); }
這個方法很簡單就是就是又一次建立了WindowManagerImpl物件,和通過之前那次不同的是這次傳入了PhoneWindow物件,這時WindowManager開始真正和WIndow關聯起來。
哪View是被怎麼Add到Window上的呢?這裡以最典型的應用程式視窗Activity為例,Activity在啟動過程中,如果Activity所在的程序不存在則會建立新的程序,建立新的程序之後就會執行代表主執行緒的例項ActivityThread,不瞭解的請檢視Android應用程式程序啟動過程(前篇) 這篇文章。ActivityThread管理著當前應用程式程序的執行緒,這在Activity的啟動過程中運用的很明顯,不瞭解的請檢視Android深入四大元件(一)應用程式啟動過程(後篇) 這篇文章。當介面要與使用者進行互動時,會呼叫ActivityThread的handleResumeActivity方法,如下所示。
... 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; // 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; wm.addView(decor, l);//註釋4 } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v( TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } ...
注意註釋4:呼叫了在註釋3位置返回的WindowManagerImpl物件的addview方法,這個方法是ViewManager中的方法,ViewManager是一個介面,WindowManager是個介面同時又實現了ViewManager介面,而WindowManagerImpl又實現了WindowManager,這是隻捋清楚這個方法的層級,清楚後放到一邊繼續檢視WindowManagerImpl中的addview方法
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);//註釋5 }
看註釋5:又呼叫了mGlobal的addview方法,如下
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... //熟悉的LayoutParams出來了 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { //根據父window調整當前window的尺寸 parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { // If there's no parent, then hardware acceleration for this view is // set from the application's hardware acceleration setting. final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // Start watching for system property changes. //檢視系統屬性的更改 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(); } } } }; SystemProperties.addChangeCallback(mSystemPropertyUpdater); } int index = findViewLocked(view, false); if (index >= 0) { if (mDyingViews.contains(view)) { // Don't wait for MSG_DIE to make it's way through root's queue. mRoots.get(index).doDie(); } else { throw new IllegalStateException("View " + view + " has already been added to the window manager."); } // The previous removeView() had not completed executing. Now it has. } // If this is a panel window, then find the window it is being // attached to for future reference. 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); } } } //新增View的所有準備工作就緒開始新增 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);//註釋6 } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }
註釋6:呼叫了ViewRootImpl中的setView方法:
... 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(); } } ...
主要就是呼叫了mWindowSession的addToDisplay方法。mWindowSession是IWindowSession型別的,它是一個Binder物件,用於進行程序間通訊,IWindowSession是Client端的代理,它的Server端的實現為Session,此前包含ViewRootImpl在內的程式碼邏輯都是執行在本地程序的,而Session的addToDisplay方法則執行在WMS所在的程序。
@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outOutsets, outInputChannel); }
addToDisplay方法中會呼叫了WMS的addWindow方法,並將自身也就是Session,作為引數傳了進去,每個應用程式程序都會對應一個Session,WMS會用ArrayList來儲存這些Session。這樣剩下的工作就交給WMS來處理,在WMS中會為這個新增的視窗分配Surface,並確定視窗顯示次序,可見負責顯示介面的是畫布Surface,而不是視窗本身。WMS會將它所管理的Surface交由SurfaceFlinger處理,SurfaceFlinger會將這些Surface混合並繪製到螢幕上。
Activity持有Window的物件,而Window通過WindowManager管理Window,而WindowManager又是通過Binder機制呼叫了WMS的介面,WMS才能把View真正顯示到螢幕上。