Android问题集(4):Unable to add window -- token android.os.BinderProxy@bf4921f is not valid;

问题描述

公司某个产品有反馈,说是在进入某个界面时容易引发崩溃,要到了崩溃日志后发现,确实爆出了一个异常,非必现,偶发bug,一看就大概明白是啥原因了,自己就写了个demo,复现了这个问题,下面是报错日志

E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: com.wisely, PID: 12063
                  android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@bf4921f is not valid; is your activity running?
                      at android.view.ViewRootImpl.setView(ViewRootImpl.java:696)
                      at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:347)
                      at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
                      at android.app.Dialog.show(Dialog.java:319)
                      at com.wisely.DialogUtil.showProgressDialog(DialogUtil.java:54)
                      at com.wisely.DialogUtil.showProgressDialog(DialogUtil.java:16)
                      at com.wisely.activity.bug.WindowDestroyBugActivity$1.handleMessage(WindowDestroyBugActivity.java:29)
                      at android.os.Handler.dispatchMessage(Handler.java:102)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.app.ActivityThread.main(ActivityThread.java:6114)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:874)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:764)

复现bug的代码

xml比较简单,未列出,当前Activity继承了一个自己写的BaseActivity,其实什么都没做,而且我本身也不建议这种抽取,不过因为这是我的demo集,场景特殊,所以才抽取了这个BaseActivity。
注意,下面的代码是有问题的代码,切勿复制粘贴

public class WindowDestroyBugActivity extends BaseActivity {

    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case 1:
                    DialogUtil.showProgressDialog(WindowDestroyBugActivity.this, "弹窗喽");
                    break;

            }

        }
    };

    /**
     * 点击事件的回调,在xml中写的
     */
    public void onClick(View v) {

        switch (v.getId()) {

            case R.id.tv_unable_to_add_window_finish:
                finish();
                mHandler.sendEmptyMessageDelayed(1, 3000);
                break;
        }
    }

    @Override
    public int getLayoutResource() {
        return R.layout.activity_unable_to_add_window;
    }

    @Override
    public String getTitleContent() {
        return "Unable to add window——token android.os.BinderProxy@2ed5fc06 is not valid;is your activity running?";
    }


}
/**
 * @author wisely
 */

public class DialogUtil {

    public static void showProgressDialog(Activity activity, String message){
        showProgressDialog(activity,message,false);
    }


    private static WeakReference mWeakReference;
    private static ProgressDialog mProgressDialog;

    /**
     * @param activity 需要弹窗的activity
     * @param message 弹窗展示的内容
     * @param flag 触摸弹窗外区域,是否取消窗口
     * */
    public static void showProgressDialog(Activity activity,String message,boolean flag){

        if(!isLiving(activity)){
            return;
        }

        if(mWeakReference == null){
            mWeakReference = new WeakReference(activity);
        }

        activity = mWeakReference.get();

        if (mProgressDialog == null) {
            if (activity.getParent() != null) {
                mProgressDialog = new ProgressDialog(activity.getParent());
            } else {
                mProgressDialog = new ProgressDialog(activity);
            }
        }

        if (!mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
            mProgressDialog.setMessage(message);
            mProgressDialog.setIndeterminate(false);
            mProgressDialog.setCancelable(flag);
            mProgressDialog.setIcon(R.mipmap.ic_launcher);
            mProgressDialog.show();
        } else {
            mProgressDialog.setMessage(message);
        }
    }

    /**
     * 判断activity是否存活
     * */
    private static boolean isLiving(Activity activity){

        if(activity == null){
            Log.d("wisely","activity == null");
            return false;
        }

        return true;
    }
}

问题解决

