美文网首页附屏類
Android Presentation关于Context

Android Presentation关于Context

作者: galaxyone | 来源:发表于2019-03-28 11:28 被阅读0次

    最近在参与一个关于副屏广告的项目中,涉及到Presentation这个副屏类,class Presentation extends Dialog,指定display去在特定显示器上显示,如果是需要副屏在副屏,则指定为1即可。

    在写副屏demo过程中,发现当指定Dialog窗口类型Type为TYPE_APPLICATION这些普通应用窗口时,Context可以使用Activity 的context,而不能使用getApplicationContext(),否则报以下异常信息。

    11-11 09:23:39.837 E/AndroidRuntime(17598): FATAL EXCEPTION: main
    11-11 09:23:39.837 E/AndroidRuntime(17598): Process: com.will.Screen, PID: 17598
    11-11 09:23:39.837 E/AndroidRuntime(17598): android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.view.ViewRootImpl.setView(ViewRootImpl.java:683)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:380)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.app.Dialog.show(Dialog.java:322)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.app.Presentation.show(Presentation.java:237)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at com.will.Screen.MainActivity$1.onClick(MainActivity.java:48)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.view.View.performClick(View.java:5647)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.view.View$PerformClick.run(View.java:22443)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.os.Handler.handleCallback(Handler.java:751)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.os.Handler.dispatchMessage(Handler.java:95)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.os.Looper.loop(Looper.java:154)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at android.app.ActivityThread.main(ActivityThread.java:6119)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at java.lang.reflect.Method.invoke(Native Method)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    11-11 09:23:39.837 E/AndroidRuntime(17598):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
    

    Log异常信息显示token为null,token可以理解成一个窗口令牌。在分析这个异常发生原因前,先来理解几个概念:

    Window:定义窗口样式和行为的抽象基类,用于作为顶层的view加到WindowManager中,其实现类是PhoneWindow。
    每个Window都需要指定一个Type(应用窗口、子窗口、系统窗口)。Activity对应的窗口是应用窗口;PopupWindow,ContextMenu,OptionMenu是常用的子窗口;像Toast和系统警告提示框(如ANR)就是系窗口,还有很多应用的悬浮框也属于系统窗口类型。

    WindowManager:用来在应用与window之间的管理接口,管理窗口顺序,消息等。

    WindowManagerService:简称Wms,WindowManagerService管理窗口的创建、更新和删除,显示顺序等,是WindowManager这个管理接口的真正的实现类。它运行在System_server进程,作为服务端,客户端(应用程序)通过IPC调用和它进行交互。

    Token:Token主是指窗口令牌(Window Token),是一种特殊的Binder令牌,Wms用它唯一标识系统中的一个窗口。

    Activity的Window和Wms的关系

    Activity有一个PhoneWindow,当我们调用setContentView时,其实最终结果是把我们的DecorView作为子View添加到PhoneWindow的DecorView中。而最终这个DecorView,过WindowMnagerImpl的addView方法添加到WMS中去的,由WMS负责管理和绘制(真正的绘制在SurfaceFlinger服务中)。

    DecorView加载
    • Presentation窗口Type设置为应用窗口类型时

    跟Activity对应的窗口一样,Presentation继承于Dialog,而Dialog有一个PhoneWindow的实例。当Presentation设置为是TYPE_APPLICATION,属于应用窗口类型:

    mPresentation.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION);
    

    Dialog的构造函数为:

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
            .......
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
            final Window w = new PhoneWindow(mContext);
            android.util.Log.e("dialog","wjx----------w :" + w );
            mWindow = w;
            android.util.Log.e("dialog","wjx----------mWindow :" + mWindow );
            android.util.Log.e("dialog","wjx------dialog----mWindowManager :" + mWindowManager );
            w.setCallback(this);
            w.setOnWindowDismissedCallback(this);
            w.setWindowManager(mWindowManager, null, null);
            w.setGravity(Gravity.CENTER);
    
            mListenersHandler = new ListenersHandler(this);
        }
    

    注意w.setWindowManager(mWindowManager, null, null)这句,把appToken设置为null。这也是Dialog和Activity窗口的一个区别,Activity会将这个appToken设置为ActivityThread传过来的token。

    当使用的是Activity context时,如上的mWindowManager获取的是Activity 的mWindowManager,在Activity的代码实现如下:

        public Object getSystemService(@ServiceName @NonNull String name) {
            if (getBaseContext() == null) {
                throw new IllegalStateException(
                        "System services not available to Activities before onCreate()");
            }
    
            if (WINDOW_SERVICE.equals(name)) {
                android.util.Log.e("wjx","wjx---activity------getSystemService---mWindowManager:" + mWindowManager);
                return mWindowManager;
            } else if (SEARCH_SERVICE.equals(name)) {
                ensureSearchManager();
                return mSearchManager;
            }
            return super.getSystemService(name);
        }
    

    在执行w.setWindowManager(mWindowManager, null, null)时,最终会执行到Window.java中,

        public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
                boolean hardwareAccelerated) {
            mAppToken = appToken;
            mAppName = appName;
            mHardwareAccelerated = hardwareAccelerated
                    || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
            if (wm == null) {
                wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
            }
            mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
        }
    

    注意mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this)这句执行,代码执行在WindowManagerImpl.java中

        private WindowManagerImpl(Context context, Window parentWindow) {
            mContext = context;
            mParentWindow = parentWindow;
        }
    
        public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
            return new WindowManagerImpl(mContext, parentWindow);
        }
    

    Window parentWindow即为this传入的window类型,而我们使用的是Activity的context,所以此处的parentWindow即为Activity 的window。

    根据异常log信息显示,当使用getApplicationContext()会报token null异常,而使用Activity context则正常,先来看下为什么使用Activity context时,tocke 不为null。

    窗口创建,都会通过WindowManagerService.java的addWindow()来实现,代码如下(只贴出与问题相关一小部分代码):

    public int addWindow(Session session, IWindow client, int seq,
                WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
                Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
                InputChannel outInputChannel) {
            .....
            synchronized(mWindowMap) {
                ......
                boolean addToken = false;
                WindowToken token = mTokenMap.get(attrs.token);
                android.util.Log.e(TAG_WM, "wjx---windowmanagerservice-----attrs.token:" + attrs.token);
                AppWindowToken atoken = null;
                boolean addToastWindowRequiresToken = false;
    
                if (token == null) {
                    android.util.Log.e(TAG_WM, "wjx-----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;
                    }
                    ........
                    token = new WindowToken(this, attrs.token, -1, false);
                    addToken = true;
                } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    atoken = token.appWindowToken;
                    android.util.Log.e(TAG_WM,"wjx---------addWindow-------atoken:" + atoken.toString());
                    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;
                    }
                    ..........
    
                mPolicy.adjustWindowParamsLw(win.mAttrs);
                win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
                .....
        }
    

    根据如上代码逻辑可知,当atoken = token.appWindowToken为null时,就报出了文章中的token=null的异常。

    1. 使用Activity context时,appWindowToken即为Activity的appWindowToken,在Activity启动的时候,WindowManagerService就调用了addAppToken(),此函数会执行mTokenMap.put(token.asBinder(), atoken)操作,会将appWindowToken存储到一个HashMap mTokenMap中。所以不会报错
    2. 使用getApplicationContext()时,appWindowToken为null,就导致了上述异常问题

    相关文章

      网友评论

        本文标题:Android Presentation关于Context

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