---
title: 'WindowManager 和 Dialog'
disqus: kyleAlien
---
WindowManager 和 Dialog
===
## OverView of Content
> 可配合 [**Builder 模式**](https://hackmd.io/rLN00StPRmqok3zPzIsxjA?view#Android-Source-Builder-%E7%A0%94%E7%A9%B6)對 AlertDialog 的創建一起看,研究 Dialog 的 show 函數如何新增畫面在界面上
[TOC]
## WindowManager 概述
不只 Dialog 的畫面、Activity 的畫面也是透過 WindowManager 來加載到螢幕上
### WindowManager 代理
* WindowManager 是 APP 應用端 對系統服務 WMS 的代理,如同在 [**LayoutInflater**](https://hackmd.io/EPfgb14TS-Kcd-Pv7tDZ-w?view#LayoutInflate-%E6%A6%82%E8%BF%B0) 中所提到的,**WMS 服務也是透過 SystemServiceRegistry 加載**,**==WINDOW_SERVICE==**
* 傳入 Context 建立 WindowManagerImpl 物件 (下面會看到不同的差異)
```java=
// SystemServiceRegistry.java
static {
...
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
...
}
// --------------------------------------------------------------------
// WindowManagerImpl.java
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
```
## Dialog 視窗
我們知道 Context 的實現類是 [**ContextImpl**](https://hackmd.io/qhIoWcSZQ-GzL3G_Sgqb0A#Context-amp-ContextImpl) 類,可以**透過 Context 的 getSystemService 來獲得具體服務 (系統服務進程) 的代理**
Dialog 就在建構函數時獲取 WindowManager 服務
### Dialog 取得 - WindowManager 服務
* Dialog constructor 建構函數:會準備 WindowManager、PhoneWindow 對象
| 類 | 功能概述 |
| -------- | -------- |
| WindowManager | 添加 Dialog 視窗 |
| PhoneWindow | 加載 xml 布局 |
```java=
/**
* Dialog.java
*/
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
private final WindowManager mWindowManager;
final Window mWindow; // 實作是 PhoneWindow 類
// Dialog construct
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
... 準備 context
// 發現 Dialog 建構函數在獲取服務
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 自己建構 PhoneWindow 類
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
// @ 追蹤 setWindowManager 方法
// 讓 Window & WindowManager 產生關係!!!
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
}
```
> 
* **透過 [Window](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/Window.java)#setWindowManager 方法:讓 Window (實作類是 PhoneWindow) & WindowManager 產生了關聯**
```java=
/**
* Window.java (抽象類)
*/
private WindowManager mWindowManager;
public void setWindowManager(WindowManager wm,
IBinder appToken, // null
String appName) { // null
setWindowManager(wm, appToken, appName, false);
}
public void setWindowManager(WindowManager wm,
IBinder appToken, // null
String appName, // null
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
// 如果沒有則再次獲取 Window 服務
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// @ 追蹤 createLocalWindowManager 方法
// 建立 WindowManagerImpl
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
```
* [**WindowManagerImpl**](**https://**)#**createLocalWindowManager** 這個方法中與 `WINDOW_SERVICE 服務` 都是創建 WindowManagerImpl 對象,差別是 **createLocalWindowManager 將傳入的 Window 設定為 parentWindow**
> 這裡的 parentWindow 就是 Dialog 的 PhoneWindow 類
> 並可以看出 Window & WindowManagerImpl 相互持有
> 
:::info
* 與 Context 建立的 WindowManagerImpl 比起來,Context 的 WindowManagerImpl 尚未與 Window 產生關聯
:::
```java=
// WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final Window mParentWindow;
// 子系統服務的呼叫,並沒有 parentView
public WindowManagerImpl(Context context) {
this(context, null /* parentWindow */, null /* clientToken */);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
private WindowManagerImpl(Context context, Window parentWindow,
@Nullable IBinder windowContextToken) {
mContext = context;
mParentWindow = parentWindow;
mWindowContextToken = windowContextToken;
}
}
```
> 
### WindowManager addView - 連接 [**WindowManagerGlobal**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/WindowManagerGlobal.java)
* 在 Dialog 中的 show 方法最後會使用 **WindowManager#addView**,從上面我們會知道它調用到 WindowManagerImpl#**addView**
```java=
// Dialog.java
public void show() {
... 省略部份
// @ mWindow 就是 PhoneWindow
mDecor = mWindow.getDecorView();
... 省略部份
WindowManager.LayoutParams l = mWindow.getAttributes();
...
// @ 追蹤 addView 方法
mWindowManager.addView(mDecor, l);
...
}
```
* WindowManagerImpl#addView 其實會調用到 [**WindowManagerGlobal**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/WindowManagerGlobal.java)#addView 方法
```java=
/**
* WindowManagerImpl,java
*/
public final class WindowManagerImpl implements WindowManager {
// 進程單例
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
// 目前是 Dialog#PhoneWindow
private final Window mParentWindow;
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
private void applyTokens(@NonNull ViewGroup.LayoutParams params) {
// token 其實就是 WindowManager.LayoutParams
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
assertWindowContextTypeMatches(wparams.type);
// Only use the default token if we don't have a parent window and a token.
if (mDefaultToken != null && mParentWindow == null && wparams.token == null) {
wparams.token = mDefaultToken;
}
wparams.mWindowContextToken = mWindowContextToken;
}
}
```
從關係圖上可以看出 WindowManagerImpl 是一個橋接類,它聯繫著 Window & WindowManagerGlobal 類
> 
### WindowManagerGlobal - 創建 ViewRootImpl
* WindowManagerGlobal#addView 方法會創建一個 ViewRootImpl 對象
```java=
/**
* WindowManagerGlobal,java
*/
public final class WindowManagerGlobal {
...
private static WindowManagerGlobal sDefaultWindowManager;
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>();
private WindowManagerGlobal() { }
// 單例
public static WindowManagerGlobal getInstance() {
// 使用 - 類鎖 同步機制
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 忽略判空、檢查...
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// Window 布局參數
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
// 目前有 parentWindow
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// 沒有 parentWindow 設定硬體加速
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;
// mLock 普通對象鎖
synchronized (mLock) {
// Start watching for system property changes.
...
// @ 建立 ViewRootImpl 對象
root = new ViewRootImpl(view.getContext(), display);
// 設定布局參數
view.setLayoutParams(wparams);
// @ 以下都是 ArrayList 對象
mViews.add(view); // View
mRoots.add(root); // ViewRootImpl
mParams.add(wparams); // WindowManager.LayoutParams 視窗參數
}
//最後執行此操作,因為它會觸發傳遞消息
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
```
這邊先跳過 Binder 與 WMS 的進程通訊,先知道 WindowManagerGlobal#addView 行為 **會觸發到 ++[ViewRootImpl](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewRootImpl.java)#setView++ 方法**
* 分析完畢 View 布局後,在 WindowManagerGlobal 中的 addView 方法主要做了下面 4 件事情
1. **新建 ViewRootImpl 對象**
2. 將 Window 布局參數 Params 設定給 View
3. 儲存 View、ViewRootImpl、Params 到 WindowManagerGlobal#ArrayList 中
4. **透過 ViewRootImpl 的 setView 方法設定 View 顯示到視窗上**
* WindowManagerGlobal & WindowManagerImpl 關係圖
> 
## ViewRootImpl
ViewRootImpl 並不是 View 類,負責與 Native 通訊,取得繪畫範圍,並驅動 App 進程繪圖
### [ViewRootImpl](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewRootImpl.java) 類 - 取得 WindowSession 服務
* 從上面 WindowManagerGlobal#addView 會發現 **++每一個新的 View 都會建立一個 ViewRootImpl 對象++**,**其實它本身並不是 View 類,它是 ==Framework 層與 native 層的交接==**
```java=
// ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks,
ThreadedRenderer.HardwareDrawCallbacks {
...
public ViewRootImpl(Context context, Display display) {
mContext = context;
// @ 追蹤 getWindowSession 方法
// 獲取 WindowSession,與 WMS 建立連接,往下追 getWindowSession
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
// "1. "
// 儲存目前的 Thread
mThread = Thread.currentThread();
...
}
}
```
:::success
* 更新 View 的 Thead,**在 ViewRootImpl 有紀錄 Thread**
在 Anroid 中如果 Work Thread 更新 UI 會導致異常拋出,並不是因為 UI Thread 才能更新 UI,而是因為 ViewRootImpl 紀錄時,**就是紀錄 UI Thread**,**==非 ViewRootImpl 紀錄的 Thread 是不能更新 ViewRootImpl==**
:::
* [**WindowManagerGlobal**](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/WindowManagerGlobal.java)#getWindowSession 方法:是 Framework 與 Native Binder 通訊的入口,並且從這裡可以看到 WindowSession 服務
> WindowSession 是一個隱密服務,ServiceManager 也不會記錄,必須透過 WMS 間接得到
```java=
// WindowManagerGlobal.java
private static IWindowManager sWindowManagerService;
public static IWindowSession getWindowSession() {
// 類鎖
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
// @ getWindowManagerService
// 獲取 WMS 服務,往下追
IWindowManager windowManager = getWindowManagerService();
// @ 與 WMS 建立一個 Session
sWindowSession = windowManager.openSession(...);
} /* 省略 catch*/
return sWindowSession;
}
}
public static IWindowManager getWindowManagerService() {
// 類鎖
synchronized (WindowManagerGlobal.class) {
// 檢查緩存
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
... 省略部份
}
return sWindowManagerService;
}
}
// ------------------------------------------------------------------
/**
* ServiceManager.java
*/
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
// 檢查本地是否有緩存
if (service != null) {
return service;
} else {
// 沒有緩存,則去 ServiceManager 進程取得 Service IBinder 對象
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
```
1. ViewRootImpl 物件的建立,會儲存當前的 Thread
> 這也就是我們 **如果使用 WorkThread 更新 UI 時為何會拋出異常**
2. [**Binder 機制**](https://hackmd.io/yNGrVdN-RtelUqYQgn9avg#%E7%B5%90%E8%AB%96):App 應用端透過 **asInterface 獲取遠端服務的代理,也就是獲取 WMS (IWindowManager) 服務的 ++代理++**
> IWindowManager 是 `.aidl` 檔案,它的實做類(真正服務端)就是 WindowManagerService
```java=
/**
* WindowManagerService.java
*/
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
... 省略
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
// 在 WMS 創建一個 Session 對象
Session session = new Session(this, callback, client, inputContext);
return session;
}
}
```
* 以上建立好 Session 後就可以 **使用 Session 來交換資訊**
:::info
* 當 ViewRootImpl & WindowManagerService 產生關連後,**還不能讓 View 顯示在螢幕上,==WMS 是管理 View 的 Z 軸==,++WMS 管理當前狀態下哪個 View 應該在最上層顯示++**
* **其是 WMS 並不是管理 Window,而是==管理特定 Window 中的 View==**
:::
> 
:::info
* 從這邊也可以知道每次呼叫 WindowGlobalManager#addView 都會創建一個 ViewRootImpl,並對應創建一個 WindowSession
> 
:::
### WindowSession 添加 ViewRootImpl
* 與 WMS 建立 Session 後會呼叫 ViewRootImpl 的 `setView` 方法:**該方法會向 WMS 發起顯示 `Dialog` or `Activity` 中的 ++DecorView 請求++**
> 回到 WindowManagerGlobal 中的 addView 方法的最後一個步驟 "setView" (起初是 Dialog 調用)
```java=
/**
* WindowManagerGlobal.java
*/
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
// mLock 普通對象鎖
synchronized (mLock) {
// Start watching for system property changes.
...
// 建立 ViewRootImpl 對象
root = new ViewRootImpl(view.getContext(), display);
// 設定布局參數
view.setLayoutParams(wparams);
// 以下都是 ArrayList
mViews.add(view); // View
mRoots.add(root); // ViewRootImpl
mParams.add(wparams); // WindowManager.LayoutParams 視窗參數
}
//最後執行此操作,因為它會觸發傳遞消息
try {
// @ 追蹤 ViewRootImpl#setView 方法
root.setView(view, wparams, panelParentView);
} /* 省略 catch */
}
// ---------------------------------------------------------------------
/**
* ViewRootImpl.java
*/
final W mWindow;
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
... 省略部份
mWindow = new W(this);
}
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
// 由於過於複雜,這裡顯示關鍵函數
requestLayout();
try {
...
// 向 WMS 發起請求
// @ 查看 addToDisplay
res = mWindowSession.addToDisplay(mWindow,
mSeq,
mWindowAttributes,
getHostVisibility(),
mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} // 省略 catch、finally
}
```
* WindowSession#addToDisplay 是添加對 Binder 傳送一個 [**IWindow**](https://cs.android.com/android/platform/superproject/+/master:out/soong/.intermediates/frameworks/base/framework-minus-apex-intdefs/android_common/xref33/srcjars.xref/android/view/IWindow.java) 對象,如果 WMS 需要調整該 View 的話,就會通過 IWindow 來通知該 view
```java=
// IWindow.java
public interface IWindow extends android.os.IInterface
{
@Override
public void executeCommand(java.lang.String command, java.lang.String parameters, android.os.ParcelFileDescriptor descriptor) throws android.os.RemoteException
{
}
@Override
public void resized(android.window.ClientWindowFrames frames, boolean reportDraw, android.util.MergedConfiguration newMergedConfiguration, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId) throws android.os.RemoteException
{
}
... 省略部份方法
}
```
:::info
* 其實也就是把 View (管理者為 ViewRootImpl) 添加進 WindowSession
:::
### [ViewRootImpl](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/ViewRootImpl.java) - setView 本地繪製
* ViewRootImpl#setView 的內容十分的多,所以我這先提出呼叫 `setView` 時較為關鍵的函數,分別是
1. **requestLayout(要求布局)**
2. **checkThread(檢查是否是主線程更新)**
:::warning
這裡就會檢查是否是當初呼叫 ViewRootImpl 的 Thread
:::
3. **scheduleTraversals(準備測量、繪製)**
> 之後會觸發 **performTraversals** 方法
```java=
/**
* ViewRootImpl.java
*
* #setView 函數呼叫 requestLayout 函數
*/
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest)
// 檢查線程是否切換
checkThread();
mLayoutRequested = true;
// 安排繪製
scheduleTraversals();
}
}
void checkThread() {
// @ 只有創建 ViewRootImpl 對象的 Thread 可以往下操作執行
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// "1. " 注意第二個參數 mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL,
mTraversalRunnable,
null
);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// 沒規定 Looper,預設當前的 Thread 的 Looper (Main Thread)
final ViewRootHandler mHandler = new ViewRootHandler();
```
* mTraversalRunnable:它是一個 **Runnable 任務**,,該任務執行 **doTraversal** 方法
```java=
// ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
// performTraversals 函數也相當的攏長
private void performTraversals() {
...
}
```
:::success
* performTraversals 主要做以下 4 個任務
1. 獲取 `Surface` 物件,用於圖形繪製
2. 側兩整個檢視樹的各個 View 的大小,**performMeasure** 函數
> 觸發 onMeasure
3. 布局整個 View 樹,**performLayout** 函數
> 觸發 onLayout
4. 繪製整個 View 樹,**performDraw** 函數
> 觸發 onDraw
:::
### ViewRootImpl 取得 Canvas - onDraw 繪製
* ViewRootImpl#performTraversals 函數,其中第 4 個要點繪製 View 樹,Framwork 會獲取到圖形繪製表面 `Surface` (也就是 `Canvas` 對象),View 就會繪製在 Canvas 上
```java=
// ViewRootImpl.java
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
// 呼叫繪製函數
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}
private void draw(boolean fullRedrawNeeded) {
// 1. 繪製表面
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
...
// 2. 繪圖表面更新
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
// 使用 GPU 繪製,也就是硬體加速
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
...
if (updated) {
requestDrawWindow();
}
// 3. 硬體加速 ! 使用硬體渲染
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
// 4. 使用 CPU 繪製圖形
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
```
* 在 ViewRootImpl#performDraw 的過程中主要有分為:^1.^ CPU 繪製、^2.^ GPU 繪製,大部分狀況下我們都是使用 CPU 繪製
:::success
* Surface 中的可繪製區域就是 Canvas,Surface 就是一個巨大畫布,Canvas 就是該畫布要繪製的區塊
:::
```java=
// ViewRootImpl.java
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;
// 1. 獲取指定區域的 Cavans 物件
canvas = mSurface.lockCanvas(dirty);
...
// TODO: Do this in native
canvas.setDensity(mDensity);
} /* 省略 catch */
try {
...
try {
// 2. 從 DecorView 開始繪製,也就是整個 Window 的根檢視,這會引起整個 View 樹的重繪操作
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
// 3. 釋放 Canvas 鎖,並通知 SurfaceFlinger 更新這塊區域
surface.unlockCanvasAndPost(canvas);
}
...
}
return true;
}
```
## 結論
* Dialog View 創建的概念圖
> 
## Appendix & FAQ
:::info
:::
###### tags: `Android Framework`