造成上面崩溃的原因很简单,就是因为在要show dialog的时候,忽然发现,dialog依附的activity已经挂掉了,于是程序崩溃。
知道了原因,解决就容易了。
我们只需要将上面DialogUtil中的isLiving方法修改一下,如下

 private static boolean isLiving(Activity activity) {

        if (activity == null) {
            Log.d("wisely", "activity == null");
            return false;
        }

        if (activity.isFinishing()) {
            Log.d("wisely", "activity is finishing");
            return false;
        }

//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {//android 4.2
//
//            if (activity.isDestroyed()) {
//                Log.d("wisely", "activity is destroy");
//                return false;
//            }
//        }

        return true;
    }

上面的代码很容易理解,注意我注释掉的代码,里面有一个方法,isDestroyed(),它是从4.2版本开始新增的方法。在很多博文里,都有关于它的使用,但我为什么会注释掉它呢?
再看它前面的代码,里面有一个isFinishing()方法,这个方法和isDestroyed()方法到底有啥不同呢?
根据我的打印发现,在onPause(),onStop(),和onDestroy()方法中,isFinishing()方法的返回值一直为true(在onCreate(),onStart(),onResume()方法中返回false),而isDestroy()的返回值,只有在onDestroy()方法中才为true,其它生命周期方法中,为false。
这么算起来,只要前面写了isFinishing(),那么写不写isDestroyed()方法都是一样的

新问题:

神马?还有新问题,对的。
在DialogUtil中,我们只添加了show进度框的方法,怎么能没加close的方法呢,好的,我们加上,如下

    /**
     * 关闭进度框
     */
    public static void closeProgressDialog() {

        if (isShowing(mProgressDialog)) {
            mProgressDialog.dismiss();
            mProgressDialog = null;
            mWeakReference.clear();
            mWeakReference = null;
        }
    }


    /**
     * 判断进度框是否正在显示
     */
    private static boolean isShowing(ProgressDialog dialog) {
        boolean isShowing = dialog != null
                && dialog.isShowing();
        Log.d("wisely",">------isShow:"+isShowing);
        return isShowing;
    }

代码很明白,但我们调用的时候,还是会存在偶发性bug,下面,我们就自己制造出一个bug。
只列出更改的代码

Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case 1:
                    DialogUtil.closeProgressDialog();
                    break;
                case 2:
                    DialogUtil.showProgressDialog(WindowDestroyBugActivity.this, "弹窗喽");
                    break;

            }

        }
    };

    /**
     * 点击事件的回调,在xml中写的
     */
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.tv_unable_to_add_window_finish:
                DialogUtil.showProgressDialog(WindowDestroyBugActivity.this, "弹窗喽");
                finish();
                mHandler.sendEmptyMessageDelayed(1, 3000);
                break;
        }
    }

逻辑很清晰,先显示一个弹窗,然后finish掉整个activity,过了3s后,再关闭dialog,然后,就爆出了下面的bug。

05-14 23:46:33.867 13456-13456/com.wisely E/AndroidRuntime: FATAL EXCEPTION: main
                                                            Process: com.wisely, PID: 13456
                                                            java.lang.IllegalArgumentException: View=DecorView@9d7cbf6[] not attached to window manager
                                                                at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:479)
                                                                at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:388)
                                                                at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:126)
                                                                at android.app.Dialog.dismissDialog(Dialog.java:360)
                                                                at android.app.Dialog.dismiss(Dialog.java:343)
                                                                at com.wisely.DialogUtil.closeProgressDialog(DialogUtil.java:67)
                                                                at com.wisely.activity.bug.WindowDestroyBugActivity$1.handleMessage(WindowDestroyBugActivity.java:28)
                                                                at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                at android.os.Looper.loop(Looper.java:154)
                                                                at android.app.ActivityThread.main(ActivityThread.java:6114)
                                                                at java.lang.reflect.Method.invoke(Native Method)
                                                                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:874)
                                                                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:764)

最终,报错的其实是DialogUtil中的dismiss方法

mProgressDialog.dismiss();

not attached to window manager,通俗点讲就是,dialog要关闭,但activity已经挂掉了。这个原因,与在show dialog时非常类似。

