Service能够创建界面(addView)吗?

一个Service能够创建界面(addView)吗?

一个app,只有Service,没有Activity,能够通过WindowManager调用addView()添加可视界面吗?

答案是可以,但是能够创建的界面类型(WindowManager.LayoutParams.type)不多,且大多需要android.permission.INTERNAL_SYSTEM_WINDOW权限,这个权限只能授予system app。

在没有Activity的进程中创建显示View,存在两个问题:

  1. 线程,安卓只能在主线程操作UI界面。
  2. 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;		//WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
                        mWindowManager.addView(cursorLocationView, params);
                        break;
                }
            }
        };
    }

第二个问题需要解决token问题,WindowManagerService.addWindow()方法中在检查到token==null的情况时通过unprivilegedAppCanCreateTokenWith()检查属性:WindowManager.LayoutParams.type,如果不是特权类型的话返回false。

//WindowManagerService.java
	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) {
            // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
            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对象:

//WindowManagerService.java
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) {
                    // Use existing parent window token for child windows.
                    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 Typeintpermission (*只能赋权给system app)
TYPE_SECURE_SYSTEM_OVERLAY2015*android.permission.INTERNAL_SYSTEM_WINDOW覆盖在所有Window上
TYPE_PHONE2002*android.permission.INTERNAL_SYSTEM_WINDOW覆盖在所有application上,但是不覆盖status bar
TYPE_APPLICATION_OVERLAY2038android.permission.SYSTEM_ALERT_WINDOW覆盖所有Activity window,(types between {@link #FIRST_APPLICATION_WINDOW} and {@link #LAST_APPLICATION_WINDOW})
Android中,如果你想要在一个已存在的界面上自定义一个View并绘制一个红色的中心圆框,你可以通过以下步骤来实现: 1. 首先,创建一个新的布局文件(例如:MyCustomView.xml),并将它设置为一个自定义View的XML布局。在这个布局中,可以包含一个圆形ImageView或者其他视图作为红框的基础元素。 ```xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/custom_view_center_circle" android:layout_width="50dp" android:layout_height="50dp" android:src="@color/red_color" app:layout_constraintCenterInParent="true" app:layout_constraintRadius="auto" app:backgroundTint="?attr/colorPrimary" /> </androidx.constraintlayout.widget.ConstraintLayout> ``` 这里我们使用了一个ImageView,并设置了`layout_constraintCenterInParent`属性使其居中,同时设置了背景颜色为红色(`@color/red_color`)。 2. 然后,在对应的Activity或Fragment中,你需要创建一个自定义View的子类,比如`MyCustomView.java`: ```java public class MyCustomView extends View { private ImageView centerCircle; public MyCustomView(Context context) { this(context, null); } public MyCustomView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { // 在这里加载XML布局 LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.my_custom_view, this, true); centerCircle = view.findViewById(R.id.custom_view_center_circle); // 如果需要动态调整大小,可以在这里设置centerCircle的宽高 centerCircle.setWidth(50); centerCircle.setHeight(50); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 可能不需要这个,如果直接用ImageView,ImageView会自动绘制其内容 // 如果你想自定义绘制样式,可以在onDraw里添加额外的逻辑 // ... } } ``` 3. 最后,在需要显示自定义红框的地方,实例化并添加到布局: ```java MyCustomView customView = new MyCustomView(context); parentLayout.addView(customView); ``` 现在你应该能看到一个带有中心红框的新View了。如果需要调整圆框的颜色、大小或其他属性,可以直接在相应地方修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值