Android进阶系列之源码分析AlertDialog建造者模式

 *本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布

建造者模式之前也写了一篇学习笔记,不过那只是很简单的运用,要去看源码,要去看源码还是得在撸一遍设计模式才行啊。不能怂就是干。

Android进阶系列之源码分析AlertDialog建造者模式_第1张图片

建造者模式,在于分工明确,一个抽象建造者,一个指挥者,一个具体的建造者,当然还需要具体的产品。那么我们以一个软件产品为例。技术主管就是抽象建造者,他和产品经理沟通,知道要做一个什么样的产品。而程序猿就是苦逼的体力劳动者,技术主管说咋做你就咋做。而指挥者就是公司的产品经理,负责和用户沟通,了解客户的需求。

那么基本的套路有了,开始撸一个简单的例子出来。

public class Product {
    public static final int ANDROID = 0;
    public static final int IOS = 1;

    private String appName;
    private String appFuction;
    private int appSystem;

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    public String getAppFuction() {
        return appFuction;
    }

    public void setAppFuction(String appFuction) {
        this.appFuction = appFuction;
    }

    public int getAppSystem() {
        return appSystem;
    }

    public void setAppSystem(int appSystem) {
        this.appSystem = appSystem;
    }
}
这是产品,有app名称,app的功能,和app的操作系统。在来一个技术主管

/**
 * 技术主管
 * Created by Administrator on 2016/12/29.
 */

public abstract class TechManager {
    public abstract TechManager setAppName(@NonNull String appName);
    public abstract TechManager setAppFuction(@NonNull String appFuction);
    public abstract TechManager setAppSystem(@AppSystem int appSystem);
    public abstract Product build();
}
在传入的系统时,我用了一个自定义注解,只能传入规定的参数,详情见: http://blog.csdn.net/sw5131899/article/details/53842362
/**
 *程序猿。IOS和ANDROID,后台都会的全栈选手
 * Created by Administrator on 2016/12/29.
 */

public class Progremer extends TechManager{

    private Product product;
    private InnerProduct innerProduct = new InnerProduct();

    @Override
    public TechManager setAppName(@NonNull String appName) {
        innerProduct.setAppName(appName);
        return this;
    }

    @Override
    public TechManager setAppFuction(@NonNull String appFuction) {
        innerProduct.setAppFuction(appFuction);
        return this;
    }

    @Override
    public TechManager setAppSystem(@AppSystem int appSystem) {
        innerProduct.setAppSystem(appSystem);
        return this;
    }

    private class InnerProduct{
        private String appName;
        private String appFuction;
        private int appSystem;

        public String getAppName() {
            return appName;
        }

        public void setAppName(String appName) {
            this.appName = appName;
        }

        public String getAppFuction() {
            return appFuction;
        }

        public void setAppFuction(String appFuction) {
            this.appFuction = appFuction;
        }

        public int getAppSystem() {
            return appSystem;
        }

        public void setAppSystem(int appSystem) {
            this.appSystem = appSystem;
        }
    }

    @Override
    public Product build() {
        product = new Product();
        product.setAppName(innerProduct.getAppName());
        product.setAppFuction(innerProduct.getAppFuction());
        product.setAppSystem(innerProduct.getAppSystem());
        return product;
    }
}
这里采用了一下建造者模式的变异,这样其实更有利于使用。至于为什么,待会再产品经理那使用就知道了。

/**
 * 产品经理
 * Created by Administrator on 2016/12/29.
 */

public class ProductManager {
    public static Product create(@AppSystem int system){
      return new Progremer().setAppSystem(system).setAppName("探探").setAppFuction("划一划,找妹子。").build();
    }
}
在这里,采用链式调用,非常方便。这也是Android在源码经常使用的一种模式。

在客户类里,只需要持有产品经理类之后,就可以得到产品了。

public class Client {
    public void main(String[] args){
        //客户:我需要一个可以摇一摇找妹子的软件
        //产品经理:分析得出那就做一个探探吧
        //技术主管:appName:探探  系统:ios,android 功能:摇一摇,找妹子
        Product android = ProductManager.create(Product.ANDROID);
        Product ios = ProductManager.create(Product.IOS);
    }
}
建造者模式也就这么多了。其实变异来的建造者可以只需要具体建造者,抽象的不要了。指挥者也可以不要了。

public class Client {
    public void main(String[] args){
        //客户:我需要一个可以摇一摇找妹子的软件
        //产品经理:分析得出那就做一个探探吧
        //技术主管:appName:探探  系统:ios,android 功能:摇一摇,找妹子
        Product android = ProductManager.create(Product.ANDROID);
        Product ios = ProductManager.create(Product.IOS);
        //程序猿觉得太累了,工资又少,干的最多。最后决定自己出去单干。
        Progremer niubiProgremer = new Progremer();
        
        Product androidBest = niubiProgremer.setAppName("探探").setAppSystem(Product.ANDROID).setAppFuction("摇一摇,找妹子").build();
        Product iosBest = niubiProgremer.setAppName("探探").setAppSystem(Product.IOS).setAppFuction("摇一摇,找妹子").build();
    }
}
到这里,有没有觉得眼熟啊,AlertDialog调用也是这样一大串,链式调度。非常方便。那么我开始撸AlertDialog的源码吧。

Android源码这里我给个百度云的连接,大家需要的自行下载。链接:http://pan.baidu.com/s/1nv8prJj 密码:5mfz

只用AlertDialog的时候,都知道。

 AlertDialog.Builder builder = new AlertDialog.Builder(this);
一看就知道创建AlertDialog时,需要通过内部类Builder来创建。那么我们来看一下Builder里有什么呢。AlertDailog就是建造者模式中指挥者的角色。

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创建了一个AlertController.AlertParams P;P是后面所有设置方法所调用的对象。那么我们再看看AlertController.AlertParams这个内部类有什么参数。

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 dialog。那么我们看看AlertController 是什么。

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方法。都是允许的。

比如:

 new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").show();


这样是可以的,最后调用的是Builder的show方法,在Builder的show方法中,调用了AlertDialog的create()得到缓存数据的AlertDialog进行显示。

new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").create().show();

这样也是可以的,手动调用create()方法,之后在调用AlertDialog的show方法进行显示。

那么这样呢?

new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").show().show();

第一个show是builder的show,里面创建了AlertDialog并且调用了AlertDialog的show方法。那么第二个show也是AlertDialog的show方法,会重复调用么?答案肯定是不可能的。看看Dialog的show方法源码、

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;
        }

       .....

        try {
            mWindowManager.addView(mDecor, l);
            mShowing = true;
    
            sendShowMessage();
        } finally {
        }
    }


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

那么建造者模式就到这儿了,源码的博大精深真是令人向往。


你可能感兴趣的:(Android,Android进阶系列)