所有需要顯示在螢幕上的內容,都是通過WindowManager來操作的,這就是我們通常所說的WMS(WindowManagerService),本篇我們一起來分析一下WindowManagerService、Surface、SurfaceFlinger、WindowManager之間是如何建立關係並互動的。我們通過Dialog來分析它們之間如何建立聯絡並互動。
首先當我們想要獲取WindowManager的時候,發現它是一個抽象類,所以我們需要找到它的實現。 我們可以通過Context的getSystemService方法獲取到對應的服務例項,而WindowManager就是在ContextImpl中註冊的服務之一。
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
複製程式碼
原來WIndowManager的例項是WindowManagerImpl。 那麼Dialog是如何獲取到WindowManager的?
首先我們先分析下Dialog的建構函式:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
複製程式碼
可以發現,在建構函式中獲取到WindowManager物件,並通過Window的setWindowManager方法將Window和WindowManager聯絡起來。
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
複製程式碼
最終通過new WindowManagerImpl(mContext, parentWindow)將WindowManager和Window聯絡起來,此時可以發現這個構建的WindowManager方法中多出一個parentWindow,表示和具體的WIndow相關聯。
現在我們跳轉到WindowManagerImpl中:
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
return new WindowManagerImpl(displayContext, mParentWindow);
}
@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、移除View、更新View的操作最終都交給了WindowManagerGlobal類,並呼叫addView方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 構建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
// 設定佈局
view.setLayoutParams(wparams);
// 將View新增到View列表中
mViews.add(view);
// 將root新增到root列表中
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
// 呼叫ViewRootImpl的addView方法將View顯示到介面上
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
throw e;
}
}
複製程式碼
通過分析WindowManagerGlobal,可以發現主要操作是:
- 構建ViewRootImpl
- 將佈局設定給View
- 將View、ViewRootImpl儲存起來
- 通過setView將view顯示到介面上
到此時,我們分析到了framework層,而WMS是在native層,那麼framework層是如何與native層建立關係和互動的呢?
這個時候我們就需要看ViewRootImpl類了,這個類繼承自Handler,是native與java層View系統通訊的橋樑,比如performTraversals函式就是收到繪製View的訊息之後,通過呼叫measure、layout、draw來繪製整個檢視。
首先我們先看建構函式:
public ViewRootImpl(Context context, Display display) {
mContext = context;
// 獲取Window Session,與WindowManagerService之間建立聯絡
mWindowSession = WindowManagerGlobal.getWindowSession();
// 儲存當前執行緒,更新UI只能在建立了ViewRootImpl的執行緒
// 所以通常情況下,我們只能在UI執行緒更新UI,但並不是不能在子執行緒中更新UI
mThread = Thread.currentThread();
}
複製程式碼
我們繼續點進WindowManagerGlobal.getWindowSession()方法:
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
// 獲取WindowManagerService
IWindowManager windowManager = getWindowManagerService();
// 與WindowManagerService建立聯絡
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
}
return sWindowManagerService;
}
}
複製程式碼
最終通過ServiceManager.getService("window")獲取WMS,並將WMS轉換為IWindowManager。
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
複製程式碼
通過getService方法可以發現,返回的是一個IBinder物件,這個也就能從側面說明Framework和WMS之間是通過Binder機制進行通訊的,獲取到WMS之後再通過IWindowManager.Stub.asInterface方法將WMS的IBinder轉換成WindowManager物件。最終通過openSession與WMS建立通訊對話,之後就可以通過這個session,java層和native層之間就可以通訊了。
這個時候View並不會顯示在介面上,WMS只負責管理View的z-order,也就是管理哪個view應該顯示在最上層。這個時候WindowManagerGlobal之後就呼叫了root.setView(view, wparams, panelParentView)方法,這個方法就是發起顯示請求:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// 請求佈局
requestLayout();
// 向WMS發起請求
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
performTraversals();
}
}
private void performTraversals() {
// 獲取Surface,用於圖形繪製
// performMeasure
// performLayout
// performDraw
}
複製程式碼
呼叫performTraversals方法,這個方法就是我們繪製介面的方法,裡面會呼叫我們所熟悉的onMeasure、onLayout、onDraw方法。
private void performDraw() {
try {
// 呼叫繪製函式
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void draw(boolean fullRedrawNeeded) {
// 獲取surface
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
// 繪製需要更新
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
// 使用硬體加速
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
// 使用硬體渲染繪製
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
// 使用cpu繪製
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
複製程式碼
在draw函式中獲取需要繪製的區域,再判斷是否使用硬體加速。 通常情況下都是使用cpu繪製,也就是直接呼叫drawSoftware:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
// 獲取canvas物件,用於framework層繪製
canvas = mSurface.lockCanvas(dirty);
} catch (Surface.OutOfResourcesException e) {
return false;
}
try {
// 開始繪製,從decorView開始繪製
mView.draw(canvas);
} finally {
// 釋放canvas鎖,並通知SurfaceFlinger更新
// private static native void nHwuiDraw(long renderer); 呼叫native方法
surface.unlockCanvasAndPost(canvas);
}
return true;
}
複製程式碼
從上面的函式可以看出繪製的過程:
- 判斷使用gpu還是cpu渲染
- 獲取繪製的surface物件
- 通過surface獲取並鎖住canvas物件
- 從DecorView開始發起整個view樹的繪製
- 解鎖canvas物件,並通知surfaceFlinger物件更新檢視,呼叫native方法。
到此為止,Activity、Dialog等元件的View就顯示到使用者的介面上了。