【Android原始碼】AlertDialog 原始碼分析

指間沙似流年發表於2017-12-23

Android系統封裝了AlertDialog,用來給我們使用。我們可以通過

AlertDialog.Builder builder = new AlertDialog.Builder(this)
	.setTitle("Title")
	.setMessage("message")
	.create()
	.show();
複製程式碼

這個其實就是典型的Builder設計模式,通過封裝複雜的dialog物件,將組建和構建分離,當使用者使用的時候可以直接呼叫組建,並最後建立出Dialog物件。

通過Dialog原始碼的分析,我們能夠更好的瞭解Builder設計模式。

public class AlertDialog extends Dialog implements DialogInterface {
	private AlertController mAlert;
	
    AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);

        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = new AlertController(getContext(), this, getWindow());
    }
    
   @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }
    
    public static class Builder {
        public Builder(Context context, int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
        }
        
        public Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }
        
        public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            P.apply(dialog.mAlert);
            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);
            }
            return dialog;
        }
        
         public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}
複製程式碼

通過上面的程式碼我們可以看到,建立AlertDialog.Build的時候會建立一個AlertController.AlertParams物件,這個物件裡面封裝了所有的Dialog的屬性,並且在呼叫Builder的類似於setTitle方法的時候會將引數賦值給AlertParams,當所有的元件的屬性賦值好之後,就呼叫create()方法,這個方法裡面就建立出AlertDialog物件,並呼叫P.apply(dialog.mAlert)方法,將AlertDialog建構函式建立出來的AlertController物件傳遞給P:

public void apply(AlertController dialog) {
  if (mCustomTitleView != null) {
      dialog.setCustomTitle(mCustomTitleView);
  } else {
      if (mTitle != null) {
          dialog.setTitle(mTitle);
      }
      if (mIcon != null) {
          dialog.setIcon(mIcon);
      }
      if (mIconId != 0) {
          dialog.setIcon(mIconId);
      }
      if (mIconAttrId != 0) {
          dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
      }
  }
  // 程式碼省略
}
複製程式碼

在apply方法中,可以發現只是把AlertParams中的引數設定到AlertController中,當我們呼叫create方法的時候,就是講AlertDialog物件的元件組裝起來,當我呼叫show的時候就會呼叫dialog的show方法:

public void show() {
	// 如果已經在顯示狀態,return
   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;
   
   if (!mCreated) {
       dispatchOnCreate(null);
   }

   onStart();
	// 獲取DecorView
   mDecor = mWindow.getDecorView();
	// 獲取佈局引數
   WindowManager.LayoutParams l = mWindow.getAttributes();
   if ((l.softInputMode
           & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
       WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
       nl.copyFrom(l);
       nl.softInputMode |=
               WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
       l = nl;
   }
	// 將decorView新增到WindowManager中
   mWindowManager.addView(mDecor, l);
   mShowing = true;
	// 傳送一個現實Dialog的訊息
   sendShowMessage();
}
複製程式碼

在show方法中:

  1. 通過diapatchOnCreate方法,呼叫Dialog的onCreate方法,並最終呼叫installContent方法。

    public void installContent() {
       /* We use a custom title so never request a window title */
       mWindow.requestFeature(Window.FEATURE_NO_TITLE);
       // 設定視窗的檢視
       int contentView = selectContentView();
       mWindow.setContentView(contentView);
       setupView();
       setupDecor();
    }
    private int selectContentView() {
       if (mButtonPanelSideLayout == 0) {
           return mAlertDialogLayout;
       }
       if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
           return mButtonPanelSideLayout;
       }
       // TODO: use layout hint side for long messages/lists
       return mAlertDialogLayout;
    }
    複製程式碼

    而我們可以看到在呼叫selectContentView的時候,會去獲取mButtonPanelSideLayout檢視,並通過WindowManager的setContentView方法來將檢視載入。

    public AlertController(Context context, DialogInterface di, Window window) {
    
       final TypedArray a = context.obtainStyledAttributes(null,
               R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
    	// 獲取檢視
       mAlertDialogLayout = a.getResourceId(
               R.styleable.AlertDialog_layout, R.layout.alert_dialog);
       a.recycle();
    }
    複製程式碼

    再通過setupView方法初始化AlertDialog佈局中的各個部分

  2. 呼叫AlertDialog的onStart方法

  3. 最後將DecorView新增到WindowManager中

相關文章