本文主要讲解 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 是什么,它是 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);
}
});
}
}
下图是调用过程图
如图所示,上一层为 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 这两个类是 AlertDialog 和核心,AlertDialog 的大部分功能都通过 AlertController 实现,而 AlertParams 包含了对话框所需的各种元素(Title、 Message、 CheckedItems、 是否多选、四个 Padding 、各种监听…)
由于这两个类是 Android 内部类,无法用 Eclipse 跟踪,所以用 source insight 打开,在 com.android.internal.app 包下。
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 的弱引用,以避免内存泄露。
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 以及各种解析资源。就不再一一列举。
不知是否记得 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 对象存放解析后的图片资源。
showDialog() 使用维护 Dialog , 我们可以借鉴,并用在自己的项目中。
如:
1. BaseActivity 中使用 SparseArray 维护 Activity 集合,以便统一退出。
2. BaseViewHolder 中,用 SparseArray 保存 findViewById 的结果
在开发过程中,我们尽量降低组件之间的耦合度,方便代码的维护和升级。(需要不断的积累、总结)
Java之建造者模式(Builder
Pattern)
设计模式之建造者模式(Builder)
设计模式总结篇系列:建造者模式(Builder)