你可以这样复用 AlertDialog

AlertDialog复用探讨

作者:李旺成###

时间:2016年4月16日###


前一阵在重构公司项目时,看到项目中用到了很多 Dialog,当时就在想,这么多的 Dialog,那能不能复用一下?
这里先介绍我是如何复用 AlertDialog 的,关于自定义 Dialog 的复用,以后有机会在写一篇。

为什么要复用

这就好比问:为什么要使用单例?使用单例有什么好处?

好吧!啰嗦两句:单例可以节省不必要的内存开销,屏蔽对象创建的复杂性。

对!这里讨论为什么要复用 AlertDialog;就是借鉴了单例设计模式,为了减少对象的创建,节省内存的开销。Java 代码优化里面有一条很基本的 —— 尽量少创建对象。

注意:如果在你的应用当中很少使用到 AlertDialog,那么就可以不同考虑这个问题了。

关于 AlertDialog 的复用我觉得可以根据 AlertDialog 在 App 中实例的数量,分为两种情况:

  • 整个 App 中单例
  • 每个 Activity 中单例

下面就这两种情况分别展开介绍。

整个 App 中单例

也就是说在整个 App 中都是唯一的,先看看实现的效果:

App 全局唯一 AlertDialog(一)
App 全局唯一 AlertDialog(二)

如上图所示,在 MainActivity 与 SecondActivity 中所显示的这六个 AlertDialog 都是同一个实例。

简介

使用单例模式,那么就可以保证在整个项目(也就是 App)中都只存在一个实例对象。

我的实现思路是这样的,使用 Application 的 Activity 来创建 AlertDialog,然后使用单例模式,保证该 AlertDialog 的全局唯一性。

要解决的问题

简单实现

代码很简单,直接看看:

public class AppAlertDialogManager {

    private static AlertDialog sAlertDialog;

    public static AlertDialog displayOneBtnDialog(String title, String msg) {
        if (sAlertDialog != null) {
            sAlertDialog.setTitle(title);
            sAlertDialog.setMessage(msg);
        } else {
            sAlertDialog = new AlertDialog.Builder(BaseApp.getInstance())
                    .setTitle(title)
                    .setMessage(msg)
                    .setNegativeButton("取消", null)
                    .setPositiveButton("确定", null)
                    .create();
            sAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        }
        return sAlertDialog;
    }

}

如何使用:

private void showAppSingleAlertDialog(String title, String msg) {
    AlertDialog alertDialog = AppAlertDialogManager.displayOneBtnDialog(title, msg);
    mADAddressTV.setText(alertDialog.toString());
    Log.e(TAG, alertDialog.toString());
    alertDialog.show();
}

这里只列出简要代码,请不要计较说这不是单例(只是演示一下),很多场景都没有覆盖到,例如:要显示按钮数量不同的 AlertDialog 该如何处理。

注意:因为,我不打算推荐这种方式,所以,也就没有将这些情况在这里实现了,这些问题将会在下面的实现方案中给出解决方案。

每个 Activity 中单例

意思就是,如果使用了 AlertDialog,那么在该 Activity 中有且仅会有一个 AlertDialog 实例,看效果图:

MainActivity 中唯一 AlertDialog
SecondActivity 中唯一 AlertDialog

如上图所示,在 MainActivity 中所显示的三个 AlertDialog 与 SecondActivity 中所显示的三个 AlertDialog ,在各自的页面下都是同一个实例;但是对比这两个 Activity 中的 AlertDialog 的地址时,发现它们并不相同,也就是说,不同页面中的 AlertDialog 不是同一个实例。

简介

这并不是单例模式那种全局唯一,只保证在当前的 Activity 中只有一个实例,也就是在当前 Activity 中可以实现复用。

我的实现思路是这样的:

Activity 中 AlertDialog 复用实现流程

首先,在一个工具类(ActivityAlertDialogManager.java)中,使用静态变量保存 AlertDialog 的实例,这与单例中使用静态变量保存单例类实例的作用一致。

然后,在每次需要显示 AlertDialog 的时候,根据工具类中保存的 AlertDialog 实例是否为空,来决定是否要创建新的 AlertDialog。如果,AlertDialog 不为空,则需要判断 Activity 是否切换。

最后,如果 Activity 切换了,那么就需要创建新的 Builder,否则可以尝试复用 Builder,然后就可以显示了。

要解决的问题

1、Buidler 的复用
AlertDialog 是通过 Buidler 来创建的,那么也需要考虑 Builder 复用的问题。所以,我也使用静态变量保留了 Builder 的实例。

