Android源码分析之Buinder模式

前言

接着上次讲到的Builder设计模式说起吧!Builder的主要作用,在我的理解上来看就是分离原料部件和组装过程,使部件组装自由化,高度解耦,内部返回自己的对象以形成链式调用,优化代码逻辑,在实例化对象过程中需要很多参数或者默认很多配置的情况下尤为突出。下面我们就来看一下Android源码中的Builder模式是怎么实现的吧!

Android源码AlertDialog的Builder设计模式

  • 在Android中,使用Builder模式最突出的就是Dialog了。每次我们在使用dialog时候,都要默认的设置很多参数,例如title,message,button等等。对于这样在实例化对象需要很多参数的情况下,Builder的设计优势便体现出来了。例如下面我们经常使用的实例化Dialog方式:

    private void showDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext());
        builder.setTitle("title")
                .setIcon(null)
                .setMessage("Message")
                .setView(null)
                .setPositiveButton("button",null)
                .setPositiveButton("button", null)
                .show();
    }
    
  • 上面展示了AlertDialog的实例过程,通过链式调用我们很方便并清晰的实例化了自己想要的Dialog,其内部含有多个参数需要配置,通过调用方法后返回自身对象,可以使得我们更加方便的进行链式调用,使得整个逻辑更加清晰。

  • 首先我们先来到源码的AlertDialog类中,AlertDialog继承Dialog实现了DialogInterface,具体代码如下:

    public class AlertDialog extends Dialog implements DialogInterface {
        //接受Builder成员变量P中的各个参数
        private AlertController mAlert;
        //省略部分
        //构造函数
        AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
            super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                    createContextThemeWrapper);
    
            mWindow.alwaysReadCloseOnTouchAttr();
            //构造AlertController实例对象mAlert
            mAlert = new AlertController(getContext(), this, getWindow());
        }
    }
        //AlertDialog实际上调用的是mAlert的setMessage方法
        public void setMessage(CharSequence message) {
            mAlert.setMessage(message);
        }
    

    上面可以看到,在实例化AlertDialog过程中,构造了一个mAler成员对象,它是AlertController中的对象,主要负责Dialo真正的参数和布局文件的设置工作,有点代理类的意味,这里我们就不做更深的讨论。

  • 在AlertDialog中,有一个Builder的静态内部类,如下:

    public static class Builder {
        //存储AlertDialog的各个参数,如title,mmessage,icon等
        private final AlertController.AlertParams P;
        //省略部分
        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 Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
            int viewSpacingRight, int viewSpacingBottom) {
        P.mView = view;
        P.mViewLayoutResId = 0;
        P.mViewSpacingSpecified = true;
        P.mViewSpacingLeft = viewSpacingLeft;
        P.mViewSpacingTop = viewSpacingTop;
        P.mViewSpacingRight = viewSpacingRight;
        P.mViewSpacingBottom = viewSpacingBottom;
        return this;
    }
    

上面在Builder实例化过程中,又通过AlertController中构建一个AlertController。AlertParams的P成员变量,这跟变量用来存储Dialog的相关参数,在下面的设置参数过程中,直接将参数通过P对象中的局部变量进行保存,相信的后面过程中,统一会从中抽取参数进行Dialog的布局设置工作。其次,在设置参数过程中,其默认直接返回本身,这样有利于我们在设置多个参数过程中方便的进行链式调用,使得程序更加简洁。

  • 下面我们来看一下AlertDialog中Bbuilder的OnCreate()方法,代码如下:

    public AlertDialog create() {
           // Context has already been wrapped with the appropriate theme.
           //4. 调用new AlertDialog构造对象,并且将参数传递给个体AlertDialog
           final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
           //5. 将P中参数应用到dialog的mAlert对象中
           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;
       }
    

    在这里,我们才看到真正的Dialog才被创建,其创建过程中,通过P(AlertController.AlertParams)这个变量获取获取之前用户设置的参数配置,通过P.apply()将配置统一配置到Dialog样式中去。

  • 对于P.apply()这个方法我们有必要来看一下其内部实现原理。其在AlertDialog的另一个重要的控制类AlertController中的静态类AlertParams中,如下:

    public void apply(AlertController dialog) {
            if (mCustomTitleView != null) {
                dialog.setCustomTitle(mCustomTitleView);
            } else {
                if (mTitle != null) {
                    dialog.setTitle(mTitle);
                }
                //省略
            }
            if (mMessage != null) {
                dialog.setMessage(mMessage);
            }
            if (mPositiveButtonText != null) {
                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                        mPositiveButtonListener, null);
            }
            //省略
            if (mView != null) {
                if (mViewSpacingSpecified) {
                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                            mViewSpacingBottom);
                } else {
                    dialog.setView(mView);
                }
            } else if (mViewLayoutResId != 0) {
                dialog.setView(mViewLayoutResId);
            }
        }
    

    上面的主要逻辑是将用户设置的AlertDialog的参数设置到真正的dialog中去,其中的各个参数一开始就被记录到AlertController中的AlertParams中去了。在AlertParams中包含众多AlertDialog的成员变量,记录用户设置的参数,用户设置的alertDialog的参数全部被记录在这里,部分代码如下:

    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 boolean mCancelable;
        public DialogInterface.OnKeyListener mOnKeyListener;
        public CharSequence[] mItems;
        public ListAdapter mAdapter;
        public DialogInterface.OnClickListener mOnClickListener;
        public int mViewLayoutResId;
        public View mView;
        //省略部分
    }
    
  • 在设置好Dialog的参数后,我们便调用show即可在界面显示设置的Dialog。我们先来看一下Dialo的shoew()方法:

    public AlertDialog show() {
        final AlertDialog dialog = create();
        dialog.show();
        return dialog;
    }
    

    具体的show()方法在Dialog中实现:

    public void show() {
        //显示状态,return
        if (mShowing) {
            //省略
            return;
        }
    
        mCanceled = false;
        //1. onCreate调用
        if (!mCreated) {
            dispatchOnCreate(null);
        }
    
        //2. onStart
        onStart();
        //3. 获取DecorView
        mDecor = mWindow.getDecorView();
    
        //省略
    
        //4. 获取布局参数
        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;
        }
    
        try {
            //5. 将mDecor添加到WindowManager中
            mWindowManager.addView(mDecor, l);
            mShowing = true;
            //6. 发送一个显示Dialog的消息
            sendShowMessage();
        } finally {
        }
    }
    

    在show()函数中主要做了一下几个事情:

  1. 通过dispatchOnCreate调用AlertDialog的onCreate方法
  2. 代用AlertDialog的onStart方法
  3. 将Dialog的DectorView添加到WindowManager中
  4. 发送消息,通过WindowManager显示当前的Dialog

