- 這是 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,一塊兒來學習,期待與你一塊兒成長