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