Android 源码的桥接模式
桥接模式介绍
桥接模式(Bridge Pattern)也称为桥梁模式,是结构型设计模式之一。桥接模式承担着连接两边的作用。
桥接模式的定义
将抽象部分与实现部分分离,使它们都可以独立地进行变化。
Android 源码中的桥接模式实现
Framework 内部的源码实现中,比较典型的桥接模式应用是 Window 与 WindowManager 之间的关系。
在 fwk 中 Window 和 PhoneWindow 构成窗口的抽象部分,其中 Window 类为该抽象部分的抽象接口,PhoneWindow 为抽象部分具体的实现及扩展。而 WindowManager 则为实现部分的基类,WindowManagerImpl 为实现部分具体的逻辑实现,其使用 WindowManagerGlobal 通过 IWindowManager 接口与 WindowManagerService (也就是 WMS)进行交互,并由 WMS 完成具体的窗口管理工作。
public abstract class 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);
// 如果传入的 WindowManager 为空,则通过 getSystemService 获取 WindowManager。
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// 将 WindowManager 和 Window 绑定,创建一个 WindowManagerImpl
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
}
关于 WindowManagerService
Android 的 framework 层主要就是由 WindowManagerService 与另外一个系统服务 ActivityManagerService(简称 AMS)还有 View 所构成,这3个模块穿插交互在整个 framework 中,掌握了它们之间的关系以及每一个步骤的逻辑,你对 framework 就至少了解百分之五十了。
和很多其他系统服务一样,WMS 也是由 SystemServer 启动。
SystemServer 启动 WMS
private void startOtherServices() {
...
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore, new PhoneWindowManager());
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
此后其他进程就可以通过用 ServiceManager 查询 window 来获取 WMS。WMS 的 main 方法如下:
public static WindowManagerService main(final Context context, final InputManagerService im,
final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
WindowManagerPolicy policy) {
DisplayThread.getHandler().runWithScissors(() ->
sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
onlyCore, policy), 0);
return sInstance;
}
调用 runWithScissors 构造了 WMS 实例。
WMS 的主要功能可以分为两方面,一是对窗口的管理;二是对事件的管理和分发。其接口方法以 AIDL 的方式定义在 IWindowManager.aidl 中,编译后会生成一个 IWindowManager.java 接口文件。
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
...
/**
* List of window tokens that have finished starting their application,
* and now need to have the policy remove their windows.
*/
// 已经完成启动的窗口
final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
/**
* List of app window tokens that are waiting for replacing windows. If the
* replacement doesn't come in time the stale windows needs to be disposed of.
*/
// 等待替换的窗口
final ArrayList<AppWindowToken> mWindowReplacementTimeouts = new ArrayList<>();
/**
* Windows that are being resized. Used so we can tell the client about
* the resize after closing the transaction in which we resized the
* underlying surface.
*/
// 尺寸正在改变的窗口
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
...
}
WMS 维护上述的各个成员变量值,可以看到大量线性表的应用,不同的窗口或同一个窗口在不同的状态阶段有可能位于不同的表中。
Android 的窗口类型主要只有 2 种:
- 应用窗口。Activity 所处的窗口、应用对话框窗口、应用弹出窗口等都属于该类。与应用窗口相关的 Window 类主要是 PhoneWindow,其主要应用于手机,PhoneWindow 继承于 Window,其核心是 DecorView。应用窗口的添加主要就是通过 WindowManager 的 addView 方法将一个 DecorView 添加到 WindowManager 中。
- 系统窗口。,常见的屏幕顶部的状态栏、底部的导航栏、桌面窗口等都是系统窗口,系统窗口没有针对性的封装类,只需直接通过WindowManager 的 addView 方法将一个 View 添加至 WindowManager 中即可。
以屏幕顶部的状态栏为例,其添加逻辑在 StatusBar.java 的 addStatusBarWindow 中。
private void addStatusBarWindow() {
// 构造具体的状态栏 View,也就是下面会用到的 mStatusBarWindow,其本质上是一个 FrameLayout
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mRemoteInputManager.setUpWithPresenter(this, mEntryManager, this,
new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationData.Entry entry,
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.row.notifyHeightChanged(true /* needsAnimation */);
updateFooter();
}
public void lockScrollTo(NotificationData.Entry entry) {
mStackScroller.lockScrollTo(entry.row);
}
public void requestDisallowLongPressAndDismiss() {
mStackScroller.requestDisallowLongPress();
mStackScroller.requestDisallowDismiss();
}
});
mRemoteInputManager.getController().addCallback(mStatusBarWindowManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
addView 实质上是由 WindowManagerGlobal 的 addView 方法实现具体逻辑,辗转多次后最终会调用到 ViewRootImpl 的 setView 方法。在该方法中通过 addToDisplay 方法向 WMS 发起一个 Session 请求,这里要注意的是,IWindowSession 也是一个 AIDL 接口文件,需要将其编译后才生成 IWindowSession.java 接口,这里 addToDisplay 方法最终会调用到 Session 中的对应方法:
addToDisplay
@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) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
addToDisplay 会调用 WMS 的 addWindow 添加窗口。
public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
int[] appOp = new int[1];
// 检查窗口权限,能否添加窗口
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
...
// 根据不同的窗口类型,检查窗口的有效性
if (token == null) {
if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
Slog.w(TAG_WM, "Attempted to add application window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_INPUT_METHOD) {
Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_VOICE_INTERACTION) {
Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_WALLPAPER) {
Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_DREAM) {
Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_QS_DIALOG) {
Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (type == TYPE_TOAST) {
// Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
parentWindow)) {
Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
+ attrs.token + ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
...
// 构造 WindowState 对象
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
...
// 将窗口添加至 Session
win.attach();
// 将窗口添加至 WMS
mWindowMap.put(client.asBinder(), win);
...
// 打开输入通道,以便当前窗口可以接收到事件
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
...
// 如果当前窗口可以接收按键事件,那么更新焦点,将窗口信息存入 InputDispatcher
boolean focusChanged = false;
if (win.canReceiveKeys()) {
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
false /*updateInputWindows*/);
if (focusChanged) {
imMayMove = false;
}
}
if (imMayMove) {
displayContent.computeImeTarget(true /* updateImeTarget */);
}
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
win.getParent().assignChildLayers();
if (focusChanged) {
mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
}
mInputMonitor.updateInputWindowsLw(false /*force*/);
if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
+ client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(displayId)) {
reportNewConfig = true;
}
}
if (reportNewConfig) {
sendNewConfiguration(displayId);
}
Binder.restoreCallingIdentity(origId);
return res;
}
addWindow 方法中会通过 win.attach 创建一个管理的 SurfaceSession 对象用来和 SurfaceFlinger 通信,而 SurfaceSession 实际对应 native 层的 SurfaceComposerClient 对象,SurfaceComposerClient 主要作用就是在应用进程与 SurfaceFlinger 服务之间建立连接。
参考资料
《Android 源码设计模式解析与实战 · 第24章 连接两地的交通枢钮——桥接模式》