對于ViewTree的繪制流程绷跑,Android開發(fā)者都很熟悉了椅野,但如果要從整個系統的全局角度出發(fā),理解Android的界面繪制機制榜旦,就需要了解系統的層級分工和設計實現幽七,本文記錄了個人對該機制的一些理解。
整個系統的分工
任何一個操作系統要實現界面繪制驶鹉,都需要處理好應用層、系統層和硬件層的分工協作铣墨,一般來說:
應用層負責定義畫面的內容室埋;
系統層負責綜合整個屏幕的畫面并保證流暢;
硬件層負責把數據輸出到顯示設備上。
我們分別來看:
應用層
除了系統窗口(如Toast)姚淆,我們主要在Activity中繪制界面孕蝉,這需要解決兩個問題:
- 定義顯示內容,基本原理就是在Canvas上繪制界面肉盹,然后調用surfaceholder.unlockCanvasAndPost函數昔驱,渲染到Surface中(視頻是解碼出視頻幀,渲染到Surface上)上忍,Surface實際處于系統層骤肛,通過Ashmem共享內存?zhèn)鹘oActivity使用。
- 定義顯示位置窍蓝、層次和生命期腋颠,基本原理就是Activity的PhoneWindow利用Bindler機制和系統層通信,交給系統層去統一管理吓笙,如addView淑玫、removeView等都是通過WMS去做的。
系統Framework層
Android系統是在linux基礎上擴展出來的面睛,結構相對復雜絮蒿,僅系統啟動畫面就有三個:BootLoader、linux內核叁鉴、android系統服務土涝,這三個都啟動完,才會打開Launcher幌墓。
對于應用開發(fā)來說但壮,最重要的是系統層中的Framework層,主要包括WMS和SurfaceFlinger兩個系統服務常侣,都運行在SystemServer進程中:
- WMS蜡饵,主要負責兩件事,window的層級胳施、window的管理:
層級上溯祸,WMS把所有界面分為應用window、子window和系統window三種舞肆,分別有自己的層級范圍(1 - ~您没、1000 - ~、2000 - ~)胆绊。
管理上,WMS要負責添加和移除window欧募,管理這些window的位置压状、大小和生命變化。
另外,WMS在調整window時种冬,還需要通知SurfaceFlinger去更新界面镣丑,這樣用戶才能看到界面調整后的效果。 - SurfaceFlinger娱两,主要負責兩件事莺匠,為應用提供Surface、整合圖形數據:
為應用提供Surface十兢,Activity獲取Surface時趣竣,是WMS代為向SurfaceFlinger做的請求
整合圖形數據,根據WMS的窗口層級旱物,把相關的Surface整合起來遥缕,并放到BufferQueue里,供底層繪制界面宵呛,實際上起到了生產者的作用单匣。
系統HAL層、系統Linux Kernel層和硬件層
把系統層的這兩部分和硬件層放在一起說宝穗,是因為他們聯系更緊密户秤,更偏底層,平時做應用開發(fā)時也基本不涉及到逮矛。
HAL層:是個抽象接口鸡号,處理界面的是Gralloc接口,HAL是為了解決linux硬件驅動的版權問題(Android開源橱鹏,但是有些廠商的硬件驅動不開源膜蠢,用HAL可以規(guī)避這些問題)。
Linux Kernel層:Linux 內核使用幀緩沖FrameBuffer來實現顯示功能莉兰,作為內存緩沖區(qū)(有32個Slot)挑围,既是操作硬件設備的接口,又可以緩解畫面流暢和完整性的問題(隊列糖荒、生產和消費)杉辙。
硬件層:利用驅動把數據輸出到顯示設備上。
Activity與Framework層的合作機制
了解過系統分工捶朵,我們就知道蜘矢,Activity需要與Framework層的SurfaceFlinger和WMS合作,才能實現界面繪制综看,這個合作機制需要解決這樣幾個問題:
- Window如何創(chuàng)建與使用
- Surface如何獲取與使用
- View如何創(chuàng)建與繪制
Window的創(chuàng)建與使用
Window的管理核心在WMS品腹,所以Window的創(chuàng)建和使用都需要與WMS建立通信,并交給WMS管理和調度红碑。
關于創(chuàng)建舞吭,Activity啟動時創(chuàng)建PhoneWindow泡垃,并與WMS建立通信,以便統一管理羡鸥。
關于使用蔑穴,在ViewRootImpl中調用WMS的addToDisplay,實現添加窗口惧浴。
具體過程如下:
首先存和,主線程ActivityThread啟動Activity時,調用的performLaunchActivity會執(zhí)行activity的attach函數關聯context衷旅,application等捐腿,這時就會創(chuàng)建PhoneWindow,并把PhoneWindow和WMS關聯芜茵,還會給WMS提供反向訪問的Bindler參數mToken:
//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) {
...
mWindow = new PhoneWindow(this, window);
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
setWindowManager函數是個多態(tài)特性叙量,并不是PhoneWindow的函數,而是抽象類Window的函數:
//Window源碼
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
所以九串,PhoneWindow持有一個WindowManagerImpl實例绞佩,我們再看WindowManagerImpl的源碼:
//WindowManagerImpl源碼
public final class WindowManagerImpl implements WindowManager {
//持有WindowManagerGlobal的單例對象
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
所以,App中所有addView的操作猪钮,都會經過Activity-->PhoneWindow-->WindowManagerImpl-->WindowManagerGlobal的路徑品山,最終在WindowManagerGlobal中執(zhí)行,
addView操作是在主線程resume Activity時發(fā)起的:
//ActivityThread源碼
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
ActivityClientRecord r = mActivities.get(token);
...
r = performResumeActivity(token, clearHide, reason);
...
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
...
ViewManager wm = a.getWindowManager();
...
wm.addView(decor, l);
根據前面的分析烤低,addView真正的執(zhí)行函數是在WindowManagerGlobal中:
//WindowManagerGlobal源碼
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
... //WindowManagerGlobal會保存每個窗口的viewrootimpl肘交,decorview和params的
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
root.setView(view, wparams, panelParentView);
然后,在ViewRootImpl的setView函數中扑馁,會調用WMS去addToDisplay:
//ViewRootImpl源碼
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
//DecorView放在attachInfo對象里
mAttachInfo.mRootView = view;
//mWindow是IWindow對象涯呻,實際上是個用來跨進程通信的Bindler,這樣WMS可以反向通信
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
...
這里面涉及到的類的關系腻要,可以參考Android 窗口管理:如何添加窗口到WMS總結的一張類圖
所以复罐,App內部的所有窗口由WindowManagerGlobal統一管理,而android系統的所有窗口由WMS統一管理雄家。
Surface的獲取
Surface是ViewRootImpl通過Bindler機制從SurfaceFlinger中通過Ashmem共享內存獲取到的效诅。
實際上,ViewRootImpl持有一個Surface對象趟济,所以問題在于乱投,ViewRootImpl中如何為Surface關聯到了SurfaceFlinger中的對象。
具體過程如下:
首先顷编,ViewRootImpl在setView和relayoutWindow時戚炫,都會調用relayoutWindow:
//ViewRootImpl源碼
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
...
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
...
void scheduleTraversals() {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
...
void doTraversal() {
...
performTraversals();
...
private void performTraversals() {
...
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...
int relayoutResult = mWindowSession.relayout(
mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration,
mSurface);
最后的mWindowSession.relayout實際上就是Bindler通信了。
然后媳纬,WMS會先創(chuàng)建一個SurfaceControl嘹悼,然后利用copyFrom獲取其中的Surface叛甫。
//WindowManagerService源碼
SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
if (surfaceControl != null) {
outSurface.copyFrom(surfaceControl);
這會執(zhí)行native函數nativeCreateFromSurfaceControl
最后,native層會通過SurfaceComposerClient去訪問SurfaceFlinger杨伙,SurfaceFlinger從BufferQueue中dequeuBuffer,最終返回Surface萌腿。(為C++源碼)
View如何創(chuàng)建與繪制
我們定義的ViewTree其實是DecorView中R.id.content那一部分限匣,所以View的創(chuàng)建與繪制,核心在于建立與Window的關聯毁菱,并能訪問Surface米死。
關于Window,DecorView是關聯了PhoneWindow贮庞。
關于Surface峦筒,DecorView通過ViewRootImpl訪問Surface。
具體過程如下:
首先窗慎,View是從Activity的setContentView開始創(chuàng)建的:
//Activity源碼
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
實際上調用了PhoneWindow的setContentView:
//PhoneWindow源碼
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
...
installDecor();
...
private void installDecor() {
...
mDecor = generateDecor(-1);
...
mDecor.setWindow(this);
...
在這個過程中物喷,DecorView得到了一個PhoneWindow對象:
//DecorView源碼
private PhoneWindow mWindow;
...
void setWindow(PhoneWindow phoneWindow) {
mWindow = phoneWindow;
可以看到,DecorView和PhoneWindow是互相引用的遮斥。
這樣峦失,DecorView就完成了與PhoneWindow的關聯术吗,這樣就可以被WMS管理尉辑。
然后,DecorView需要獲取到ViewRootImpl较屿,DecorView的頂級父類View提供了getViewRootImpl()函數:
//View源碼
public ViewRootImpl getViewRootImpl() {
if (mAttachInfo != null) {
return mAttachInfo.mViewRootImpl;
}
return null;
}
Attachnfo是View的內部類隧魄,ViewRootImpl在初始化時,會創(chuàng)建這個對象隘蝎,并把自己傳進去:
ViewRootImpl源碼
public ViewRootImpl(Context context, Display display) {
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
而且购啄,ViewRootImpl在setView時也會設置自己的父控件:
//ViewRootImpl源碼
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
view.assignParent(this);
這樣,DecorView的具體功能就可以交給ViewRootImpl去實現:
//DecorView源碼
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
...
getViewRootImpl().requestInvalidateRootRenderNode();
}
最后末贾,ViewRootImpl持有的Surface提供Canvas闸溃,用于繪制界面內容:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
...
canvas = mSurface.lockCanvas(dirty);
所以,利用ViewRootImpl的Surface提供的Canvas拱撵,就可以繪制了辉川。
小結
總的來說,對開發(fā)者來說拴测,除了Activity之外乓旗,最重要的就是ViewRootImpl、PhoneWindow和WindowManagerGlobal
1.ViewRootImpl
ViewRootImpl是繪制的起點(控制DectorView的繪制)集索,也是繪制的目標(mSurface)屿愚,每次WindowManagerGlobal中addView汇跨,都會生成并保存一個ViewRootImpl對象;最終的繪制canvas妆距,也是渲染到ViewRootImpl持有的mSurface中去穷遂。
2.PhoneWindow
PhoneWindow夾在Activity和DecorView之間,主要起到解耦和減負的作用娱据,可以把Activity與View的管理/window的管理切割開蚪黑。
例如,添加View實際上是交給WindowManager去addView/removeView/updateView中剩,但是Activity不需要直接與WindowManager交互忌穿,而是讓PhoneWindow去setContentView,PhoneWindow再去調用WindowManager的addView操作结啼。
3.WindowManager
WindowManager是個抽象類掠剑,只有WindowManagerImpl一個實現,而WindowManagerImpl實際上郊愧。
ViewRootImpl被WindowManagerGlobal嚴密地管理了起來朴译,WMS管理window時,也是通過操縱ViewRootImpl實現的糕珊,所以都說ViewRootImpl是WindowManager和DecorView的連接紐帶动分。
其他
深入理解Android的界面繪制機制,我們就能理解很多擴展功能的原理:
擴展場景1
自定義系統開機畫面红选,雖然沒有啟動Android澜公,但是可以操作硬件驅動、或操作linux的framebuffer實現界面繪制喇肋,這也是各廠商自己定制系統時的修改方法坟乾。
擴展場景2
側滑App,為什么可以向一側滑動整個App界面蝶防,考慮到App實際上是向DecorView添加了ViewTree甚侣,這就可以在DecorView和ViewGroup中間插一層透明的View,這樣就能滑動原有的ViewTree间学,達到側滑效果殷费。
原ViewTree
public class SWLayout extends FrameLayout{
...
//用當前ViewGroup代替ViewTree的根節(jié)點
ViewGroup decorView= (ViewGroup) activity.getWindow().getDecorView();
View child=decorView.getChildAt(0);
decorView.removeView(child);
addView(child);
decorView.addView(this);
...
}
擴展場景3
我們知道事件分發(fā)是從Activity開始的,但是懸浮窗也可以設置為允許響應事件低葫,但是懸浮窗是沒有Activity的详羡,只做了addView,那么懸浮窗的事件是如何響應的嘿悬?
硬件層攔截到事件实柠,會從WMS傳遞到ViewRootImpl,而ViewRootImpl是WindowManagerGlobal在addView時創(chuàng)建的善涨,所以懸浮窗雖然只做了addView窒盐,也有ViewRootImpl草则,也能響應事件。
當然蟹漓,有Activity的情況下炕横,ViewRootImpl的mView是DecorView,所以會把事件傳遞給DecorView牧牢,DecorView處理事件的函數為:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
其中看锉,mWindow是持有DecorView的PhoneWindow對象,而這個PhoneWindow對象的Callback塔鳍,是在Activity的attach中,創(chuàng)建出PhoneWindow后呻此,把Activity作為了Callback:
//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) {
...
mWindow = new PhoneWindow(this, window);
...
mWindow.setCallback(this);
所以轮纫,在有Activity的情況下,事件傳遞是ViewRootImpl-->DectorView-->PhoneWindow.Callback也就是Activity焚鲜,然后再傳遞給PhoneWindow-->DectorView-->ViewTree掌唾,這也可以解釋為什么DectorView里同時存在dispatchTouchEvent和superDispatchTouchEvent兩個函數。
而在沒有Activity的情況下忿磅,ViewRootImpl的mView是我們addView時傳入的ViewTree糯彬,事件就直接傳遞給ViewTree了。
參考
《深入理解Android內核設計思想》
(Activity葱她、View撩扒、Window的理解一篇文章就夠了)[https://mp.weixin.qq.com/s/7vlWU3HWPZ8pYFeG5FQBlA]
Android系統的開機畫面顯示過程分析
手把手教你讀懂源碼,View的加載流程詳細剖析
Android中MotionEvent的來源和ViewRootImpl
Android中View的量算吨些、布局及繪圖機制
公共技術點之 View 繪制流程
Android應用程序與SurfaceFlinger服務的關系概述和學習計劃
Window與WMS通信過程
Android窗口管理分析(1):View如何繪制到屏幕上的主觀理解
android阿里面試題錦集
Android 6.0 inflate過程分析
Android窗口管理分析(1):View如何繪制到屏幕上的主觀理解
公共技術點之 View 事件傳遞
公共技術點之 View 繪制流程