你可以這樣復(fù)用 AlertDialog

AlertDialog復(fù)用探討

作者:李旺成###

時(shí)間:2016年4月16日###


前一陣在重構(gòu)公司項(xiàng)目時(shí),看到項(xiàng)目中用到了很多 Dialog,當(dāng)時(shí)就在想,這么多的 Dialog,那能不能復(fù)用一下?
這里先介紹我是如何復(fù)用 AlertDialog 的,關(guān)于自定義 Dialog 的復(fù)用,以后有機(jī)會(huì)在寫(xiě)一篇。

為什么要復(fù)用

這就好比問(wèn):為什么要使用單例?使用單例有什么好處?

好吧!啰嗦兩句:?jiǎn)卫梢怨?jié)省不必要的內(nèi)存開(kāi)銷(xiāo),屏蔽對(duì)象創(chuàng)建的復(fù)雜性。

對(duì)!這里討論為什么要復(fù)用 AlertDialog;就是借鑒了單例設(shè)計(jì)模式,為了減少對(duì)象的創(chuàng)建,節(jié)省內(nèi)存的開(kāi)銷(xiāo)。Java 代碼優(yōu)化里面有一條很基本的 —— 盡量少創(chuàng)建對(duì)象。

注意:如果在你的應(yīng)用當(dāng)中很少使用到 AlertDialog,那么就可以不同考慮這個(gè)問(wèn)題了。

關(guān)于 AlertDialog 的復(fù)用我覺(jué)得可以根據(jù) AlertDialog 在 App 中實(shí)例的數(shù)量,分為兩種情況:

  • 整個(gè) App 中單例
  • 每個(gè) Activity 中單例

下面就這兩種情況分別展開(kāi)介紹。

整個(gè) App 中單例

也就是說(shuō)在整個(gè) App 中都是唯一的,先看看實(shí)現(xiàn)的效果:

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

如上圖所示,在 MainActivity 與 SecondActivity 中所顯示的這六個(gè) AlertDialog 都是同一個(gè)實(shí)例。

簡(jiǎn)介

使用單例模式,那么就可以保證在整個(gè)項(xiàng)目(也就是 App)中都只存在一個(gè)實(shí)例對(duì)象。

我的實(shí)現(xiàn)思路是這樣的,使用 Application 的 Activity 來(lái)創(chuàng)建 AlertDialog,然后使用單例模式,保證該 AlertDialog 的全局唯一性。

要解決的問(wèn)題

簡(jiǎn)單實(shí)現(xiàn)

代碼很簡(jiǎn)單,直接看看:

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

這里只列出簡(jiǎn)要代碼,請(qǐng)不要計(jì)較說(shuō)這不是單例(只是演示一下),很多場(chǎng)景都沒(méi)有覆蓋到,例如:要顯示按鈕數(shù)量不同的 AlertDialog 該如何處理。

注意:因?yàn)?,我不打算推薦這種方式,所以,也就沒(méi)有將這些情況在這里實(shí)現(xiàn)了,這些問(wèn)題將會(huì)在下面的實(shí)現(xiàn)方案中給出解決方案。

每個(gè) Activity 中單例

意思就是,如果使用了 AlertDialog,那么在該 Activity 中有且僅會(huì)有一個(gè) AlertDialog 實(shí)例,看效果圖:

MainActivity 中唯一 AlertDialog
SecondActivity 中唯一 AlertDialog

如上圖所示,在 MainActivity 中所顯示的三個(gè) AlertDialog 與 SecondActivity 中所顯示的三個(gè) AlertDialog ,在各自的頁(yè)面下都是同一個(gè)實(shí)例;但是對(duì)比這兩個(gè) Activity 中的 AlertDialog 的地址時(shí),發(fā)現(xiàn)它們并不相同,也就是說(shuō),不同頁(yè)面中的 AlertDialog 不是同一個(gè)實(shí)例。

簡(jiǎn)介

這并不是單例模式那種全局唯一,只保證在當(dāng)前的 Activity 中只有一個(gè)實(shí)例,也就是在當(dāng)前 Activity 中可以實(shí)現(xiàn)復(fù)用。

我的實(shí)現(xiàn)思路是這樣的:

Activity 中 AlertDialog 復(fù)用實(shí)現(xiàn)流程

