Builder设计模式和AlertDialog的源码分析

2022-07-20 15:12:04 浏览数 (1)

Builder模式是一步步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细的控制对象的构造流程。该模式是为了将构造复杂对象的过程和它的部件解耦,使得构建过程和部件的表示隔离开来。

栗子:

我们通过一个例子来引出Builder模式。假设有一个Person类,我们通过该Person类来构建一大批人,这个Person类里有很多 属性,最常见的比如name,age,weight,height等等,并且我们允许这些值不被设置,也就是允许为null,该类的定义如下:

代码语言:javascript复制
public class Person {
  private String name;
  private int age;
  private double height;
  private double weight;
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  public double getHeight() {
    return height;
  }
  public void setHeight(double height) {
    this.height = height;
  }
  public double getWeight() {
    return weight;
  }
  public void setWeight(double weight) {
    this.weight = weight;
  }
  public Person() {}
  public Person(String name) {
    this.name = name;
  }
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  public Person(String name, int age, double height) {
    this.name = name;
    this.age = age;
    this.height = height;
  }
  public Person(String name, int age, double height, double weight) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
  }
}

于是你就可以这样创建各个需要的对象:

代码语言:javascript复制
Person p1=new Person();
Person p2=new Person("张三");
Person p3=new Person("李四",18);
Person p4=new Person("王五",21,180);
Person p5=new Person("赵六",17,170,65.4);

可以想象一下这样创建的坏处,最直观的就是四个参数的构造方法的最后面的两个参数到底是什么意思,可读性不怎么好,还有一个问题就是当有很多参数时,编写这个构造函数就会显得异常麻烦,这时候便可以用Builder模式。

我们给Person增加一个 静态内部类Builder类,并修改Person类的构造函数,代码如下:

代码语言:javascript复制
public class Person {
  private String name;
  private int age;
  private double height;
  private double weight;
  privatePerson(Builder builder){
    this.name=builder.name;
    this.age=builder.age;
    this.height=builder.height;
    this.weight=builder.weight;
  }  
public String getName(){
    return name;
  }  
public void setName(String name){
    this.name = name;  
}  
public int getAge(){
    return age;
  }  
public void setAge(int age){
    this.age = age;
  }
public double getHeight(){
    return height;
  }  
public void setHeight(double height){
    this.height = height;
  }  
public double getWeight() {
    return weight;
  }  
public void setWeight(double weight){
    this.weight = weight;
  }  
  
static class Builder{
    private String name;
    private int age;
    private double height;
    private double weight;
    public Builder name(String name){
      this.name=name;
      return this;
    }
    public Builder age(int age){
      this.age=age;
      return this;
    }
    public Builder height(double height){
      this.height=height;
      return this;
    }
    public Builder weight(double weight){
      this.weight=weight;
      return this;
    }
    public Person build(){
      return new Person(this);
    }
  }
}

在Builder类里定义了一份与Person类一模一样的变量,通过一系列的成员函数进行设置属性值,但是返回值都是this,也就是都是Builder对象,最后提供了一个build函数用于创建Person对象,返回的是Person对象,对应的构造函数在Person类中进行定义,也就是构造函数的入参是Builder对象,然后依次对自己的成员变量进行赋值,对应的值都是Builder对象中的值。此外Builder类中的成员函数返回Builder对象自身的另一个作用就是让它支持链式调用,使代码可读性大大增强。 于是我们就可以这样创建Person类:

代码语言:javascript复制
Person.Builder builder=new Person.Builder();
Person person=builder
  .name("张三")
  .age(18)
  .height(178.5)
  .weight(67.4)
  .build();

最后总结一下

  • 定义一个静态内部类Builder,内部的成员变量和外部类一样
  • Builder类通过一系列的方法用于成员变量的赋值,并返回当前对象本身(this)
  • Builder类提供一个build方法或者create方法用于创建对应的外部类,该方法内部调用了外部类的一个私有构造函数,该构造函数的参数就是内部类Builder
  • 外部类提供一个私有构造函数供内部类调用,在该构造函数中完成成员变量的赋值,取值为Builder对象中对应的值

Android源码中Builder模式实现

在Android源码中,最常用到的Builder模式就是AlertDialog.Builder,使用该Builder来构造复杂的AlertDialog对象。

代码语言:javascript复制
AlertDialog.Builder builder = new AlertDialog.Builder(this);  

我们先来看一下AlertDialog的相关源码:

代码语言:javascript复制
public class AlertDialog extends Dialog implements DialogInterface {
    private AlertController mAlert;
    //构造函数
    protected AlertDialog(Context context, @StyleRes int themeResId) {
        this(context, themeResId, true);
    }
    AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);
        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = AlertController.create(getContext(), this, getWindow());
    }

    //实际上调用的是mAlert的setText方法
    @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }

    //实际上调用的是mAlert的setMessage方法
    public void setMessage(CharSequence message) {
        mAlert.setMessage(message);
    }
    ......
}

接下来看一下AlertDialog的内部类Builder的源码:

代码语言:javascript复制
public static class Builder {  
        private final AlertController.AlertParams P;  
        private final int mTheme;  
  
         
        public Builder(@NonNull Context context) {  
            this(context, resolveDialogTheme(context, 0));  
        }  
  
         
        public Builder(@NonNull Context context, @StyleRes int themeResId) {  
            P = new AlertController.AlertParams(new ContextThemeWrapper(  
                    context, resolveDialogTheme(context, themeResId)));  
            mTheme = themeResId;  
        }  
  
        @NonNull  
        public Context getContext() {  
            return P.mContext;  
        }  
  
        public Builder setTitle(@StringRes int titleId) {  
            P.mTitle = P.mContext.getText(titleId);  
            return this;  
        }  
  
