- 这是 Android 10 源码分析系列的第 5 篇
- 分支:android-10.0.0_r14
- 全文阅读大概 10 分钟
经过这篇文章你将学习到如下内容,将在文末总结部分会给出相应的答案java
阅读本文以前,若是以前没有看过 Apk加载流程之资源加载一 和 Apk加载流程之资源加载二 点击下方连接前去查看,这几篇文章都是互相关联的android
本文主要来主要围绕如下几个方面来分析:git
在开始分析Dialog的源码以前,须要了解一下Dialog加载绘制流程,涉及到的数据结构与职能github
在包 android.app 下:算法
了解完相关的数据结构与职能,接下来回顾一下Dialog的建立流程编程
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(R.mipmap.ic_launcher);
builder.setMessage("Message部分");
builder.setTitle("Title部分");
builder.setView(R.layout.activity_main);
builder.setPositiveButton("肯定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
}
});
alertDialog = builder.create();
alertDialog.show();
复制代码
上面代码都不会很陌生,主要使用了设计模式当中-构建者模式,设计模式
主要经过上面四步完成Dialog的建立和显示,接下来根据源码来分析每一个方法的具体实现,以及Dialog的视图怎么与Window作关联安全
AlertDialog.Builder builder = new AlertDialog.Builder(this);
复制代码
AlertDialog.Builder是AlertDialog的内部类,用于封装AlertDialog的构造过程,看一下Builder的构造方法 frameworks/base/core/java/android/app/AlertDialog.javabash
// AlertController.AlertParams类型的成员变量
private final AlertController.AlertParams P;
public Builder(Context context) {
this(context, resolveDialogTheme(context, Resources.ID_NULL));
}
public Builder(Context context, int themeResId) {
// 构造ContextThemeWrapper,ContextThemeWrapper 是 Context的子类,主要用来处理和主题相关的
// 初始化成为变量 P
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
复制代码
AlertController.AlertParams 是AlertController的内部类,负责AlertDialog的初始化参数 frameworks/base/core/java/com/android/internal/app/AlertController.java数据结构
public AlertParams(Context context) {
mContext = context;
// mCancelable 用来控制点击外部是否可取消,默承认以取消
mCancelable = true;
// LayoutInflater 主要来解析layout.xml文件
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
复制代码
AlertDialog.Builder初始化完成以后,调用它的builder.setXXX 系列方法完成Dialog的初始化 frameworks/base/core/java/android/app/AlertDialog.java
// ... 省略了不少builder.setXXX方法
public Builder setTitle(@StringRes int titleId) {
P.mTitle = P.mContext.getText(titleId);
return this;
}
public Builder setMessage(@StringRes int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
P.mPositiveButtonText = P.mContext.getText(textId);
P.mPositiveButtonListener = listener;
return this;
}
public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
P.mPositiveButtonText = text;
P.mPositiveButtonListener = listener;
return this;
}
// ... 省略了不少builder.setXXX方法
复制代码
上面全部setXXX方法都是给Builder的成员变量P赋值,而且他们的返回值都是Builder类型,所以能够经过消息琏的方式调用
builder.setTitle().setMessage().setPositiveButton()...
复制代码
PS: 在Kotlin应该尽可能避免使用构建者模式,使用Kotlin中的具名可选参数,实现构建者模式,代码更加简洁,为了避免影响阅读的流畅性,将这部份内容放到了文末扩展阅读部分
builder.setXXX 系列方法以后调用builder.create方法完成AlertDialog构建,接下来看一下create方法 frameworks/base/core/java/android/app/AlertDialog.java
public AlertDialog create() {
// P.mContext 是ContextWrappedTheme 的实例
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
// Dialog的参数其实保存在P这个类里面
// mAler是AlertController的实例,经过这个方法把P中的变量传给AlertController.AlertParams
P.apply(dialog.mAlert);
// 用来控制点击外部是否可取消,mCancelable 默认为true
dialog.setCancelable(P.mCancelable);
// 若是能够取消设置回调监听
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
// 设置一系列监听
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
// 返回 AlertDialog 对象
return dialog;
}
复制代码
咱们来分析一下AlertDialog是如何构建的,来看一下它的造方法具体实现 frameworks/base/core/java/android/app/AlertDialog.java
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
// getContext() 返回的是ContextWrapperTheme
// getWindow() 返回的是 PhoneWindow
// mAlert 是AlertController的实例
mAlert = AlertController.create(getContext(), this, getWindow());
}
复制代码
PhoneWindows是何时建立的?AlertDialog继承自Dialog,首先调用了super的构造方法,来看一下Dialog的构造方法 frameworks/base/core/java/android/app/Dialog.java
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
// 获取WindowManager对象
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 构建PhoneWindow
final Window w = new PhoneWindow(mContext);
// mWindow 是PhoneWindow实例
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
// 继承 Handler
mListenersHandler = new ListenersHandler(this);
}
复制代码
咱们回到AlertDialog构造方法,在AlertDialog构造方法内,调用了 AlertController.create方法,来看一下这个方法
public static final AlertController create(Context context, DialogInterface di, Window window) {
final TypedArray a = context.obtainStyledAttributes(
null, R.styleable.AlertDialog, R.attr.alertDialogStyle,
R.style.Theme_DeviceDefault_Settings);
int controllerType = a.getInt(R.styleable.AlertDialog_controllerType, 0);
a.recycle();
// 根据controllerType 使用不一样的AlertController
switch (controllerType) {
case MICRO:
// MicroAlertController 是matrix风格 继承自AlertController
return new MicroAlertController(context, di, window);
default:
return new AlertController(context, di, window);
}
}
复制代码
根据controllerType 返回不一样的AlertController,到这里分析完了AlertDialog是如何构建的
调用AlertDialog.Builder的create方法以后返回了AlertDialog的实例,最后调用了AlertDialog的show方法显示dialog,可是AlertDialog是继承自Dialog的,实际上调用的是Dialog的show方法 frameworks/base/core/java/android/app/Dialog.java
public void show() {
// mShowing变量用于表示当前dialog是否正在显示
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
// mCreated这个变量控制dispatchOnCreate方法只被执行一次
if (!mCreated) {
dispatchOnCreate(null);
} else {
// Fill the DecorView in on any configuration changes that
// may have occured while it was removed from the WindowManager.
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
// 用于设置ActionBar
onStart();
// 获取DecorView
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
// 获取布局参数
WindowManager.LayoutParams l = mWindow.getAttributes();
boolean restoreSoftInputMode = false;
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
l.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
restoreSoftInputMode = true;
}
// 将DecorView和布局参数添加到WindowManager中,完成view的绘制
mWindowManager.addView(mDecor, l);
if (restoreSoftInputMode) {
l.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
}
mShowing = true;
// 向Handler发送一个Dialog的消息,从而显示AlertDialog
sendShowMessage();
}
复制代码
在上面代码中,根据mCreated变量,判断dispatchOnCreate方法是否已经调用,若是没有则调用dispatchOnCreate方法 frameworks/base/core/java/android/app/Dialog.java
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
// 调用 onCreate 方法
onCreate(savedInstanceState);
mCreated = true;
}
}
复制代码
在dispatchOnCreate方法中主要调用Dialog的onCreate方法, Dialog的onCreate方法是个空方法,因为咱们建立的是AlertDialog对象,AlertDialog继承于Dialog,因此调用的是AlertDialog的onCreate方法 frameworks/base/core/java/android/app/AlertDialog.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
复制代码
在这方法里面调用了AlertController的installContent方法,来看一下具体的实现逻辑 frameworks/base/core/java/com/android/internal/app/AlertController.java
public void installContent() {
// 获取相应的Dialog布局文件
int contentView = selectContentView();
// 调用setContentView方法解析布局文件
mWindow.setContentView(contentView);
// 初始化布局文件中的组件
setupView();
}
复制代码
private int selectContentView() {
if (mButtonPanelSideLayout == 0) {
return mAlertDialogLayout;
}
if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
return mButtonPanelSideLayout;
}
return mAlertDialogLayout;
}
复制代码
返回的布局是mAlertDialogLayout,布局文件是在AlertController的构造方法初始化的 frameworks/base/core/java/com/android/internal/app/AlertController.java
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
复制代码
回到咱们的Dialog的show方法,在执行了dispatchOnCreate方法以后,又调用了onStart方法,这个方法主要用于设置ActionBar,而后初始化WindowManager.LayoutParams对象,最后调用mWindowManager.addView()方法完成界面的绘制,绘制完成以后调用sendShowMessage方法 frameworks/base/core/java/android/app/Dialog.java
private void sendShowMessage() {
if (mShowMessage != null) {
// Obtain a new message so this dialog can be re-used
Message.obtain(mShowMessage).sendToTarget();
}
}
复制代码
向Handler发送一个Dialog的消息,从而显示AlertDialog,该消息最终会在ListenersHandler中的handleMessage方法中被执行,ListenersHandler是Dialog的内部类,继承Handler frameworks/base/core/java/android/app/Dialog.java
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS:
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
break;
case CANCEL:
((OnCancelListener) msg.obj).onCancel(mDialog.get());
break;
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
break;
}
}
复制代码
若是msg.what = SHOW,会执行OnShowListener.onShow方法,msg.what的值和OnShowListener调用setOnShowListener方法赋值的 frameworks/base/core/java/android/app/Dialog.java
public void setOnShowListener(@Nullable OnShowListener listener) {
if (listener != null) {
mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
} else {
mShowMessage = null;
}
}
复制代码
mListenersHandler构造了Message对象,当咱们在Dialog中发送showMessage的时候,被mListenersHandler所接收
在上文分析中根据mCreated变量,判断dispatchOnCreate方法是否已经调用,若是没有则调用dispatchOnCreate方法,在dispatchOnCreate方法中主要调用Dialog的onCreate方法,因为建立的是AlertDialog对象,AlertDialog继承于Dialog,因此实际调用的是AlertDialog的onCreate方法,来完成布局文件的解析,和布局文件中控件的初始化
同理咱们自定义CustomDialog继承自Dialog,因此调用的是自定义CustomDialog的onCreate方法,代码以下
public class CustomDialog extends Dialog {
Context mContext;
// ... 省略构造方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.custom_dialog, null);
setContentView(view);
}
}
复制代码
在onCreate方法中调用了 Dialog的setContentView 方法, 来分析setContentView方法 frameworks/base/core/java/android/app/Dialog.java
public void setContentView(@NonNull View view) {
mWindow.setContentView(view);
}
复制代码
mWindow是PhoneWindow的实例,最后调用的是PhoneWindow的setContentView解析布局文件,Activity的setContentView最后也是调用了PhoneWindow的setContentView方法,具体的解析流程,能够参考以前的文章Activity布局加载流程 0xA03 Android 10 源码分析:Apk加载流程之资源加载
Dialog和Activity的显示逻辑是类似的都是内部管理这一个Window对象,用WIndow对象实现界面的加载与显示逻辑
Dialog的的建立流程?
Dialog的视图怎么与Window作关联了?
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
// 获取WindowManager对象
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 构建PhoneWindow
final Window w = new PhoneWindow(mContext);
// mWindow 是PhoneWindow实例
mWindow = w;
...
}
复制代码
public void show() {
// 获取DecorView
mDecor = mWindow.getDecorView();
// 获取布局参数
WindowManager.LayoutParams l = mWindow.getAttributes();
// 将DecorView和布局参数添加到WindowManager中
mWindowManager.addView(mDecor, l);
}
复制代码
最终会经过WindowManager将DecorView添加到Window之中,用WIndow对象实现界面的加载与显示逻辑
自定义CustomDialog的view的是如何绑定的?
如何使用Kotlin具名可选参数构造类,实现构建者模式?
这部份内容参考扩展阅读部分
相比于Java的构建者模式,经过具名可选参数构造类具备如下优势?
如何在Dialog中使用DataBinding?
这部份内容参考扩展阅读部分
刚才在上文中提到了,在Kotlin中应该尽可能避免使用构建者模式,使用Kotlin的具名可选参数构造类,实现构建者模式,代码更加简洁
在 "Effective Java" 书中介绍构建者模式时,是这样子描述它的:本质上builder模式模拟了具名的可算参数,就像Ada和Python中的同样
关于Java用构建者模式实现自定义dialog,这里就不展现了,能够百度、Google搜索一下,代码显得很长........幸运的是,Kotlin是一门拥有具名可选参数的变成语言,Kotlin中的函数和构造器都支持这一特性,接下里咱们使用具名可选参数构造类,实现构建者模式,点击JDataBinding前往查看,核心代码以下:
class AppDialog(
context: Context,
val title: String? = null,
val message: String? = null,
val yes: AppDialog.() -> Unit
) : DataBindingDialog(context, R.style.AppDialog) {
init {
requireNotNull(message) { "message must be not null" }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(root)
display.text = message
btnNo.setOnClickListener { dismiss() }
btnYes.setOnClickListener { yes() }
}
}
复制代码
调用方式也更加的简单
AppDialog(
context = this@MainActivity,
message = msg,
yes = {
// do something
}).show()
复制代码
相比于Java的构建者模式,经过具名可选参数构造类具备如下优势:
DataBinding是什么?查看Google官网,会有更详细的介绍
DataBinding 是 Google 在 Jetpack 中推出的一款数据绑定的支持库,利用该库能够实如今页面组件中直接绑定应用程序的数据源
在使用Kotlin的具名可选参数构造类实现Dailog构建者模式的基础上,用DataBinding进行二次封装,加上DataBinding数据绑定的特性,使Dialog变得更加简洁、易用
Step1: 定义一个基类DataBindingDialog
abstract class DataBindingDialog(@NonNull context: Context, @StyleRes themeResId: Int) :
Dialog(context, themeResId) {
protected inline fun <reified T : ViewDataBinding> binding(@LayoutRes resId: Int): Lazy<T> =
lazy {
requireNotNull(
DataBindingUtil.bind<T>(LayoutInflater.from(context).inflate(resId, null))
) { "cannot find the matched view to layout." }
}
}
复制代码
Step2: 改造AppDialog
class AppDialog(
context: Context,
val title: String? = null,
val message: String? = null,
val yes: AppDialog.() -> Unit
) : DataBindingDialog(context, R.style.AppDialog) {
private val mBinding: DialogAppBinding by binding(R.layout.dialog_app)
init {
requireNotNull(message) { "message must be not null" }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
mBinding.apply {
setContentView(root)
display.text = message
btnNo.setOnClickListener { dismiss() }
btnYes.setOnClickListener { yes() }
}
}
}
复制代码
同理DataBinding在Activity、Fragment、Adapter中的使用也是同样的,利用Kotlin的inline、reified、DSL等等语法,能够设计出更加简洁并利于维护的代码
关于基于DataBinding封装的DataBindingActivity、DataBindingFragment、DataBindingDialog基础库相关代码,后续也会陆续完善基础库,点击JDataBinding前往查看,欢迎start
致力于分享一系列Android系统源码、逆向分析、算法相关的文章,每篇文章都会反复推敲,结合新的技术,带来一些新的思考,写出更通俗易懂的文章,若是你同我同样喜欢coding,一块儿来学习,期待与你一块儿成长