首先,在一個(gè)工具類(lèi)(ActivityAlertDialogManager.java)中,使用靜態(tài)變量保存 AlertDialog 的實(shí)例,這與單例中使用靜態(tài)變量保存單例類(lèi)實(shí)例的作用一致。

然后,在每次需要顯示 AlertDialog 的時(shí)候,根據(jù)工具類(lèi)中保存的 AlertDialog 實(shí)例是否為空,來(lái)決定是否要?jiǎng)?chuàng)建新的 AlertDialog。如果,AlertDialog 不為空,則需要判斷 Activity 是否切換。

最后,如果 Activity 切換了,那么就需要?jiǎng)?chuàng)建新的 Builder,否則可以嘗試復(fù)用 Builder,然后就可以顯示了。

要解決的問(wèn)題

1、Buidler 的復(fù)用
AlertDialog 是通過(guò) Buidler 來(lái)創(chuàng)建的,那么也需要考慮 Builder 復(fù)用的問(wèn)題。所以,我也使用靜態(tài)變量保留了 Builder 的實(shí)例。

解決了 Builder 復(fù)用的問(wèn)題就可以復(fù)用 AlertDialog?使用一個(gè) Builder 對(duì)象的 show() 方法,顯示的是同一個(gè) AlertDialog?

先來(lái)看下 Builder.show() 方法:

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

坑!有沒(méi)有,我當(dāng)時(shí)只考慮了 Builder 的復(fù)用,然后,直接使用 Builder 實(shí)例 show() 出來(lái)。結(jié)果可想而知,每次顯示的 AlertDialog 都是新的實(shí)例。

2、AlertDialog Button 如何復(fù)用
AlertDialog 的 setTitle() 和 setMessage() 方法每次調(diào)用后都可以生效,但是 setButton() 方法卻有點(diǎn)問(wèn)題,設(shè)置過(guò)一遍之后,以后不管怎么設(shè)置都沒(méi)有效果。

關(guān)于這個(gè)問(wèn)題,在我嘗試使用 AlertDialog 的 setButton() 方法沒(méi)有起到想要的效果,以及沒(méi)有找到辦法修改已有 AlertDialog 的 Buidler (我當(dāng)時(shí)以為多次調(diào)用 Builder 的 setXxxButton() 方法有用,所以嘗試了一下)之后,我就沒(méi)有再在這兩個(gè)方法上找思路了。

setButton() 既然行不通,那好,AlertDialog 也是個(gè) Dialog,那么,它應(yīng)該也可以管理自己的 View。對(duì),就是拿到 AlertDialog 的 Button,然后,給它們重新賦值,或者說(shuō)修改其屬性等。

3、static 變量引用 Activity
Android 編程中有一個(gè)很容易導(dǎo)致內(nèi)存泄漏的問(wèn)題就是使用 static 變量引用 Activity。static 變量的生命周期很長(zhǎng),使用 static 引用 Activity,很容易導(dǎo)致 Activity 無(wú)法釋放。

這里采用的方法是在 Activity 的 onPause() 方法中,清空 ActivityAlertDialogManager 中對(duì) Activity 的引用。

簡(jiǎn)單實(shí)現(xiàn)

這里給出了一個(gè)簡(jiǎn)易的實(shí)現(xiàn),只是考慮的一個(gè)按鈕和兩個(gè)按鈕 AlertDialog 復(fù)用時(shí)的一些處理。權(quán)當(dāng)參考,以思路為主,看代碼:

public class ActivityAlertDialogManager {

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

    //==========普通靜態(tài)變量==========
    private static AlertDialog sAlertDialog;                        // 一個(gè)Activity下只產(chǎn)生一個(gè)AlertDialog實(shí)例
    private static AlertDialog.Builder sBuilder;                        // 一個(gè)Activity下只產(chǎn)生一個(gè)AlertDialog.Builder實(shí)例
    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);
        // 顯示出該對(duì)話框
        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;
        // 通過(guò)AlertDialog.Builder這個(gè)類(lèi)來(lái)實(shí)例化我們的一個(gè)AlertDialog的對(duì)象
        AlertDialog.Builder builder = getBuilder(Activity, tipInfo);
        builder = addAlertDialogPositiveButton(tipInfo.sureBtnText, sureListener, builder);
        builder = addAlertDialogNegativeButton(tipInfo.cancelBtnText, cancelListener, builder);
        // 顯示出該對(duì)話框
        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;
        }
        // 通過(guò)AlertDialog.Builder這個(gè)類(lèi)來(lái)實(shí)例化我們的一個(gè)AlertDialog的對(duì)象
        // 設(shè)置Title的圖標(biāo)
        builder.setIcon(tipInfo.iconResId);
        // 設(shè)置Title的內(nèi)容
        builder.setTitle(tipInfo.title);
        // 設(shè)置Content來(lái)顯示一個(gè)信息
        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);
        // 設(shè)置一個(gè)PositiveButton
        builder.setPositiveButton(btnText, listener);
        return builder;
    }

    private static AlertDialog.Builder addAlertDialogNegativeButton(String btnText, DialogInterface.OnClickListener listener, final AlertDialog.Builder builder) {
        listener = getDefaultOnClickListener(listener);
        // 設(shè)置一個(gè)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();
    }

}