        public Builder setTitle(CharSequence title) {  
            P.mTitle = title;  
            return this;  
        }  
  
       ........  
       
        public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {  
            P.mPositiveButtonText = P.mContext.getText(textId);  
            P.mPositiveButtonListener = listener;  
            return this;  
        }  
  
        public AlertDialog create() {  
             
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);  
            P.apply(dialog.mAlert);//这里调用了apply真正创建了需要显示的dialog,也就是说之前的设置都是以P做一个数据缓存  
            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;  
        }  
    }  

Builder类可以设置AlertDialog中的title、message、button等参数,这些参数存储在类型为AlertController.AlertParams的成员变量P中,AlertController.AlertParams中包含了AlertDialog视图中对应的成员变量。在调用Builder的creat函数时会创建AlertDialog,并且将Builder成员变量中P的参数应用到AlertDialog的mAlert对象中,即P.apply(dialog.mAlert)代码段,看看AlertParams类中apply方法的实现:

代码语言:javascript复制
public static class AlertParams {  
        public final Context mContext;  
        public final LayoutInflater mInflater; 
        public int mIconId = 0;  
        public Drawable mIcon;  
        public int mIconAttrId = 0;  
        public CharSequence mTitle;  
        public View mCustomTitleView;  
        public CharSequence mMessage;  
        public CharSequence mPositiveButtonText;  
      ........  
  
        public AlertParams(Context context) {  
            mContext = context;  
            mCancelable = true;  
            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
        }  
  
        public void apply(AlertController dialog) {//传入一个dialog,获取AlertParams缓存的数据。  
            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));  
                }  
            }  
            if (mMessage != null) {  
                dialog.setMessage(mMessage);  
            }  
            if (mPositiveButtonText != null) {  
                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,  
                        mPositiveButtonListener, null);  
            }  
            if (mNegativeButtonText != null) {  
                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,  
                        mNegativeButtonListener, null);  
            }  
            if (mNeutralButtonText != null) {  
                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,  
                        mNeutralButtonListener, null);  
            }  
            // For a list, the client can either supply an array of items or an  
            // adapter or a cursor  
            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {  
                createListView(dialog);  
            }  
            if (mView != null) {  
                if (mViewSpacingSpecified) {  
                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,  
                            mViewSpacingBottom);  
                } else {  
                    dialog.setView(mView);  
                }  
            } else if (mViewLayoutResId != 0) {  
                dialog.setView(mViewLayoutResId);  
            }  
  
            /* 
            dialog.setCancelable(mCancelable); 
            dialog.setOnCancelListener(mOnCancelListener); 
            if (mOnKeyListener != null) { 
                dialog.setOnKeyListener(mOnKeyListener); 
            } 
            */  
        }  
  
        ......  
    }  

在apply方法中,只是将AlertParams参数设置到AlertController中,我们来看一下AlertController类:

代码语言:javascript复制
class AlertController {  
    private final Context mContext;  
    final AppCompatDialog mDialog;  
    private final Window mWindow;  
  
    private CharSequence mTitle;  
    private CharSequence mMessage;  
    ListView mListView;  
    private View mView;  
  
    private int mViewLayoutResId;  
  
    private int mViewSpacingLeft;  
    private int mViewSpacingTop;  
    private int mViewSpacingRight;  
    private int mViewSpacingBottom;  
    private boolean mViewSpacingSpecified = false;  
  
    Button mButtonPositive;  
    private CharSequence mButtonPositiveText;  
    Message mButtonPositiveMessage;  
  
    Button mButtonNegative;  
    private CharSequence mButtonNegativeText;  
    Message mButtonNegativeMessage;  
  
    Button mButtonNeutral;  
    private CharSequence mButtonNeutralText;  
    Message mButtonNeutralMessage;  
  
    NestedScrollView mScrollView;  
  
    private int mIconId = 0;  
    private Drawable mIcon;  
  
    private ImageView mIconView;  
    private TextView mTitleView;  
    private TextView mMessageView;  
    private View mCustomTitleView;  
  
    ListAdapter mAdapter;  
  
    int mCheckedItem = -1;  
  
    private int mAlertDialogLayout;  
    private int mButtonPanelSideLayout;  
    int mListLayout;  
    int mMultiChoiceItemLayout;  
    int mSingleChoiceItemLayout;  
    int mListItemLayout;  
  
    private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE;  
  
    Handler mHandler;  
  
    ..........
}  

AlertController.AlertParams 持有AlertController的所有属性,在调用builder里的设置属性方法时,就是给AlertController.AlertParams做一个缓存。在调用了builder 的show方法之后。里面在调用具体dialog的show方法显示弹窗。

那么AlertDialog在建造者模式中担任的是指挥者,Bilder就是具体的建造者。采用了链式调用。AlertController是产品,而AlertController.AlertParams是产品的缓存。比如我调用了两次setTitle(),在缓存时后一次会覆盖前一次,这样就解决了开发者冲动调用的问题。最后不论是调用Builder的show方法,还是调用调用AlertDialog的show方法。都是允许的。

我们来看一下Dialog的show方法:

代码语言:javascript复制
    public void show() {
        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);
        } 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);
        }
        onStart();
        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();
        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;
        }
        mWindowManager.addView(mDecor, l);
        mShowing = true;
        sendShowMessage();
    }

在调用底层的show方法时,会先进行一次判断,第一次show之后mShowing已经设为true。那么第二次调用时,判断到已经显示,就不会再次调用绘制逻辑

在show方法中主要做了如下几件事:

(1)通过dispatchOnCreate函数来调用AlertDialog的onCreate函数

(2)然后调用AlertDialog的onStart函数

(3)最后将Dialog的DecorView的添加到WindowManager中

那么建造者模式就到这儿了,更详细的挖掘还得 去挖源码

0 人点赞