再解决

原因明白,解决也很简单
只需要在close的时候,多加一个判断

 if (isShowing(mProgressDialog) && isExist_Living(mWeakReference)) {
     ....
 }
private static boolean isExist_Living(WeakReference weakReference) {

    if(weakReference != null){
        Activity activity = weakReference.get();
        if (activity == null) {
            return false;
        }

        if (activity.isFinishing()) {
            return false;
        }
        return true;
    }

    return false;
}

好了,问题解决了。

最后一个问题

纳尼?还有问题,不是说好都解决了吗?!
好吧,其实还真有一个,在上面的代码运行时,会发现一个问题,虽然不会导致程序挂掉,但看着也让人不爽快,如下

E/WindowManager: android.view.WindowLeaked: Activity com.wisely.activity.bug.WindowDestroyBugActivity has leaked window DecorView@f9bf091[] that was originally added here
                     at android.view.ViewRootImpl.(ViewRootImpl.java:423)
                     at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:337)
                     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
                     at android.app.Dialog.show(Dialog.java:319)
                     at com.wisely.DialogUtil.showProgressDialog(DialogUtil.java:55)
                     at com.wisely.DialogUtil.showProgressDialog(DialogUtil.java:17)
                     at com.wisely.activity.bug.WindowDestroyBugActivity.onClick(WindowDestroyBugActivity.java:54)
                     at java.lang.reflect.Method.invoke(Native Method)
                     at android.view.View$DeclaredOnClickListener.onClick(View.java:4694)
                     at android.view.View.performClick(View.java:5611)
                     at android.view.View$PerformClick.run(View.java:22276)
                     at android.os.Handler.handleCallback(Handler.java:751)
                     at android.os.Handler.dispatchMessage(Handler.java:95)
                     at android.os.Looper.loop(Looper.java:154)
                     at android.app.ActivityThread.main(ActivityThread.java:6114)
                     at java.lang.reflect.Method.invoke(Native Method)
                     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:874)
                     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:764)

上面的错误翻译一下就是,你的dialog窗口泄露了,因为没有在activity finish的时候,关闭dialog

再再解决

思路明细,解决方案自然有,只需要在某个生命周期方法中,调用dialog关闭的方法即可。
那么,问题来了,在哪个生命周期方法中close呢?有3个可选,onPause(),onStop(),onDestroy()。
当然是onPause()方法了,很好,我们在onPause()方法中调用DialogUtil中的closeProgressDialog()方法,但是发现,貌似没有作用,原因为嘛,我们来看closeProgressDialog()中的一个判断

private static boolean isExist_Living(WeakReference weakReference) {

    if(weakReference != null){
        Activity activity = weakReference.get();
        if (activity == null) {
            return false;
        }
        if (activity.isFinishing()) {
            return false;
        }
        return true;
    }
    return false;
}

由于我们是在onPause方法中调用的closeProgressDialog()方法,所以activity.isFinishing()的返回值为true,导致整个isExist_Living()方法的返回值为false,最终在closeProgressDialog()方法中,并不会运行mProgressDialog.dismiss()这句代码。
那怎么办,没办法,只能单独为它来设置一个方法了,如下:

public static void closeProgressDialog(boolean flag){
    if (isShowing(mProgressDialog) && flag) {
        mProgressDialog.dismiss();
        mProgressDialog = null;
        mWeakReference.clear();
        mWeakReference = null;
    }
}

这个方法与之前的方法类似,唯一的不同之处就在于有一个传入参数,在onPause方法中调用时,直接传入true即可。

@Override
protected void onPause() {
    super.onPause();
    DialogUtil.closeProgressDialog(true);
}

至于方法的抽取,就不在本篇博文的讨论范围之内了。

总结

偶发的bug,时间久了,也就变成必现了。

你可能感兴趣的:(一虫一世界)