浙江建設(shè)職業(yè)繼續(xù)教育學(xué)院網(wǎng)站如何搭建網(wǎng)站平臺
0. 相關(guān)分享
Android從屏幕刷新到View的繪制(一)之 Window、WindowManager和WindowManagerService之間的關(guān)系
Android從屏幕刷新到View的繪制(二)之Choreographer、Vsync與屏幕刷新
1. 相關(guān)類
WindowManagerService,下文簡稱WMS
這是一個系統(tǒng)服務(wù),由SystemServer啟動,運行在一個Binder線程,管理著Android系統(tǒng)中所有的Window。
這有什么實際作用呢?除了刷新View,它還可以為其他服務(wù)提供Window管理的支持,例如當觸摸屏幕時產(chǎn)生輸入事件,InputManangerService可以通過WMS來拿到所有Window信息,找到合適的Window進行輸入事件的派發(fā),此后,Window就會把這個輸入事件傳遞給頂級View,也就是DecorView,接著就進入到熟悉的事件分發(fā)機制了。
Window
表示一個窗口的抽象的概念,這是一個空實現(xiàn)的抽象類,在APP進程中,它有實現(xiàn)類PhoneWindow。
PhoneWindow
是Window的實現(xiàn)類,一個Activity對應(yīng)著一個PhoneWindow,在PhoneWindow中有一個頂級View——DecorView
DecorView
Activity的根View,繼承自FrameLayout
ViewRootImpl
負責(zé)DecorView下所有View的調(diào)度,例如invalidate()等,Activity下的所有View都會向上找到DecorView,最終找到ViewRootImpl來處理
WindowManagerImpl
它是WindowManager接口的實現(xiàn)類,WindowManager接口又繼承自ViewManager,顧名思義它是管理View和Window關(guān)系的。ViewManager中規(guī)范了三個方法:addView(), updateViewLayout(), removeView()。這三個任務(wù)最后也交到了WindowManagerGlobal來處理
WindowManagerGlobal
它是一個單例設(shè)計,一個APP進程對應(yīng)一個WindowManagerGlobal,持有WMS的binder引用,可以通過它來與WMS進行IPC(跨進程通信)交互。
它還擁有許多集合,例如mViews包含了進程下所有View,mRoots包含了進程下所有ViewRootImpl,mDyingViews包含了進程下所有要銷毀的View。
2. 上述類間的關(guān)系圖
Window是View的載體,我們想要對Window進行添加、刪除、更新View,就要通過WindowManager,實際管理著是WindowManagerGlobal,它與WMS通過Session進行IPC通信,具體的實現(xiàn)交給了WMS處理。
WMS也會為每個WIndow創(chuàng)建一個WindowState來管理它們,具體的渲染工作交給了SurfaceFinger處理。本文只討論View、Window、WindowManager與WMS的關(guān)系。
3. Window對View的管理
Window是抽象的概念,它的實現(xiàn)類為PhoneWindow,內(nèi)部維護著一個DecorView,換句話說,WIndow是以View的形式呈現(xiàn)給用戶的。Window對View的操作,實際是通過ViewRootImpl實現(xiàn)。使用過程中,我們不會接觸并訪問到Window,而是通過WindowMananger來進行操作。
接下來說的Window對View的管理其實具體來說是對DecorView的管理,一個Window對應(yīng)一個DecorView。其中DecorView的創(chuàng)建、刪除,就相當于Window的添加、刪除,所以也有的地方說,這部分的討論叫做window的創(chuàng)建、更新、刪除。
3.1 Window對View的添加
WindowManager的實現(xiàn)類是WindowManagerImpl
//WindowManagerImpl
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);mGlobal.updateViewLayout(view, params);
}@Override
public void removeView(View view) {mGlobal.removeView(view, false);
}
WindowManagerImpl將對View的添加、刪除、更新都交給了WIndowManagerGlobal,mGlobal是一個單例:
//WindowManagerGlobal
public static WindowManagerGlobal getInstance() {synchronized (WindowManagerGlobal.class) {if (sDefaultWindowManager == null) {sDefaultWindowManager = new WindowManagerGlobal();}return sDefaultWindowManager;}
}
首先我們看到WindowManagerGlobal的addView(),具體步驟大概如下:
- 各類數(shù)據(jù)檢查
- 更新mViews、mRoots等集合
- 創(chuàng)建一個ViewRootImpl,將要添加的view交給它
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {//1.數(shù)據(jù)檢查//...ViewRootImpl root;View panelParentView = null;synchronized (mLock) {//2. 更新mViews/mRoots等集合root = new ViewRootImpl(view.getContext(), display);mViews.add(view);mRoots.add(root);mParams.add(wparams);//3.把要添加的view交給ViewRootImpltry {root.setView(view, wparams, panelParentView);} catch (RuntimeException e) {//...}}
}
ViewRootImpl添加到View之后,主要做了幾件事:
- 調(diào)用requestLayout()異步刷新view
- 通過session與WMS通信,真正完成window的添加
//ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {//值維護一個view(DecorView)if (mView == null) {mView = view;int res;//1. 調(diào)用requestLayout繪制ViewrequestLayout();//...try {//通過session與WMS通信,完成window的添加res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);} catch (RemoteException e) {...}//...
}
首先,requestLayout()最后通過scheduleTraversals()來申請繪制,這部分我們在屏幕繪制的部分再詳談。只需要知道發(fā)起了重繪View的請求即可。
//ViewRootImpl
@Override
public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {//檢查線程checkThread();//標志位mLayoutRequested = true;//請求繪制scheduleTraversals();}
}
當收到允許繪制的通知的時候,最終會進入到performTraversals(),從而對DecorView自頂向下地分發(fā)繪制流程,如measure/layout/draw。
requestLayout()之后,ViewRootImpl通過session通知WMS,去完成window的添加。IWindowSession是一個Binder引用,可以通過它來與WMS通信。WMS為每個應(yīng)用創(chuàng)建一個單獨的session,這個session可以通過WindowManagerGlobal獲得。
public ViewRootImpl(Context context, Display display) {mContext = context;//實例化ViewRootImpl的時候,ViewRootImpl就從WIndowManagerGlobal中拿到了可用的sessionmWindowSession = WindowManagerGlobal.getWindowSession();...
}
看一下WindowManagerGlobal中如何提供session的:
public static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {//單例,如果已經(jīng)有了,就不再創(chuàng)建了if (sWindowSession == null) {try {InputMethodManager imm = InputMethodManager.getInstance();//WMS的引用,這個是全局引用,和AMS一樣,在ServiceManager中獲取。IWindowManager windowManager = getWindowManagerService();//wms創(chuàng)建一個session交給當前客戶端進程的WindowManagerGlobal。sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {@Overridepublic void onAnimatorScaleChanged(float scale) {ValueAnimator.setDurationScale(scale);}},imm.getClient(), imm.getInputContext());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}
}
首先我們發(fā)現(xiàn)WindowManagerGlobal首先會拿到WMS的引用,然后才通過WMS創(chuàng)建一個session,用于后續(xù)的通信。我們知道,系統(tǒng)服務(wù)由ServiceMananger管理,可以全局獲取到WMS的binder引用。但為了讓W(xué)MS知道和它通信的到底是哪個window,這久需要單獨創(chuàng)建一個session,客戶端window通過session向WMS發(fā)起通信。
我們來看一下WMS是如何處理這個openSession()請求的:
//WindowManagerService
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,IInputContext inputContext) {//實例化了一個SessionSession session = new Session(this, callback, client, inputContext);return session;
}
Session是一個binder實體,它持有WMS的直接引用,客戶端window可以通過session來間接地通知WMS做一些操作。
獲取到session后,ViewRootImpl的setView()就進入到了最后一步:session.addToDisplay():
//Session
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,Rect outStableInsets, Rect outOutsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {//mServices就是WMS,讓W(xué)MS來addWindow()return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
我們來到WMS的addWIndow()
//WindowManagerService
public int addWindow(Session session,IWindow client,...){//為session建立一個WindowState,可以通過這個WindowState來與客戶端通信。final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], seq, attrs, viewVisibility, session.mUid,session.mCanAddInternalSystemWindow);
}
至此,APP進程就成功將view注冊到WMS中,同時,APP進程的WindowManagerGlobal可以通過session對WMS進行binder通信,WMS也可以通過WindowState來與WindowManagerGlobal進行binder通信。
3.2 Window對View的更新
我們再看到mGlobal.updateViewLayout(view,params);
//WindowManagerGlobal
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {//1. 參數(shù)檢查if (view == null) {throw new IllegalArgumentException("view must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;//2.更新layoutParams以及mRoot中對應(yīng)的ViewRootImplview.setLayoutParams(wparams);synchronized (mLock) {//找到這個view對應(yīng)的ViewRootImpl是誰int index = findViewLocked(view, true);ViewRootImpl root = mRoots.get(index);mParams.remove(index);mParams.add(index, wparams);//更新ViewRootImpl,這里setLayoutParams()最后也會調(diào)用到scheduleTraversals()來請求重繪,細節(jié)不在這里討論root.setLayoutParams(wparams, false);}
}
最后到了ViewRootImpl.setLayoutParams(),最后也會調(diào)用到scheduleTraversals()來請求重繪,View的繪制、刷新的細節(jié)不在本文中討論。
3.3 WIndow對View的刪除
對View的刪除大概分為以下幾步:
- 首先讓ViewRootImpl用die來刪除
- 然后將要刪除的view記錄到mGlobal的mDyingViews集合中。
- View可能立即刪除 doDie(),也可能不是立即刪,就放入隊列
- 移除各種回調(diào)
- 最后通知WMS移除這個window
我們直接看到最后:
//ViewRootImpl
mWindowSession.remove(mWindow);
4. 都有哪些Window會進行這樣的創(chuàng)建、更新、刪除操作?
Activity、Dialog、Toast等都需要View,而View都需要依附于WIndow
先從簡單的Dialog對Window的創(chuàng)建談起,最后再長篇大論到Activity的Window的創(chuàng)建
4.1 Dialog 的window創(chuàng)建
Dialog的構(gòu)造方法明顯看打了PhoneWindow的實例化、WindowManager的引用(借以與WMS通信)
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {//...//獲取windowManagermWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//實例化PhoneWindowfinal Window w = new PhoneWindow(mContext);mWindow = w;//設(shè)置回調(diào)w.setCallback(this);w.setOnWindowDismissedCallback(this);w.setOnWindowSwipeDismissedCallback(() -> {if (mCancelable) {cancel();}});w.setWindowManager(mWindowManager, null, null);//...
}
接著來到setContentView,就是把視圖布局交給DecorView,細節(jié)我們在Activity.ssetContent()中討論,幾乎一樣的。我們再來看一下show()方法
public void show() {//...mDecor = mWindow.getDecorView();//...WindowManager.LayoutParams l = mWindow.getAttributes();boolean restoreSoftInputMode = false;if ((l.softInputMode& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {l.softInputMode |=WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;restoreSoftInputMode = true;}//使用WindowManager.addViewmWindowManager.addView(mDecor, l);//...
}
mWindowManager.addView()我們知道最后會通過session通知到WMS,需要創(chuàng)建一個window來展示這個dialog。
4.2 長篇大論 Activity 的 Window創(chuàng)建
大概步驟如下:
- APP進程啟動時,會通知AMS,application啟動好了,并把applicationThread這個binder實體引用交給AMS
- AMS再以此通知app進程的第一個activity啟動
- Activity啟動之前初始化WindowManagerGlobal
- 最后onResume()執(zhí)行完畢后,將window的添加通知給WMS
我們直接切入重點,AMS -> ActivityStarter-> ActivityStackSupervisor->realStartActivity()->app進程->handleLaunchActivity(),在這個方法中主要調(diào)用了Activity幾個回調(diào):
- attach()
- onCreate()
- onStart()
- onResume()
//ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {// Initialize before creating the activityif (!ThreadedRenderer.sRendererDisabled) {GraphicsEnvironment.earlyInitEGL();}//創(chuàng)建單例,創(chuàng)建WMS引用 IWindowManagerWindowManagerGlobal.initialize();//啟動activityperformLaunchActivity();//回調(diào)onResume()handleResumeActivity();
}
Activity.attach()主要做了幾件事:
- 建立了一個與Activity一一對應(yīng)的PhoneWindow實例mWindow
- 為mWindow設(shè)置一個WMS引用
- Activity的mWindowManager也持有WMS引用
public class Activity implements Window.Callback,Window.OnWindowDismissedCallback,WindowControllerCallback,...{private Window mWindow;//一個Activity有一個Window,實現(xiàn)類為PhoneWindowprivate WindowManager mWindowManager;//WMS的引用final void attach(...){//一個activity對應(yīng)一個PhoneWindowmWindow = new PhoneWindow(Activit.this,window,...);//設(shè)置一些回調(diào)mWindow.setWindowControllerCallback(this);mWindow.setCallback(this);mWindow.setOnWindowDismissedCallback(this);//...//給Window設(shè)置一個WMS的引用mWindow.setWindowManager(//獲取WMS的遠程引用(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);//拿到WMS的引用mWindowManager = mWindow.getWindowManager();}
}
正常情況下,我們可能會在onCreate()中調(diào)用setContentView():
public void setContentView(int layoutResId){getWindow().setContentView(layoutResId);initWindowDecorActionBar();
}
getWindow()拿到的是mWindow,實現(xiàn)類是PhoneWindow,看到它的setContentView()
//PhoneWindow
private DecorView mDecor;
private ViewGroup mContentParent;public void setContentView(int layoutResId){if(mContentParent==null){installDecor();//創(chuàng)建decorView,如果沒有的話}//...
}private void installDecor(){//1. mDecore初始化if(mDecor==null){mDecore = generateDecor(-1);//new DecoreView(phoneWindow.this)}//2. mContentParent初始化,mDecoreView下的第一個ViewGroupif (mContentParent == null) {mContentParent = generateLayout(mDecor);//根據(jù)xml屬性配置Activity的默認版型樣式}}protected ViewGroup generateLayout(DecorView decor){//...mDecor.startChanging();mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//設(shè)置背景//...//設(shè)置title之類的mDecor.finishChanging();
}
Activity的DecorView是由PhoneWindow來管理的,包括mContentParent。最后來到handleResumeActivity(),需要注意的是:
- 首先回調(diào)onResume()
- 然后使用設(shè)置了的DecorView,或者給一個默認的DecorView,通知WMS要addView()
performResumeActivity()->Activity.performResume()->Activity.onResume()
//ActivityThread
final void handleResumeActivity(){//1.回調(diào)onResumer = performResumeActivity();//2.如果之前沒有通過setContentView()設(shè)置mDecor,則給一個默認的Activity a = r.activity;//當前這個activityif(r.window==null){//如果activity在onResume之后還沒有mDecor,則會在這里給一個View decor = r.window.getDecorView();//如果沒有的話會PhoneWindow.installDecor()decor.setVisibility(View.INVISIBLE);//這個wm是一個WindowManager,它持有WMS的引用//WindowManager也是一個接口,實現(xiàn)類是WindowManagerImplViewManager wm = a.getWindowManager();a.mDecor = decor;if(a.mVisibleFromClient){if(!a.mWindowAdded){a.mWindowAdded= true;wm.addView(decor,l);//l:WM.LayoutParams}} }
}
如果在onResume()結(jié)束之前,用戶都沒調(diào)用setContentView(),那么會在這里給到一個mDecor。
wm.addView(decor,l)-> WindowManagerGlobal.addView()
此后的工作我們之前就討論過了,不過是創(chuàng)建一個ViewRootImpl來維護這個DecorView,請求繪制之后通過session讓W(xué)MS來添加window。
Activity.onDestroy()時,進入到ActivityThread.handleDestroyActivity(),其中通知了WMS去removeView(),也就是移除這個Activity的Window。最后再通知AMS自己的銷毀,ActivityManager.getService().activityDestroyed();
參考文獻:
本文基于Android8.0源碼分析。結(jié)合一些博客的思路進行編排。
https://juejin.cn/post/6863756420380196877
https://blog.csdn.net/hfy8971613/article/details/103241153