解决了 Builder 复用的问题就可以复用 AlertDialog?使用一个 Builder 对象的 show() 方法,显示的是同一个 AlertDialog?

先来看下 Builder.show() 方法:

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

坑!有没有,我当时只考虑了 Builder 的复用,然后,直接使用 Builder 实例 show() 出来。结果可想而知,每次显示的 AlertDialog 都是新的实例

2、AlertDialog Button 如何复用
AlertDialog 的 setTitle() 和 setMessage() 方法每次调用后都可以生效,但是 setButton() 方法却有点问题,设置过一遍之后,以后不管怎么设置都没有效果。

关于这个问题,在我尝试使用 AlertDialog 的 setButton() 方法没有起到想要的效果,以及没有找到办法修改已有 AlertDialog 的 Buidler (我当时以为多次调用 Builder 的 setXxxButton() 方法有用,所以尝试了一下)之后,我就没有再在这两个方法上找思路了。

setButton() 既然行不通,那好,AlertDialog 也是个 Dialog,那么,它应该也可以管理自己的 View。对,就是拿到 AlertDialog 的 Button,然后,给它们重新赋值,或者说修改其属性等。

3、static 变量引用 Activity
Android 编程中有一个很容易导致内存泄漏的问题就是使用 static 变量引用 Activity。static 变量的生命周期很长,使用 static 引用 Activity,很容易导致 Activity 无法释放。

这里采用的方法是在 Activity 的 onPause() 方法中,清空 ActivityAlertDialogManager 中对 Activity 的引用。

简单实现

这里给出了一个简易的实现,只是考虑的一个按钮和两个按钮 AlertDialog 复用时的一些处理。权当参考,以思路为主,看代码:

public class ActivityAlertDialogManager {

    //==========常量==========
    private static final String TAG = "ActivityADManager";

    //==========普通静态变量==========
    private static AlertDialog sAlertDialog;                        // 一个Activity下只产生一个AlertDialog实例
    private static AlertDialog.Builder sBuilder;                        // 一个Activity下只产生一个AlertDialog.Builder实例
    private static Activity sLastActivity = null;

