Android Presentation關于Context

最近在參與一個關于副屏廣告的項目中,涉及到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,就導致了上述異常問題
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容