一个Service能够创建界面(addView)吗?
一个app,只有Service,没有Activity,能够通过WindowManager调用addView()添加可视界面吗?
答案是可以,但是能够创建的界面类型(WindowManager.LayoutParams.type)不多,且大多需要android.permission.INTERNAL_SYSTEM_WINDOW权限,这个权限只能授予system app。
在没有Activity的进程中创建显示View,存在两个问题:
- 线程,安卓只能在主线程操作UI界面。
- token,在启动Activity时实例化ActivityRecord对象创建token(ActivityRecord继承WindowToken类)。
第一个问题好解决,Service生命周期方法onStartCommand()运行在主线程,可以在此方法中定义Handler,service线程通过handler发送消息跨线程通信,在主线程操作UI界面。
Handler handler;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
CursorLocationView cursorLocationView = new CursorLocationView(BleService.this);
params.type = 2015;
mWindowManager.addView(cursorLocationView, params);
break;
}
}
};
}
第二个问题需要解决token问题,WindowManagerService.addWindow()方法中在检查到token==null的情况时通过unprivilegedAppCanCreateTokenWith()检查属性:WindowManager.LayoutParams.type,如果不是特权类型的话返回false。
private boolean unprivilegedAppCanCreateTokenWith(WindowState parentWindow,
int callingUid, int type, int rootType, IBinder tokenForLog, String packageName) {
if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
ProtoLog.w(WM_ERROR, "Attempted to add application window with unknown token "
+ "%s. Aborting.", tokenForLog);
return false;
}
if (rootType == TYPE_INPUT_METHOD) {
ProtoLog.w(WM_ERROR, "Attempted to add input method window with unknown token "
+ "%s. Aborting.", tokenForLog);
return false;
}
if (rootType == TYPE_VOICE_INTERACTION) {
ProtoLog.w(WM_ERROR,
"Attempted to add voice interaction window with unknown token "
+ "%s. Aborting.", tokenForLog);
return false;
}
if (rootType == TYPE_WALLPAPER) {
ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with unknown token "
+ "%s. Aborting.", tokenForLog);
return false;
}
if (rootType == TYPE_QS_DIALOG) {
ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with unknown token "
+ "%s. Aborting.", tokenForLog);
return false;
}
if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
ProtoLog.w(WM_ERROR,
"Attempted to add Accessibility overlay window with unknown token "
+ "%s. Aborting.", tokenForLog);
return false;
}
if (type == TYPE_TOAST) {
if (doesAddToastWindowRequireToken(packageName, callingUid, parentWindow)) {
ProtoLog.w(WM_ERROR, "Attempted to add a toast window with unknown token "
+ "%s. Aborting.", tokenForLog);
return false;
}
}
return true;
}
可以看到以下类型是需要token的,都和用户界面交互强相关:
- FIRST_APPLICATION_WINDOW~LAST_APPLICATION_WINDOW
- TYPE_INPUT_METHOD
- TYPE_VOICE_INTERACTION
- TYPE_WALLPAPER
- TYPE_QS_DIALOG
- TYPE_ACCESSIBILITY_OVERLAY
- TYPE_TOAST(appInfo.targetSdkVersion < Build.VERSION_CODES.O 时例外)
其余特权类型在token=null的情况下,创建新的Token对象:
public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
int requestUserId) {
...
if (token == null) {
if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,
rootType, attrs.token, attrs.packageName)) {
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
if (hasParent) {
token = parentWindow.mToken;
} else {
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
token = new WindowToken(this, binder, type, false, displayContent,
session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
}
}
}
一些权限类型:
Window Type | int | permission (*只能赋权给system app) | |
---|
TYPE_SECURE_SYSTEM_OVERLAY | 2015 | *android.permission.INTERNAL_SYSTEM_WINDOW | 覆盖在所有Window上 | TYPE_PHONE | 2002 | *android.permission.INTERNAL_SYSTEM_WINDOW | 覆盖在所有application上,但是不覆盖status bar | TYPE_APPLICATION_OVERLAY | 2038 | android.permission.SYSTEM_ALERT_WINDOW | 覆盖所有Activity window,(types between {@link #FIRST_APPLICATION_WINDOW} and {@link #LAST_APPLICATION_WINDOW}) |
|