思路在上面已經(jīng)簡(jiǎn)單說(shuō)過(guò),而且代碼里面基本都有注釋?zhuān)@里就不多做解釋了。

小結(jié)

兩種方式對(duì)比

上面說(shuō)過(guò),我建議使用“每個(gè) Activity 中單例”的實(shí)現(xiàn)方案,但是“整個(gè) App 中單例”也有其優(yōu)勢(shì),這里簡(jiǎn)單介紹下:

使用 Application Activity 的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):整個(gè) App 中都只有一個(gè) AlertDialog 的實(shí)例,也就是單例的優(yōu)點(diǎn),最少的實(shí)例
缺點(diǎn):
1、需要申請(qǐng)多余的權(quán)限,需要更改 Window 的 type,更坑的是顯示與否是不確定的

在 MIUI 系統(tǒng)中,可以關(guān)閉 App 顯示懸浮窗的權(quán)限,如果該權(quán)限被關(guān)閉了,那么你會(huì)發(fā)現(xiàn),全局 AlertDialog 怎么都顯示不出來(lái),關(guān)鍵還不會(huì)報(bào)錯(cuò)。這里,我考慮過(guò)先確認(rèn)是否打開(kāi)了懸浮窗權(quán)限,然后給出相應(yīng)提示,引導(dǎo)用戶開(kāi)啟該權(quán)限。并不順利,放棄了,這是不建議使用該方式的最主要原因。

MIUI 中設(shè)置懸浮窗權(quán)限

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

返回桌面失效

你可以試一下,展示全局對(duì)話框,然后點(diǎn)回到桌面按鍵,你會(huì)發(fā)現(xiàn)該對(duì)話框還存在,并沒(méi)有和你的 App 一起隱藏。

當(dāng)然,這個(gè)問(wèn)題可以解決,監(jiān)聽(tīng)回到桌面的廣播,然后去處理一下就可以了;但是,這不是麻煩了不是。

使用 Activity Activity 的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):相比 Application Activity 而言,沒(méi)有它的那些缺點(diǎn)
缺點(diǎn):要注意避免 Activity 的內(nèi)存泄漏

寫(xiě)在最后的話##

不足:這里主要是想探討一下思路,代碼很簡(jiǎn)略,肯定還有很多不足的地方,希望大家多提建議,集思廣益。

就拿權(quán)限申請(qǐng)來(lái)說(shuō),其實(shí)有不需要權(quán)限的顯示全局懸浮窗(注意這里沒(méi)有說(shuō)是 AlertDialog)的方案,有人分析過(guò) UC 是如何不申請(qǐng)權(quán)限就可以實(shí)現(xiàn)展示懸浮窗的(),但是這不是這篇文章該討論的了。

解決問(wèn)題的思路:多思考,思維不要僵化;思路很重要,要是感覺(jué)行不通,不要一條路走到黑,相信條條大路通羅馬。

項(xiàng)目地址

GitHub

附上動(dòng)圖

AlertDialog 復(fù)用演示
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,001評(píng)論 25 709
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,545評(píng)論 19 139
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 7,323評(píng)論 0 17
  • 回鄉(xiāng)下帶回的凳子,很是喜歡??
    Alina姐閱讀 184評(píng)論 0 0
  • 1.) 打開(kāi)你的Xcode工程. 在Supporting Files目錄下,選擇 File > New > Fil...
    iOSZHU閱讀 531評(píng)論 0 1

友情鏈接更多精彩內(nèi)容