Android弹窗的实现及相关bug

弹窗是APP用来与用户交互提醒的一种表现形式,就本人知道的实现方式有以下几种:
1)activity
2)fragment
3)popupwindow
4)dialog
5)DialogFragment
下面我们进行展开说明:
1、也许有人会问,activity如何实现弹窗的呢?其实可以的,只要你将activity设置为透明主题即可
2、fragment就不用说了,本来碎片的优点就是灵活可复用,实现弹窗不是问题,更何况后面要讲的DialogFragment本身就是继承Fragment的
3、popupwindow也没什么好说的,它与dialog最大的区别在于它是阻塞线程的,而dialog是非阻塞线程的
4、说起dialog,我们说一下有关的两个bug:

dialog的第一个bug:

WindowManager: android.view.WindowLeaked: Activity xxx.xxx.xxx.xxxActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{4ac945c4 V.E..... R.....ID 0,0-900,90} that was originally added here

所谓的“has leaked window”,就是窗口泄漏,我们都知道dialog是依赖于activity存在的,故创建dialog时的context必须是activity的context。而之所以会出现窗口泄露,一般情况是dialog正显示着,但activity却被销毁了,用代码重现大概是这样子的:

dialog.show();
finish();

换句话说,在activity被销毁前,没有调用dialog的dismiss方法,就会出现上面的窗口泄露bug
知道原因后,解决方案也非常简单:
1.主动销毁:在finish之前把dialog给dismiss掉就行了。
2.被动销毁:在activity的onDestroy中,或者根据自己项目具体情况,在activity生命周期覆写函数中把dialog 给dismiss掉。

dialog的第二个bug

java.lang.IllegalArgumentException: View=com.android.internal.policy.impl.PhoneWindow$DecorView{4b155550 V.E..... R.....I. 0,0-900,90} not attached to window manager

所谓“not attached to window manager”,就是说dialog没有可附加的窗口,一般情况是当activity被销毁后,调用dialog的show或dismiss方法就会出现该bug,用代码重现大概是这样子的:

finish();
handler.post(new Runnable() {
            @Override
            public void run() {
               dialog.dismiss();
            }
        },1000)

如上代码,activity销毁,1秒后调用dialog的dismiss方法,一般的业务场景为:使用线程进行网络请求后要关掉对话框,但此时activity已被销毁(手速很快的返回或其它原因退出当前activity),就会出现该bug。
同样知道原因后,解决方案也非常简单:
我们只需在调用dialog的show和dismiss的方法前,判断activity是否已经被销毁。为了便于使用,建议在创建dialog时直接覆盖show和dismiss两个方法即可,代码如下:

    Dialog dlg = new Dialog(activity){
            @Override
            public void show() {
                if(!activity.isFinishing())
                    super.show();
            }
            @Override
            public void dismiss() {
                if(!activity.isFinishing())
                   super.dismiss();
            }
        };

5、DialogFragment是在android 3.0时被引入的,有些人会奇怪:为什么还要引入这么一个东东呢?
其实嘛,上面dialog的两个bug可不是白讲的!没错,如果使用DialogFragment的话,完全不存在着上面那样的窗口泄露问题。
因为就如上面所说的,DialogFragment是继承于Fragment的,所以,它拥有Fragment的生命周期,由FragmentManager进行管理,故此,其明显比dialog有更大的好处,举个最简单的例子:一个activity上正显示着一个dialog,如果此时旋转了屏幕方向,activity重建后,dialog却消失了,并且会出现上面dialog的第一个bug——窗口泄露,而如果使用DialogFragment的话,则完全不受影响,activity重建后,dialog依然能够正常显示,这得益于DialogFragment拥有完整的生命周期。
下面来说说本人使用DialogFragment遇到的问题:
如果你将DialogFragment进行复用的话,当你多次调用其show方法的话,会出现如下异常:

java.lang.IllegalStateException: Fragment already added: LoadDialogFragment{4ad202d4 #0 loading}

正常来说,如果show和dismiss配对调用的话,是不会出现该问题的,但是,有时确实很难保证配对调用,保不齐哪里的异步就出现多次调用show呢?所以,该问题还是得解决的!
其实,从这个bug字面上来说,不就是说Fragment已经被添加了吗?那么在调用show之前,我们进行一下判断不是OK了吗?将代码改为如下:

  public void showLoading() {
        if (loadingDialog == null) {
            loadingDialog=new LoadDialogFragment();
        }
        if(!loadingDialog.isAdded()){
            loadingDialog.show(getSupportFragmentManager(),"loading");
        }
    }

可惜结果还是不行,两次调用 showLoading(),程序还是崩溃!
打了断点,发现第一次调用show方法后,mAdded这个变量依然是false,这就奇了怪啦!难道isAdded()这个API是摆设的吗?不可能!本着“存在即是合理”的原则,我再次查看了show方法的源码:

 public void show(FragmentManager manager, String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commit();
    }

如上源码,我们注意到最后一步事务提交: ft.commit(),其实这个commit并不是立即执行的, 它会被发送到主线程的任务队列当中去, 当主线程准备好执行它的时候执行。
也就是说,其实它是异步的,故此才导致mAdded变量刷新不及时,所以,解决方案为,使事务提交即时生效,只需要在commit之后加上executePendingTransactions(),这样就能将异步转为同步,故代码更改为:

  public void showLoading() {
        if (loadingDialog == null) {
            loadingDialog=new LoadDialogFragment();
        }
        if(!loadingDialog.isAdded()){
            loadingDialog.show(getSupportFragmentManager(),"loading");
            getSupportFragmentManager().executePendingTransactions();//即时生效
        }
    }

经试验,原因如上述所说,该方案有效!
后继续查看源码,无意中发现DialogFragment除了show之外,还有showNow这个API,源码如下:

  public void showNow(FragmentManager manager, String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.commitNow();
    }

其实区别也在最后一步:ft.commit();变成了 ft.commitNow();
经查资料得知:之前用executePendingTransactions()会将所有在队列中还有你当前提交的transaction都执行了, 而commitNow()将只会执行你当前要提交的transaction. 所以commitNow()可以避免你不小心执行了那些你可能并不想执行的transactions。
同时,考虑到当activity被销毁后,getSupportFragmentManager()会出现空指针异常,故最终将代码更改为如下:

  public void showLoading() {
        if (loadingDialog == null) { 
            loadingDialog=new LoadDialogFragment();
        }
        if(!loadingDialog.isAdded() && !isFinishing()){
            loadingDialog.showNow(getSupportFragmentManager(),"loading");
        }
    }

最后,如同官方所云:推荐使用DialogFragment来创建对话框,不推荐直接用Dialog创建对话框。
另外,如果弹窗是从底部出来的话,可考虑直接使用android.support.design.widget.BottomSheetDialogFragment哦,O(∩_∩)O

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

推荐阅读更多精彩内容