[ApiDemos] AlertDialog 使用和源码分析

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

AlertDialogSamples 所在 ApiDemos 位置:

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

建造者模式

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

UML图:

[ApiDemos] AlertDialog 使用和源码分析_第1张图片

有设计者

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

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

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

原理分析

下图是调用过程图

[ApiDemos] AlertDialog 使用和源码分析_第2张图片

如图所示,上一层为 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)

你可能感兴趣的:(dialog,ApiDemos)