美文网首页
一次安卓dialog出现崩溃的解决思路

一次安卓dialog出现崩溃的解决思路

作者: 江心的风 | 来源:发表于2018-12-12 15:01 被阅读0次

    近期碰到了一个费解的bug,一个自定义的dialog库在新写的demo上运行总是崩溃,报出WindowManager$BadTokenException,可是在其他项目里都是正常的,很是费解,于是我一步一步的分析出问题所在。

    1.错误分析

    首先贴上错误日志,如图:

    error1.png

    看到这个exception,还是很常见的,经常在一些弹框上出现。出现原因可能有:

    1. 弹框弹出时,activity已经销毁了
    2. 使用的是Application的context

    但根据后面描述,应该是activity没运行。于是我去检查了我的dialog弹出时机,并没有在activity销毁时弹出。我这边的代码看不出来什么问题,所以我觉得得往问题的源头开始找起。

    2.从源码追踪

    打开androidxref,搜索下报错的源头ViewRootImpl。我的手机是8.0的,所以选择此版本的源代码。

    /frameworks/base/core/java/android/view/ViewRootImpl.java

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
                if (mView == null) {
                    ... 省略无关代码
                    int res; /* = WindowManagerImpl.ADD_OKAY; */
                    requestLayout();
                    try {
                        mOrigWindowType = mWindowAttributes.type;
                        mAttachInfo.mRecomputeGlobalAttributes = true;
                        collectViewAttributes();
                        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
                    } catch (RemoteException e) {
                    } finally {
                        if (restore) {
                            attrs.restore();
                        }
                    }
    
                    if (res < WindowManagerGlobal.ADD_OKAY) {
                        mAttachInfo.mRootView = null;
                        mAdded = false;
                        mFallbackEventHandler.setView(null);
                        unscheduleTraversals();
                        setAccessibilityFocus(null, null);
                        switch (res) {
                            case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                            case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                                throw new WindowManager.BadTokenException(
                                        "Unable to add window -- token " + attrs.token
                                        + " is not valid; is your activity running?");
                        }
                        throw new RuntimeException(
                                "Unable to add window -- unknown error code " + res);
                    }
                }
            }
        }
    
    

    可以看到根据mWindowSession.addToDisplay的返回值,抛出了这个exception,找下mWindowSession的初始化地方。

     public ViewRootImpl(Context context, Display display) {
            mContext = context;
            mWindowSession = WindowManagerGlobal.getWindowSession();
            mDisplay = display;
            ...
        }
    

    继续去看WindowManagerGlobal

    /frameworks/base/core/java/android/view/WindowManagerGlobal.java

      public static IWindowManager getWindowManagerService() {
            synchronized (WindowManagerGlobal.class) {
                if (sWindowManagerService == null) {
                    //此处使用aidl获得IWindowManager实例
                    sWindowManagerService = IWindowManager.Stub.asInterface(
                            ServiceManager.getService("window"));
                    try {
                        sWindowManagerService = getWindowManagerService();
                        ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
                return sWindowManagerService;
            }
        }
    

    啊哦,原来是通过aidl返回的WindowManagerService,好的离真相更近了,继续!找到WindowManagerService

    /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

        @Override
        public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
                IInputContext inputContext) {
            if (client == null) throw new IllegalArgumentException("null client");
            if (inputContext == null) throw new IllegalArgumentException("null inputContext");
            Session session = new Session(this, callback, client, inputContext);
            return session;
        }
    

    可以看到是返回的Session对象。

    /frameworks/base/services/core/java/com/android/server/wm/Session.java

    让我们再看下Session的addToDisplay方法

        @Override
        public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
                int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
                Rect outOutsets, InputChannel outInputChannel) {
            return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                    outContentInsets, outStableInsets, outOutsets, outInputChannel);
        }
    

    终于看到了罪魁祸首是谁了,这个mService就是WindowManagerService的实例。让我们看下到底为什么他要抛出异常,走进它的addWindow方法。

    /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

    public int addWindow(Session session, IWindow client, int seq,
                WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
                Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
                InputChannel outInputChannel) {
                //省略大量多余代码
                if (token == null) {
                    if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                        Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                              + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                    if (type == TYPE_INPUT_METHOD) {
                        Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                              + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                    if (type == TYPE_VOICE_INTERACTION) {
                        Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                              + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                    if (type == TYPE_WALLPAPER) {
                        Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                              + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                    if (type == TYPE_DREAM) {
                        Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                              + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                    if (type == TYPE_QS_DIALOG) {
                        Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                              + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                    if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                        Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                    if (type == TYPE_TOAST) {
                        // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                        if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                                attachedWindow)) {
                            Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                    + attrs.token + ".  Aborting.");
                            return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                        }
                    }
                    token = new WindowToken(this, attrs.token, -1, false);
                    addToken = true;
                } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    atoken = token.appWindowToken;
                    if (atoken == null) {
                        Slog.w(TAG_WM, "Attempted to add window with non-application token "
                              + token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                    } else if (atoken.removed) {
                        Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                              + token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_APP_EXITING;
                    }
                    if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
                        // No need for this guy!
                        if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
                                TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
                        return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
                    }
                } else if (type == TYPE_INPUT_METHOD) {
                    if (token.windowType != TYPE_INPUT_METHOD) {
                        Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                                + attrs.token + ".  Aborting.");
                          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                } else if (type == TYPE_VOICE_INTERACTION) {
                    if (token.windowType != TYPE_VOICE_INTERACTION) {
                        Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
                                + attrs.token + ".  Aborting.");
                          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                } else if (type == TYPE_WALLPAPER) {
                    if (token.windowType != TYPE_WALLPAPER) {
                        Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
                                + attrs.token + ".  Aborting.");
                          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                } else if (type == TYPE_DREAM) {
                    if (token.windowType != TYPE_DREAM) {
                        Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
                                + attrs.token + ".  Aborting.");
                          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                } else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                        Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                } else if (type == TYPE_TOAST) {
                    //就是这里导致的这次崩溃
                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                    addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
                            callingUid, attachedWindow);
                    if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                } else if (type == TYPE_QS_DIALOG) {
                    if (token.windowType != TYPE_QS_DIALOG) {
                        Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                } else if (token.appWindowToken != null) {
                    Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
                    // It is not valid to use an app token with other system types; we will
                    // instead make a new token for it (as if null had been passed in for the token).
                    attrs.token = null;
                    token = new WindowToken(this, null, -1, false);
                    addToken = true;
                }
            } 
    
            Binder.restoreCallingIdentity(origId);
    
            return res;
        }
    

    可能导致之前的异常的是ADD_BAD_APP_TOKENADD_BAD_SUBWINDOW_TOKEN,引起这2个的有不少,一个一个看下去。终于找到一个可能的原因。

    Apps targeting SDK above N MR1 cannot arbitrary add toast windows.

    原来targetSdkVersion在安卓7.1以上的版本都不能添加此type。看下我们app的版本,竟然是27。emmm,调低到24。运行,my god!成功了,哈哈。

    3.总结

    原来原因是这么的简单,只是targetSdkVersion指定太高,我们来看下安卓中常见的几个版本:

    • minSdkVersion:用于指定app最低兼容的系统版本,低于此版本的系统将无法安装此app
    • targetSdkVersion:通俗的来说就是兼容版本,由于安卓系统会更新,每个版本都会向前兼容。如果当前系统版本高于你的targetSdkVersion,就会去使用兼容方案。当然取较高的版本也可以使用最新的特性。

    可以看到targetSdkVersion还是不宜太高,不然很多兼容方案都不会使用,导致各种错误。

    题外话

    其实这个targetSdkVersion是我建玩工程后,忘了改。用android新建工程时总会采用最高版本,我不经想到,能不能不要每次都手动改。

    相关文章

      网友评论

          本文标题:一次安卓dialog出现崩溃的解决思路

          本文链接:https://www.haomeiwen.com/subject/ixxphqtx.html