[ApiDemos] AlertDialog 使用和源码分析

来源:互联网 发布:翟欣欣最新进展 知乎 编辑:程序博客网 时间:2024/06/08 05:54

本文主要讲解 ApiDemos 中的 AlertDialog 章,介绍 AlertDialog 的使用和部分方法实现原理。本文是 ApiDemos 分析系列的第一篇,也是笔者第一次尝试以自己的角度分析源码。

AlertDialogSamples 所在 ApiDemos 位置:

Java: src/com.example.android.apis/app/AlertDialogSamples.java
.
Xml: /res/any/layout/alert_dialog.xml

建造者模式

AlertDialog 最明显的特点就是使用了建造者模式,一般来说建造者模式适合于一个具有较多的零件(属性)的产品(对象)的创建过程。根据产品创建过程中零件的构造是否具有一致的先后顺序,可以将其分为”有设计者“ 和 ”无设计者“,两种形式。

UML图:

这里写图片描述

有设计者

在现实生活中,建造一个房子,但我们不知道怎么造,就要请负责总体设计的设计师和负责具体施工的工人,设计师只设计图纸、命令工人干活,不参与施工。工人负责具体细节(窗户、地板的构建)。最后,我们要从工人手中接过建造好的房子。

对建造者(工人)的规范:

package cn.house;public interface Builder {    /**     * 建造窗户     */    public void mkWindow();    /**     * 建造房屋     */    public void mkFloor();    /**     * 获取房间     */    public Room getRoom();}

实现了 Builder 接口的工人:

package cn.house;public class RoomBuilder implements Builder{    private Room room = new Room();    /** 具体创建窗户 */    public void mkWindow() {        Window window = new Window();        room.setWindow(window);    }    /** 具体创建地板 */    public void mkFloor() {        Floor floor = new Floor();        room.setFloor(floor);    }    /** 交付以创建好的房子 */    public Room getRoom() {        return room;    }}

设计师:

package cn.house;public class Designer {    /**     * 命令 Builder      * @param builder      */    public void command(Builder builder){        // 建造房屋        builder.mkWindow();        // 建造地板        builder.mkFloor();    }}

测试用例:

public static void main(String[] args) {        Builder builder = new RoomBuilder();        Designer design = new Designer();        design.command(builder);        Room room = builder.getRoom();        Window window = room.getWindow();        Floor floor = room.getFloor();        System.out.println(window);        System.out.println(floor);    }

无设计者

今天的主角 AlertDialog 就属于无设计者 的形式,下面是 AlertDialog 的简单模式:

package cn;public class AlertDialog {    private String title;    private String message;    private int buttonCount;    private AlertDialog() {        // empty    }    /** 获取标题 */    public String getTitle() {        return title;    }    /** 获取信息 */    public String getMessage() {        return message;    }    /** 获取按钮数 */    public int getButtonCount() {        return buttonCount;    }    /** 显示 */    public void show(){        System.out.println("show");    }    /** 建造者 */    public static class Builder{        private AlertDialog entity = new AlertDialog();        public Builder(boolean isContext){            if (!isContext){                throw new RuntimeException("必须有上下文");            }        }        /** 设置标题 */        public Builder setTitle(String title) {            entity.title = title;            return this;        }        /** 设置内容 */        public Builder setMessage(String message) {            entity.message = message;            return this;        }        /** 设置按钮数 */        public Builder setButtonCount(int buttonCount) {            entity.buttonCount = buttonCount;            return this;        }        /** 交付结果 */        public AlertDialog build(){            return entity;        }    }}

测试用例:

    public static void main(String[] args) {        new AlertDialog.Builder(true)            .setTitle("Title")            .setMessage("Message")            .setButtonCount(2)            .build()            .show();}

可以看出,AlertDialog 直接命令 Builder ,并没有涉及到 Designer,所以它是无序的。

showDialog 显示对话框的原理

showDialog 是什么,它是 Activity 提供的显示 Dialog 的简便方法,封装在 Activity 中。showDialog() 方法 Api level 1 中被添加, Api level 13(Honeycomb 3.0)被废弃。

我们可以推测:
1. AlertDialog 是 Activity 的一部分
2. Android 代码逐渐朝着低耦合发展

简单用法

public class MainActivity extends Activity {    protected static final int DIALOG_YES_NO_MESSAGE = 0;    @Override    @Deprecated    protected Dialog onCreateDialog(int id) {        switch (id) {         case DIALOG_YES_NO_MESSAGE:             return new AlertDialog.Builder(MainActivity.this)                 .setIconAttribute(android.R.attr.alertDialogIcon)                 .setTitle("标题")                 .setPositiveButton("OK", new DialogInterface.OnClickListener() {                     public void onClick(DialogInterface dialog, int whichButton) {                         /* User clicked OK so do some stuff */                     }                 })                 .setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {                     public void onClick(DialogInterface dialog, int whichButton) {                         /* User clicked Cancel so do some stuff */                     }                 })                 .create();        default:            return null;        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.btn_open_dialog).setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                showDialog(DIALOG_YES_NO_MESSAGE);            }        });    }}

原理分析

下图是调用过程图

showDialog 调用过程

如图所示,上一层为 Activity,下一层为自己定义的 Activity 的派生类。

