关于dialog,PopupWindow,SoftInputBoard的弹出时机的问题

字数 294阅读 868

Android中Window有三大层次,
dialog,PopupWindow,SoftInputBoard这些都属于第三层次-不能独立存在,必须依附于其他window,
体现在代码上就是必须能够拿到activity的windowtoken,才能够成功弹出

也就是activity在windowmanager的管理期内,对应的回调api是onAttachedToWindow和onDetachedFromWindow,在这个期间,我们可以认为,activity的windowtoken不为null.
BadTokenException有两种情况,一种是比activity先弹,一种是activity结束了才弹

activity几个生命周期回调的间隔时间

测试方法内均没有耗时的计算,下面的日志显示,从oncreate开始算起,20ms后onstart,75ms后onResume,312ms后onAttachedToWindow. 实际数据大概就是这个数量级.
dialog调用show方法到真正显示在屏幕上,也是两三百毫秒这个数量级,和activity的显示一样,都涉及到windowmanager的binder远程调用.

activity生命周期回调时间.png
activity生命周期图

测试

https://github.com/hss01248/DialogUtil

crash oncreate
crash ondestory

to eliminate the crash:

Never declare/instantiate Dialogs as local variables.
Make all Dialogs instance variables of the Activity.
Override onDestroy and call if(dialog != null) dialog.dismiss();

在baseactivity中:

@Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        if(isFirstIn){
            onFirstAttach();
            isFirstIn = false;
        }else {
            onNotFirstAttach();
        }
    }

    /**
     * 非第一次onResume
     */
    protected void onNotFirstAttach() {}

    /**
     * 第一次onResume
     */
    protected  void onFirstAttach(){}

在 onFirstAttach()中弹出dialog和popupwindow,弹出时注意加上判定和try-catch:

@Override
    protected void onFirstAttach() {
        super.onFirstAttach();
    ...

                   if(SplashActivity.this.isFinishing() ){
                        return;
                    }
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                        if(SplashActivity.this.isDestroyed()){
                            return;
                        }
                    }

                    try {
                        languageSelectDialog.show();
                    }catch (Exception e){//show需要200-300ms,如果期间activity被finish了,也会抛BadTokenException
                        e.printStackTrace();
                    }

        
    }

在onDestory中结束dialog

@Override
    protected void onDestroy() {
        super.onDestroy();
        if(languageSelectDialog !=null && languageSelectDialog.isShowing()){
            languageSelectDialog.dismiss();
        }
    }

推荐阅读更多精彩内容