    //==========AlertDialog==========//
    public static AlertDialog displayOneBtnDialog(@NonNull Activity Activity, String title, String msg) {
        if (TextUtils.isEmpty(msg)) return null;
        if (sAlertDialog == null) {
            TipInfo tipInfo = TipInfo.createTipInfo(title, msg);
            sAlertDialog = displayOneBtnDialog(Activity, tipInfo, null);
        } else {
            sAlertDialog.setTitle(title);
            sAlertDialog.setMessage(msg);
            DialogInterface.OnClickListener listener = null;
            sAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "取消", listener);
            sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
        }
        return sAlertDialog;
    }

    public static AlertDialog displayOneBtnDialog(@NonNull Activity Activity, TipInfo tipInfo, DialogInterface.OnClickListener sureListener) {
        if (tipInfo == null) return null;
        AlertDialog.Builder builder = getBuilder(Activity, tipInfo);
        builder = addAlertDialogPositiveButton(tipInfo.sureBtnText, sureListener, builder);
        // 显示出该对话框
        sAlertDialog = builder.create();
        DialogInterface.OnClickListener listener = null;
        sAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "", listener);
        if (sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) {
            sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
        }
        return sAlertDialog;
    }

    public static AlertDialog displayTwoBtnDialog(@NonNull Activity Activity, String title, String msg) {
        if (TextUtils.isEmpty(msg)) return null;
        if (sAlertDialog == null) {
            TipInfo tipInfo = TipInfo.createTipInfo(title, msg);
            sAlertDialog = displayTwoBtnDialog(Activity, tipInfo, null, null);
        } else {
            sAlertDialog.setTitle(title);
            sAlertDialog.setMessage(msg);
            if (sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) {
                sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setText("取消");
                sAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.VISIBLE);
            }
        }
        return sAlertDialog;
    }

    public static AlertDialog displayTwoBtnDialog(@NonNull Activity Activity, TipInfo tipInfo, DialogInterface.OnClickListener cancelListener, DialogInterface.OnClickListener sureListener) {
        if (tipInfo == null) return null;
        // 通过AlertDialog.Builder这个类来实例化我们的一个AlertDialog的对象
        AlertDialog.Builder builder = getBuilder(Activity, tipInfo);
        builder = addAlertDialogPositiveButton(tipInfo.sureBtnText, sureListener, builder);
        builder = addAlertDialogNegativeButton(tipInfo.cancelBtnText, cancelListener, builder);
        // 显示出该对话框
        sAlertDialog = builder.show();
        return sAlertDialog;
    }

    @NonNull
    private static AlertDialog.Builder getBuilder(@NonNull Activity Activity, TipInfo tipInfo) {
        AlertDialog.Builder builder;
        if (Activity == sLastActivity) {
            if (sBuilder != null) {
                builder = sBuilder;
            } else {
                builder = createNewBuilder(Activity);
            }
        } else {
            reset();
            builder = createNewBuilder(Activity);
            sLastActivity = Activity;
            sBuilder = builder;
        }
        // 通过AlertDialog.Builder这个类来实例化我们的一个AlertDialog的对象
        // 设置Title的图标
        builder.setIcon(tipInfo.iconResId);
        // 设置Title的内容
        builder.setTitle(tipInfo.title);
        // 设置Content来显示一个信息
        builder.setMessage(tipInfo.msg);
        return builder;
    }

    private static void reset() {
        sBuilder = null;
        sAlertDialog = null;
        sLastActivity = null;
    }

    @NonNull
    private static AlertDialog.Builder createNewBuilder(@NonNull Activity Activity) {
        AlertDialog.Builder builder;
        builder = new AlertDialog.Builder(Activity);
        sBuilder = builder;
        return builder;
    }

    private static AlertDialog.Builder addAlertDialogPositiveButton(String btnText, DialogInterface.OnClickListener listener, final AlertDialog.Builder builder) {
        listener = getDefaultOnClickListener(listener);
        // 设置一个PositiveButton
        builder.setPositiveButton(btnText, listener);
        return builder;
    }

    private static AlertDialog.Builder addAlertDialogNegativeButton(String btnText, DialogInterface.OnClickListener listener, final AlertDialog.Builder builder) {
        listener = getDefaultOnClickListener(listener);
        // 设置一个PositiveButton
        builder.setNegativeButton(btnText, listener);
        return builder;
    }

    @NonNull
    private static DialogInterface.OnClickListener getDefaultOnClickListener(DialogInterface.OnClickListener listener) {
        if (listener != null) return listener;
        listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.e(TAG, "AlertDialog Button Click!");
            }
        };
        return listener;
    }
    //==========AlertDialog==========//


    //==========逻辑方法==========//
    public static void destory(@NonNull Activity Activity) {
        if (Activity != sLastActivity) {
            Activity = null;
            return;
        }
        if (sAlertDialog != null) {
            sAlertDialog.cancel();
        }
        Activity = null;
        reset();
    }

}

思路在上面已经简单说过,而且代码里面基本都有注释,这里就不多做解释了。

小结

两种方式对比

上面说过,我建议使用“每个 Activity 中单例”的实现方案,但是“整个 App 中单例”也有其优势,这里简单介绍下:

使用 Application Activity 的优缺点

优点:整个 App 中都只有一个 AlertDialog 的实例,也就是单例的优点,最少的实例
缺点:
1、需要申请多余的权限,需要更改 Window 的 type,更坑的是显示与否是不确定的

在 MIUI 系统中,可以关闭 App 显示悬浮窗的权限,如果该权限被关闭了,那么你会发现,全局 AlertDialog 怎么都显示不出来,关键还不会报错。这里,我考虑过先确认是否打开了悬浮窗权限,然后给出相应提示,引导用户开启该权限。并不顺利,放弃了,这是不建议使用该方式的最主要原因。

MIUI 中设置悬浮窗权限

2、返回桌面“失效”
先看效果:

返回桌面失效

你可以试一下,展示全局对话框,然后点回到桌面按键,你会发现该对话框还存在,并没有和你的 App 一起隐藏。

当然,这个问题可以解决,监听回到桌面的广播,然后去处理一下就可以了;但是,这不是麻烦了不是。

使用 Activity Activity 的优缺点

优点:相比 Application Activity 而言,没有它的那些缺点
缺点:要注意避免 Activity 的内存泄漏

写在最后的话##

不足:这里主要是想探讨一下思路,代码很简略,肯定还有很多不足的地方,希望大家多提建议,集思广益。

就拿权限申请来说,其实有不需要权限的显示全局悬浮窗(注意这里没有说是 AlertDialog)的方案,有人分析过 UC 是如何不申请权限就可以实现展示悬浮窗的(),但是这不是这篇文章该讨论的了。

解决问题的思路:多思考,思维不要僵化;思路很重要,要是感觉行不通,不要一条路走到黑,相信条条大路通罗马。

项目地址

GitHub

附上动图

AlertDialog 复用演示
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容