以上几个方面调用了Dialog的一系列生命周期,完成Dialo的实例化过程,下面我们跟踪一下AlertDialog的onCreate方法(Dialog的oncreate是一个空实现):

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 调用AlertController的inatallContent方法
    mAlert.installContent();
}
  • 在AlertDialog的oncreate中,其父类Dialog的通过dispatchOnCreate将方法分发给子类自己,如下:
    public void installContent() {
        /* We use a custom title so never request a window title */
        //设置窗口,没有title
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        int contentView = selectContentView();
        //设置窗口的内容视图布局
        mWindow.setContentView(contentView);
        //初始化AlertDialog的其他子视图内容
        setupView();
        setupDecor();
    }
    
  • 在installContent中,设置AlertDialog的内容布局,这个布局的默认布局文件及布局样式在AlertDialog的构造函数中进行了初始化

    public AlertController(Context context, DialogInterface di, Window window) {
        mContext = context;
        mDialogInterface = di;
        mWindow = window;
        mHandler = new ButtonHandler(di);
    
        //获取布局样式和属性
        final TypedArray a = context.obtainStyledAttributes(null,
                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
    
        mAlertDialogLayout = a.getResourceId(
                R.styleable.AlertDialog_layout, R.layout.alert_dialog);
        mButtonPanelSideLayout = a.getResourceId(
                R.styleable.AlertDialog_buttonPanelSideLayout, 0);
        mListLayout = a.getResourceId(
                R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
        //省略
        a.recycle();
    }
    
  • 对于setupview()方法,我们进一步看看:

     private void setupView() {
        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
        //1. 初始化title区域
        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
        //2. 初始化内容区域
        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
        //3. 初始化button区域
        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
    
        // Install custom content before setting up the title or buttons so
        // that we can handle panel overrides.
        //4. 用户自定义视图区域
        final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
        setupCustomContent(customPanel);
    
        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
    
        // Resolve the correct panels and remove the defaults, if needed.
        //5. 如果用户自定义内容区域,替换默认区域内容为自定义
        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
    
        //6. 填充相应布局
        setupContent(contentPanel);
        setupButtons(buttonPanel);
        setupTitle(topPanel);
    
        //7. 处理显示与隐藏逻辑
        final boolean hasCustomPanel = customPanel != null
                && customPanel.getVisibility() != View.GONE;
        final boolean hasTopPanel = topPanel != null
                && topPanel.getVisibility() != View.GONE;
        final boolean hasButtonPanel = buttonPanel != null
                && buttonPanel.getVisibility() != View.GONE;
    
        //省略部分    
    }
    

    setupView()主要加载初始化AlertDialog布局文件中的各个部分。统一加载系统默认的Dialog布局文件和用户设置的布局文件,然后根据用户自定义的程度,优先设置自定义的布局方式及相关属性值,并将其设置到Dialog中去。

  • 此时,用户通过show方法后,WindoeManager便会将AlertDialog显示到界面上。

总结
Android源码的Builder设计模式在AlertDialog中使用最为频繁,通过AlertDialog我们可以这样理解:

  1. AlertDialog内部使用Builder设计模式,其内部类Builder每次构建返回自身,有利于链式调用,代码结构清晰
  2. AlertDialog的Builder掌握所有的Dialog参数配置工作,其具体配置由AlertController来实现
  3. AlertController是AlertDialog的重要实现类,用户配置的参数信息由内部静态类AlertParams来保存,其内部具体完成Dialog的装配工作
  4. Dialog的具体显示和消失逻辑由WindowManager来完成
坚持原创技术分享,您的支持将鼓励我继续创作!