Android 应用开发之 Window 浅析
简单回顾 Android 应用开发中 Window 相关的一些知识点。
乔布斯说 Android 是抄来的。到底是不是抄来的我不好评判。但从开发者角度看, Android 跟 iOS 确实有相似的地方。我发现某些功能的实现原理在 iOS 中更简单直观一些,例如 iOS 中 Window 的创建以及消息循环的启动,对照起来看有助于加深对 Anroid 端的理解。
Activity 的 Window 是如何初始化的
iOS 的 window
iOS 程序完成启动时执行,在下面的函数中创建 window 对象,并且将程序内容通过 window 呈现给用户。AppDelegate 中的代码看起来非常直观,没绕任何弯子:
1 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
- appDelegate 持有 window 对象
- 可以在 window 上添加 UIView
- 添加 UIView 后设置 window 可见
另外要提一下的是 iOS 中 UIWindow 是 UIView 的子类。我们很容易在脑袋中想象出上面的代码和UI的对应关系。
Android 的 window
不过,同样的过程在 Android 中就有点绕了。Android 平台中的 Window.addView()
方法类似于 iOS [window addSubview]
。我们也确实可以主动调用 Window.addView()
来实现悬浮窗效果。但 Android 应用呈现 Activity 内容时不能直接使用这个方法,通常是使用 Activity.setContentView()
。那 Android 应用中 的 Window 对到底是由谁在哪里创建的?程序内容又是如何被呈现出来的?
先找出相关的类。类图如下:
1 | classDiagram |
再整理出对应的流程:
1 | # 程序入口 |
画成时序图是这样的:
1 | sequenceDiagram |
问题1。Android 应用中 的 Window 对到底是由谁在哪里创建的?
答:在Activity.attach()
方法创建 Window 对象问题2。程序内容又是如何被呈现出来的?
答:使用Actvity.setContent()
设置程序内容,程序内容被添加到 Window.mDecor 对象中,最终由Activity.makeVisible()
方法呈现出来
跟 iOS 对比,这里的类关系以及整个流程起来复杂太多了。个人感觉 Android 中 Window 类的定位很尴尬,似乎设计得有问题:
- 所谓的 WindowManager 本质上就是 View 的Manager,它管理的不是 Window,而是 View
- 既然 WindowManager 管理的是 View 对象,那何必再抽象出 Window 这一层。直接继承 View 来实现相关的功能可能更合理。少一个概念理解起来会更容易。
Android 的消息循环是如何启动的?
iOS RunLoop
事件驱动的程序需要有一个机制来负责等待事件发生——消息循环。这个机制是命令行程序和交互式程序之间的分界点。
写 iOS 程序时系统自动帮我们建立了主线程的消息循环。
Your application does not need to create these objects explicitly; each thread, including the application’s main thread, has an associated run loop object. Only secondary threads need to run their run loop explicitly, however. The app frameworks automatically set up and run the run loop on the main thread as part of the application startup process.
虽然我们看不到这个消息循环是如何建立的,但可以通过以下简单的 OC 代码来推测这个过程:
1 | int main(int argc, const char * argv[]) { |
Android Looper
Android 上是如何建立消息循环的呢?我们无法写出类似的纯 Java 代码。不过好在很容易可以从 ActivitiThread.java
中一窥其中的奥秘:
1 | public static void main(String[] args) { |
总结
画张草图总结一下。
- 第一个表示 Android 中的 Window 跟程序内容(Your View)的关系。从图中可以看到嵌套了好几层
- Window 是一个应用窗口的抽象,侧重于表达一个对应 Activity 的窗口。Window 对象通常对应于一个 Activity 对象
- View 侧重于表达一个视图窗口的显示
- 第二个表示 iOS 中的 Window 跟程序内容(Your View)的关系。从图中可以看出它们的关系简单得多
- iOS 中的 Window (UIWindow) 其实是一个 UIView
对照着上面的图,就不难理解下面 findViewById()
流程了。
1 | Activity.findViewById() |
此外,Android 中的 Window 对象也可以对应于一个 Dialog 对象,见 Dialog.java 源码。所以,我们把下图中的 Activity 替换成 Dialog 其实也是成立的。
Android 开发中一个典型的内存泄漏问题是忘记 dimiss Dialog。这类泄漏是怎么发生的,你想明白了吗?
如果你明白前面的内容,结合 Dialog.show()
方法的代码就很容易理解这种内存泄漏是如何产生的:
show()
方法调用mWindowManager.addView()
将 Dialog 的内容 (即mDecor
)添加到了 WindowMangermDecor
通常肯定持有 Activity 的强引用- 不调用
mWindowManager.removeViewImmediate()
方法进行清理的话,被引用的 Activity 产生内存泄漏是必然的
public void show() {
...
mDecor = mWindow.getDecorView();
...
mWindowManager.addView(mDecor, l);
mShowing = true;
...
}