其中:
第1步:给按钮设置点击事件,开始执行 showDialog(id) 方法
第3步:showDialog(id, args) 方法维护了一个 mManagedDialogs 缓存池。

 public final boolean showDialog(int id, Bundle args) {        if (mManagedDialogs == null) {            mManagedDialogs = new SparseArray<ManagedDialog>();        }        ManagedDialog md = mManagedDialogs.get(id);        if (md == null) {            md = new ManagedDialog();            md.mDialog = createDialog(id, null, args);            if (md.mDialog == null) {                return false;            }            mManagedDialogs.put(id, md);        }        md.mArgs = args;        onPrepareDialog(id, md.mDialog, args);        md.mDialog.show();        return true;    }

每一个 Activity 都会创建一个 Dialog 缓存池,同一个 Activity 只创建一份不同 ID 类型的 Dialog,目的是提高效率。其中 onPrepareDialog(id, md.mDialog, args) 的作用是绑定 Dialog和Activity 。

也就是说,AndroidSDK 封装了 Dialog 对象的 维护 ,把 创建使用 这两步交给使用者去实现。只有当使用者实现了这两部,整个流程才算完结。这无疑是高度耦合的。所以在高版本,这种做法已经被废弃。

AlertController 和 AlertParams

AlertController 和 AlertParams 这两个类是 AlertDialog 和核心,AlertDialog 的大部分功能都通过 AlertController 实现,而 AlertParams 包含了对话框所需的各种元素(Title、 Message、 CheckedItems、 是否多选、四个 Padding 、各种监听…)

由于这两个类是 Android 内部类,无法用 Eclipse 跟踪,所以用 source insight 打开,在 com.android.internal.app 包下。

按钮事件处理器: ButtonHandler

 private static final class ButtonHandler extends Handler {        // Button clicks have Message.what as the BUTTON{1,2,3} constant        private static final int MSG_DISMISS_DIALOG = 1;        // 静态内部类使用外部成员变量,一般使用弱引用        private WeakReference<DialogInterface> mDialog;        public ButtonHandler(DialogInterface dialog) {            mDialog = new WeakReference<DialogInterface>(dialog);        }        // 处理各种按钮事件(确定,取消),销毁        @Override        public void handleMessage(Message msg) {            switch (msg.what) {                case DialogInterface.BUTTON_POSITIVE:                case DialogInterface.BUTTON_NEGATIVE:                case DialogInterface.BUTTON_NEUTRAL:                    ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);                    break;                case MSG_DISMISS_DIALOG:                    ((DialogInterface) msg.obj).dismiss();            }        }    }

以上可以看出:
1. Dialog 内部通过 Handler+Message 实现了事件的处理,
2. 内部类 ButtonHandler 持有外部对象成员 mDialog 的弱引用,以避免内存泄露。

AlertParams 设置 View 和 Title、Message 过程

      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));                }            }            if (mMessage != null) {                dialog.setMessage(mMessage);            }            // ......            if (mView != null) {                if (mViewSpacingSpecified) {                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,                            mViewSpacingBottom);                } else {                    dialog.setView(mView);                }            }        }

不论设置 Title 还是 Content 之前,都判断是否存在 CustomXXView (自定义的 View ),没有自定义 View 才进步设置 Title 、Message

其他方法均为设置背景、设置 ICON 以及各种解析资源。就不再一一列举。

setIconAttr 的原理分析

不知是否记得 AlertDialog 提供了 setIconAttr 方法方便设置对话框的图标。

示例代码如下:

protected Dialog onCreateDialog(int id) {        switch (id) {         case DIALOG_YES_NO_MESSAGE:             return new AlertDialog.Builder(MainActivity.this)                 .setIconAttribute(android.R.attr.alertDialogIcon)                 .setTitle("标题")                 .setPositiveButton("OK", new DialogInterface.OnClickListener() {                     public void onClick(DialogInterface dialog, int whichButton) {                         /* User clicked OK so do some stuff */                     }                 })                 .setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {                     public void onClick(DialogInterface dialog, int whichButton) {                         /* User clicked Cancel so do some stuff */                     }                 })                 .create();        default:            return null;        }    }

Eclipse 中按住 Ctrl+左键 跟踪后发现,该属性定义在:

SDK_DIR/platforms/android-19/data/res/values/attrs.xml

内容为:

<attr name=”alertDialogIcon” format=”reference” />

表示该属性指向的内容为引用类型,可以是:图片、文本、XML 文件

跟踪后可看到:

public Builder setIconAttribute(int attrId) {    TypedValue out = new TypedValue();    P.mContext.getTheme().resolveAttribute(attrId, out, true);    P.mIconId = out.resourceId;    return this;}

P.mContext 指向 Activity 实例

以上代码可知:
1. 该 AttrsID 和 Android 系统当前 Theme 下的图片资源存在映射关系。
2. ContextThemeWrapper 父类负责根据 AttrsID 找到当前 Theme 中对应的 ICON。
3. TypedValue 对象存放解析后的图片资源。

技巧总结:

SparseArray

showDialog() 使用维护 Dialog , 我们可以借鉴,并用在自己的项目中。
如:
1. BaseActivity 中使用 SparseArray 维护 Activity 集合,以便统一退出。
2. BaseViewHolder 中,用 SparseArray 保存 findViewById 的结果

低耦合的思想

在开发过程中,我们尽量降低组件之间的耦合度,方便代码的维护和升级。(需要不断的积累、总结)

参考

  • Java之建造者模式(Builder
    Pattern)

  • 设计模式之建造者模式(Builder)

  • 设计模式总结篇系列:建造者模式(Builder)

0 0
原创粉丝点击