引言
- 这是 Android 10 源码分析系列的第 5 篇
- 分支:android-10.0.0_r14
- 全文阅读大概 10 分钟
通过这篇文章你将学习到以下内容,将在文末总结部分会给出相应的答案
- Dialog的的创建流程?
- Dialog的视图怎么与Window做关联了?
- 自定义CustomDialog的view的是如何绑定的?
- 如何使用Kotlin具名可选参数构造类,实现构建者模式?
- 相比于Java的构建者模式,通过具名可选参数构造类具有以下优点?
- 如何在Dialog中使用DataBinding?
阅读本文之前,如果之前没有看过 Apk加载流程之资源加载一 和 Apk加载流程之资源加载二 点击下方链接前去查看,这几篇文章都是互相关联的
本文主要来主要围绕以下几个方面来分析:
- Dialog加载绘制流程
- 如何使用Kotlin具名可选参数构造类,实现构建者模式
- 如何在Dialog中使用DataBinding
源码分析
在开始分析Dialog的源码之前,需要了解一下Dialog加载绘制流程,涉及到的数据结构与职能
在包 android.app 下:
- Dialog:Dialog是窗口的父类,主要实现Window对象的初始化和一些共有逻辑
- AlertDialog:继承自Dialog,是具体的Dialog的操作实现类
- AlertDialog.Builder:是AlertDialog的内部类,主要用于构造AlertDialog
- AlertController:是AlertDialog的控制类
- AlertController.AlertParams:是AlertController的内部类,负责AlertDialog的初始化参数
了解完相关的数据结构与职能,接下来回顾一下Dialog的创建流程
1 | java复制代码AlertDialog.Builder builder = new AlertDialog.Builder(this); |
上面代码都不会很陌生,主要使用了设计模式当中-构建者模式,
- 构建AlertDialog.Builder对象
- builder.setXXX 系列方法完成Dialog的初始化
- 调用builder.create()方法创建AlertDialog
- 调用AlertDialog的show()完成View的绘制并显示AlertDialog
主要通过上面四步完成Dialog的创建和显示,接下来根据源码来分析每个方法的具体实现,以及Dialog的视图怎么与Window做关联
1 构建AlertDialog.Builder对象
1 | ini复制代码AlertDialog.Builder builder = new AlertDialog.Builder(this); |
AlertDialog.Builder是AlertDialog的内部类,用于封装AlertDialog的构造过程,看一下Builder的构造方法
frameworks/base/core/java/android/app/AlertDialog.java
1 | java复制代码// AlertController.AlertParams类型的成员变量 |
- ContextThemeWrapper 继承自ContextWrapper,Application、Service继承自ContextWrapper,Activity继承自ContextThemeWrapper
- P是AlertDialog.Builder中的AlertController.AlertParams类型的成员变量
- AlertParams中包含了与AlertDialog视图中对应的成员变量,调用builder.setXXX系列方法之后,我们传递的参数就保存在P中了
1.1 AlertParams封装了初始化参数
AlertController.AlertParams 是AlertController的内部类,负责AlertDialog的初始化参数
frameworks/base/core/java/com/android/internal/app/AlertController.java
1 | ini复制代码public AlertParams(Context context) { |
- 主要执行了AlertController.AlertParams的初始化操作,初始化了一些成员变量,LayoutInflater 主要来解析layout.xml文件,关于LayoutInflater可以参考之前的文章0xA04 Android 10 源码分析:Apk加载流程之资源加载(二)
- 初始化完成AlertParams之后,就完成了AlertDialog.Builder的构建
2 调用AlertDialog.Builder的setXXX系列方法
AlertDialog.Builder初始化完成之后,调用它的builder.setXXX 系列方法完成Dialog的初始化
frameworks/base/core/java/android/app/AlertDialog.java
1 | less复制代码// ... 省略了很多builder.setXXX方法 |
上面所有setXXX方法都是给Builder的成员变量P赋值,并且他们的返回值都是Builder类型,因此可以通过消息琏的方式调用
1 | scss复制代码builder.setTitle().setMessage().setPositiveButton()... |
PS: 在Kotlin应该尽量避免使用构建者模式,使用Kotlin中的具名可选参数,实现构建者模式,代码更加简洁,为了不影响阅读的流畅性,将这部分内容放到了文末扩展阅读部分
3 builder.create方法
builder.setXXX 系列方法之后调用builder.create方法完成AlertDialog构建,接下来看一下create方法
frameworks/base/core/java/android/app/AlertDialog.java
1 | scss复制代码public AlertDialog create() { |
- 根据P.mContex 构建了一个AlertDialog
- mAler是AlertController的实例,调用apply方法把P中的变量传给AlertController.AlertParams
- 设置是否可以点击外部取消,默认可以取消,同时设置回调监听
- 最后返回AlertDialog对象
3.1 如何构建AlertDialog
我们来分析一下AlertDialog是如何构建的,来看一下它的造方法具体实现
frameworks/base/core/java/android/app/AlertDialog.java
1 | less复制代码AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { |
PhoneWindows是什么时候创建的?AlertDialog继承自Dialog,首先调用了super的构造方法,来看一下Dialog的构造方法
frameworks/base/core/java/android/app/Dialog.java
1 | less复制代码Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { |
- 获取WindowManager对象,构建了PhoneWindow,到这里我们知道了PhoneWindow是在Dialog构造方法创建的
- 初始化了Dialog的成员变量mWindow,mWindow 是PhoneWindow的实例
- 初始化了Dialog的成员变量mListenersHandler,mListenersHandler继承Handler
我们回到AlertDialog构造方法,在AlertDialog构造方法内,调用了 AlertController.create方法,来看一下这个方法
1 | java复制代码public static final AlertController create(Context context, DialogInterface di, Window window) { |
根据controllerType 返回不同的AlertController,到这里分析完了AlertDialog是如何构建的
4 调用Dialog的show方法显示Dialog
调用AlertDialog.Builder的create方法之后返回了AlertDialog的实例,最后调用了AlertDialog的show方法显示dialog,但是AlertDialog是继承自Dialog的,实际上调用的是Dialog的show方法
frameworks/base/core/java/android/app/Dialog.java
1 | ini复制代码public void show() { |
- 判断dialog是否已经显示,如果显示了直接返回
- 判断dispatchOnCreate方法是否已经调用,如果没有则调用dispatchOnCreate方法
- 获取布局参数添加到WindowManager,调用addView方法完成view的绘制
- 向Handler发送一个Dialog的消息,从而显示AlertDialog
4.1 dispatchOnCreate
在上面代码中,根据mCreated变量,判断dispatchOnCreate方法是否已经调用,如果没有则调用dispatchOnCreate方法
frameworks/base/core/java/android/app/Dialog.java
1 | scss复制代码void dispatchOnCreate(Bundle savedInstanceState) { |
在dispatchOnCreate方法中主要调用Dialog的onCreate方法, Dialog的onCreate方法是个空方法,由于我们创建的是AlertDialog对象,AlertDialog继承于Dialog,所以调用的是AlertDialog的onCreate方法
frameworks/base/core/java/android/app/AlertDialog.java
1 | scss复制代码protected void onCreate(Bundle savedInstanceState) { |
在这方法里面调用了AlertController的installContent方法,来看一下具体的实现逻辑
frameworks/base/core/java/com/android/internal/app/AlertController.java
1 | scss复制代码public void installContent() { |
- 调用selectContentView方法获取布局文件,来看一下具体的实现
- frameworks/base/core/java/com/android/internal/app/AlertController.java*
1 | csharp复制代码private int selectContentView() { |
返回的布局是mAlertDialogLayout,布局文件是在AlertController的构造方法初始化的
frameworks/base/core/java/com/android/internal/app/AlertController.java
1 | ini复制代码mAlertDialogLayout = a.getResourceId( |
- 调用Window.setContentView方法解析布局文件,Activity的setContentView最后也是调用了Window.setContentView这个方法,具体的解析流程,可以参考之前的文章Activity布局加载流程 0xA03 Android 10 源码分析:Apk加载流程之资源加载
- 调用setupView方法初始化布局文件中的组件, 到这里dispatchOnCreate方法分析结束
4.2 调用mWindowManager.addView完成View的绘制
回到我们的Dialog的show方法,在执行了dispatchOnCreate方法之后,又调用了onStart方法,这个方法主要用于设置ActionBar,然后初始化WindowManager.LayoutParams对象,最后调用mWindowManager.addView()方法完成界面的绘制,绘制完成之后调用sendShowMessage方法
frameworks/base/core/java/android/app/Dialog.java
1 | csharp复制代码private void sendShowMessage() { |
向Handler发送一个Dialog的消息,从而显示AlertDialog,该消息最终会在ListenersHandler中的handleMessage方法中被执行,ListenersHandler是Dialog的内部类,继承Handler
frameworks/base/core/java/android/app/Dialog.java
1 | csharp复制代码public void handleMessage(Message msg) { |
如果msg.what = SHOW,会执行OnShowListener.onShow方法,msg.what的值和OnShowListener调用setOnShowListener方法赋值的
frameworks/base/core/java/android/app/Dialog.java
1 | less复制代码public void setOnShowListener(@Nullable OnShowListener listener) { |
mListenersHandler构造了Message对象,当我们在Dialog中发送showMessage的时候,被mListenersHandler所接收
4.3 自定义Dialog的view的是如何绑定的
在上文分析中根据mCreated变量,判断dispatchOnCreate方法是否已经调用,如果没有则调用dispatchOnCreate方法,在dispatchOnCreate方法中主要调用Dialog的onCreate方法,由于创建的是AlertDialog对象,AlertDialog继承于Dialog,所以实际调用的是AlertDialog的onCreate方法,来完成布局文件的解析,和布局文件中控件的初始化
同理我们自定义CustomDialog继承自Dialog,所以调用的是自定义CustomDialog的onCreate方法,代码如下
1 | scala复制代码public class CustomDialog extends Dialog { |
在onCreate方法中调用了 Dialog的setContentView 方法, 来分析setContentView方法
frameworks/base/core/java/android/app/Dialog.java
1 | less复制代码public void setContentView(@NonNull View view) { |
mWindow是PhoneWindow的实例,最后调用的是PhoneWindow的setContentView解析布局文件,Activity的setContentView最后也是调用了PhoneWindow的setContentView方法,具体的解析流程,可以参考之前的文章Activity布局加载流程 0xA03 Android 10 源码分析:Apk加载流程之资源加载
总结
Dialog和Activity的显示逻辑是相似的都是内部管理这一个Window对象,用WIndow对象实现界面的加载与显示逻辑
Dialog的的创建流程?
- 构建AlertDialog.Builder对象
- builder.setXXX 系列方法完成Dialog的初始化
- 调用builder.create()方法创建AlertDialog
- 调用AlertDialog的show()初始化Dialog的布局文件,Window对象等,然后执行mWindowManager.addView方法,开始执行绘制View的操作,最终将Dialog显示出来
Dialog的视图怎么与Window做关联了?
- 在Dialog的构造方法中初始化了Window对象
1 | less复制代码Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { |
- 调用Dialog的show方法,完成view的绘制和Dialog的显示
1 | scss复制代码public void show() { |
最终会通过WindowManager将DecorView添加到Window之中,用WIndow对象实现界面的加载与显示逻辑
自定义CustomDialog的view的是如何绑定的?
- 调用Dialog的show方法,在该方法内部会根据mCreated变量,判断dispatchOnCreate方法是否已经调用,如果没有则调用dispatchOnCreate方法,在dispatchOnCreate方法中主要调用Dialog的onCreate方法
- 自定义CustomDialog继承自Dialog,所以调用的是自定义CustomDialog的onCreate方法,在CustomDialog的onCreate方法中调用setContentView方法,最后调用的是PhoneWindow的setContentView解析布局文件,解析流程参考0xA03 Android 10 源码分析:Apk加载流程之资源加载
如何使用Kotlin具名可选参数构造类,实现构建者模式?
这部分内容参考扩展阅读部分
相比于Java的构建者模式,通过具名可选参数构造类具有以下优点?
- 代码非常的简洁
- 每个参数名都可以显示的,声明对象时无须按照顺序书写,非常的灵活
- 构造函数中每个参数都是val声明的,在多线程并发业务场景中更加的安全
- Kotlin的require方法,让我们在参数约束上更加的友好
如何在Dialog中使用DataBinding?
这部分内容参考扩展阅读部分
扩展阅读
1. Kotlin实现构建者模式
刚才在上文中提到了,在Kotlin中应该尽量避免使用构建者模式,使用Kotlin的具名可选参数构造类,实现构建者模式,代码更加简洁
在 “Effective Java” 书中介绍构建者模式时,是这样子描述它的:本质上builder模式模拟了具名的可算参数,就像Ada和Python中的一样
关于Java用构建者模式实现自定义dialog,这里就不展示了,可以百度、Google搜索一下,代码显得很长……..幸运的是,Kotlin是一门拥有具名可选参数的变成语言,Kotlin中的函数和构造器都支持这一特性,接下里我们使用具名可选参数构造类,实现构建者模式,点击JDataBinding前往查看,核心代码如下:
1 | kotlin复制代码class AppDialog( |
调用方式也更加的简单
1 | less复制代码AppDialog( |
相比于Java的构建者模式,通过具名可选参数构造类具有以下优点:
- 代码非常的简洁
- 每个参数名都可以显示的,声明对象时无须按照顺序书写,非常的灵活
- 构造函数中每个参数都是val声明的,在多线程并发业务场景中更加的安全
- Kotlin的require方法,让我们在参数约束上更加的友好
2. 如何在Dialog中使用DataBinding
DataBinding是什么?查看Google官网,会有更详细的介绍
DataBinding 是 Google 在 Jetpack 中推出的一款数据绑定的支持库,利用该库可以实现在页面组件中直接绑定应用程序的数据源
在使用Kotlin的具名可选参数构造类实现Dailog构建者模式的基础上,用DataBinding进行二次封装,加上DataBinding数据绑定的特性,使Dialog变得更加简洁、易用
Step1: 定义一个基类DataBindingDialog
1 | less复制代码abstract class DataBindingDialog(@NonNull context: Context, @StyleRes themeResId: Int) : |
Step2: 改造AppDialog
1 | kotlin复制代码class AppDialog( |
同理DataBinding在Activity、Fragment、Adapter中的使用也是一样的,利用Kotlin的inline、reified、DSL等等语法,可以设计出更加简洁并利于维护的代码
关于基于DataBinding封装的DataBindingActivity、DataBindingFragment、DataBindingDialog基础库相关代码,后续也会陆续完善基础库,点击JDataBinding前往查看,欢迎start
结语
致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,正在努力写出更好的文章,如果这篇文章对你有帮助给个 star,文章中有什么没有写明白的地方,或者有什么更好的建议欢迎留言,欢迎一起来学习,在技术的道路上一起前进。
计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请帮我点个赞,我会陆续完成更多 Jetpack 新成员的项目实践。
算法
由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序。
- 数据结构: 数组、栈、队列、字符串、链表、树……
- 算法: 查找算法、搜索算法、位运算、排序、数学、……
每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路、时间复杂度和空间复杂度,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin,一起来学习,期待与你一起成长。
Android 10 源码系列
正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以关注我 GitHub 上的 Android10-Source-Analysis,文章都会同步到这个仓库。
- 0xA01 Android 10 源码分析:APK 是如何生成的
- 0xA02 Android 10 源码分析:APK 的安装流程
- 0xA03 Android 10 源码分析:APK 加载流程之资源加载
- 0xA04 Android 10 源码分析:APK 加载流程之资源加载(二)
- 0xA05 Android 10 源码分析:Dialog 加载绘制流程以及在 Kotlin、DataBinding 中的使用
- 更多……
工具系列
- 为数不多的人知道的 AndroidStudio 快捷键(一)
- 为数不多的人知道的 AndroidStudio 快捷键(二)
- 关于 adb 命令你所需要知道的
- 如何高效获取视频截图
- 10分钟入门 Shell 脚本编程
- 如何在项目中封装 Kotlin + Android Databinding
逆向系列
本文转载自: 掘金