android.view.WindowManager$BadTokenException解决

2021-02-20 11:56发布

一般报错日志如下

android.view.WindowManager$BadTokenException

Unable to add window -- token android.os.BinderProxy@65000e for displayid = 0 is not valid; is your activity running?

还原失败(未找到符号表)(404_1_0_2_0_0_0_0_9_0)

1  android.view.ViewRootImpl.setView(ViewRootImpl.java:936)
2  android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:398)
3  android.view.WindowManagerImpl.addView(WindowManagerImpl.java:131)
4  android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4529)
5  android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:51)
6  android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecuto

问题分析 

根据堆栈分析源码,发现wm.addView(decor, l);最终调用的是Activity#getWindowManager方法返回的WindowManager的addView,而addview方法属于WindowManager实现的接口ViewManager的方法。所以可以想到使用动态代理的方式拦截addview方法。

addView调用系统源码如下:

if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

代码实现

// 该方案有一定的弊端,因为强制将原WindowManager替换为我们代理类,系统获取windowManager时直接强转会出现类型异常。
// 不过目前只在Activity#getSystemService方法遇到。需要处理下
// 该问题在华为手机遇到量比较大,所以建议只处理华为相关手机
public class HookWindowManager {
    public static final String TAG = "HookWindowManager";

    private Object mWindowManager;

    public void init(Activity activity) {
        if (Build.VERSION.SDK_INT != Build.VERSION_CODES.P) {
            return;
        }
        if (Build.BRAND.equalsIgnoreCase("HUAWEI") ||
                Build.BRAND.equalsIgnoreCase("HONOR")) {
            try {
                Log.d(TAG, "invoke " + activity.getClass().getSimpleName());
                Field wm = Activity.class.getDeclaredField("mWindowManager");
                wm.setAccessible(true);
                mWindowManager = wm.get(activity);

                Class windowManagerCls = Class.forName("android.view.WindowManager");
                Class[] classes = {windowManagerCls};
                Object viewManageProxy = Proxy.newProxyInstance(
                        windowManagerCls.getClassLoader(),
                        classes,
                        new ViewManagerProxy(mWindowManager, activity));
                wm.set(activity, viewManageProxy);
            } catch (Exception e) {
                Log.e(TAG, "" + e);
        }
    }

    public Object getRealWindowManager() {
        return mWindowManager;
    }

    private class ViewManagerProxy implements InvocationHandler {
        private Object iViewManager;
        private Activity activity;

        public ViewManagerProxy(Object iActivityManager, Activity activity) {
            this.iViewManager = iActivityManager;
            this.activity = activity;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


            if ("addView".equals(method.getName())) {
                try {
                    LOGGER.d(TAG, "addView invoke execute ");
                    return method.invoke(iViewManager, args);
                } catch (Exception e) {
                    LOGGER.w(TAG, "addView exception: ", e);
                    if (e instanceof WindowManager.BadTokenException && activity != null) {
                        activity.finish();
                    }
                    return null;
                }
            }
            return method.invoke(iViewManager, args);
        }
    }
}

只需对指定Activity加入这段代理代码即可达到拦截addView方法,捕获异常。

注意:

因为对Activity的windowmanger注入了我们实现的代理,而系统获取windowmanager时会强转,类型不一样所以会报错。

getSystemService调用的是Activity的方法,所以需要重写Activity#getSystemService

public Object getSystemService(@NonNull String name) {
    if (WINDOW_SERVICE.equals(name)) {
        Object windowManager = mHookWindowManager.getRealWindowManager();
        if (windowManager != null) {
            return windowManager;
        }
    }
    return super.getSystemService(name);
}

 

标签: