附屏類

Android Presentation关于Context

2019-03-28  本文已影响0人  galaxyone

最近在参与一个关于副屏广告的项目中,涉及到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加载

跟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,就导致了上述异常问题
上一篇下一篇

猜你喜欢

热点阅读