Android 后台服务启动Actvity

一、问题背景

相机自动化测试需求,测试apk通过bindService绑定相机apk里面的一个服务,通过AIDL接口的方式向相机apk发送命令,服务接收到命令之后会拉起相机的Activity。原本没有人为干预的情况下是可以拉起这个Activity的,但是拉起Activity之前,我们如果按下Home键,让测试apk退出的话,后台服务就无法拉起Activity了。经过调查发现Android10 之后做了这个限制

https://developer.android.google.cn/guide/components/activities/background-starts

 二、项目需求框架

ITestApp通过bindService连接到ITestAppService,是跨进程的。

 ITestAppService是我们的相机应用里面的一个服务,主要是响应外部应用的命令;接收到外部应用的命令之后调用相机内部代码。

其中第一条命令一般就是启动 CameraActivity,就是后台服务启动Activity。

三、分析无法启动原因

当按了Home键之后,后台服务就无法启动相机Activity了,以下是ActivityTaskManager的日志,启动被终止了。

11-16 13:54:01.143  1160  8007 W ActivityTaskManager: Background activity start 
[callingPackage: com.android.camera2; callingUid: 10150; appSwitchState: 1; 
isCallingUidForeground: false; callingUidHasAnyVisibleWindow: false; 
callingUidProcState: FOREGROUND_SERVICE; isCallingUidPersistentSystemProcess: false; 
realCallingUid: 10150; isRealCallingUidForeground: false; realCallingUidHasAnyVisibleWindow: false; 
realCallingUidProcState: FOREGROUND_SERVICE; isRealCallingUidPersistentSystemProcess: false; 
originatingPendingIntent: null; allowBackgroundActivityStart: false; 
intent: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.android.camera2/com.android.camera.CameraActivity (has extras) }; 
callerApp: ProcessRecord{a469c90 22598:com.android.camera2/u0a150}; inVisibleTask: false]

反正就是一堆启动的条件都没有满足,所以终止了。

拦截的逻辑就在系统ActivityStarter.java的这个方法里面,反正就是一个条件都不满足了。

四、相机应用解决办法、

AMS WMS的相关代码太复杂,没有过多时间仔细研究。简单看了一下shouldAbortBackgroundActivityStart里面返回false的逻辑,有些还是很好理解的。

像这一段表明只要在相机应用的AndroidManifest.xml文件里面加上android:sharedUserId=“android.uid.system“ 这一行就可以了,实际验证之后发现确实可行。

 

 像这一段 说明只要应用有SYSTEM_ALERT_WINDOW 权限就可以了,实测也是可以的

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

 五、测试应用解决办法

如果不想在被测应用加权限/设置android:sharedUserId,也可以在测试端想办法,我们的做法是在测试应用里面加一个浮窗,那么应用退出的时候浮窗还在,还能继续启动被测应用的activity。

浮窗代码也是copy的,就是通过一个服务启动的

public class FloatingService extends Service {
    private static final String TAG = "CAMTEST_FloatingService";

    public FloatingService() {

    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        showFloatingWindow();
        return super.onStartCommand(intent, flags, startId);
    }

    WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();

    private void showFloatingWindow() {
        if (Settings.canDrawOverlays(this)) {
            // 获取WindowManager服务
            WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

            // 新建悬浮窗控件
            TextView textView = new TextView(getApplicationContext());
            textView.setText("接口测试需要, 勿惊...");
            textView.setTextColor(0xffff0000);
            textView.setGravity(Gravity.CENTER);
            textView.setBackgroundColor(Color.parseColor("#FF6200EE"));
            textView.setOnTouchListener(new FloatingOnTouchListener());

            // 设置LayoutParam
            int screenWidth = windowManager.getDefaultDisplay().getWidth();
            int screenHeight = windowManager.getDefaultDisplay().getHeight();
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            layoutParams.format = PixelFormat.RGBA_8888;
            layoutParams.width = 500;
            layoutParams.height = 100;
            layoutParams.x = screenWidth - layoutParams.width;
            layoutParams.y = screenHeight - layoutParams.height;

            // 将悬浮窗控件添加到WindowManager
            windowManager.addView(textView, layoutParams);
        }
    }

    private class FloatingOnTouchListener implements View.OnTouchListener {
        private int x;
        private int y;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int nowX = (int) event.getRawX();
                    int nowY = (int) event.getRawY();
                    int movedX = nowX - x;
                    int movedY = nowY - y;
                    x = nowX;
                    y = nowY;
                    layoutParams.x = layoutParams.x + movedX;
                    layoutParams.y = layoutParams.y + movedY;

                    // 更新悬浮窗控件布局
                    WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
                    windowManager.updateViewLayout(view, layoutParams);
                    break;
                default:
                    break;
            }
            return false;
        }
    }
}

启动地方代码

public void startFloatingService(View view) {
        if (!Settings.canDrawOverlays(this)) {
            Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT);
            startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0);
        } else {
            Log.d(TAG, "startFloatingService startService");
            startService(new Intent(MainActivity.this, FloatingService.class));
        }
    }

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Android 后台服务启动 Unity 程序,可以使用 `UnityPlayerNativeActivity`。这个 Activity 是 Unity 引擎的入口点,可以通过 Intent 启动。 下面是一个简单的示例代码,演示如何在后台服务启动 Unity 程序: ```java Intent intent = new Intent(this, UnityPlayerNativeActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); ``` 这里,`this` 是指代服务的上下文。通过创建一个新的 Intent 对象,将目标 Activity 指定为 `UnityPlayerNativeActivity`,并调用 `startActivity` 方法来启动 Unity 程序。`Intent.FLAG_ACTIVITY_NEW_TASK` 标志表示要在一个新的任务栈中启动 Activity,而不是在当前任务栈中启动。 如果你需要向 Unity 程序传递参数,可以使用 Intent 的 `putExtra` 方法。例如,要传递一个字符串参数,可以这样做: ```java Intent intent = new Intent(this, UnityPlayerNativeActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra("message", "Hello, Unity!"); startActivity(intent); ``` 在 Unity 程序中,可以使用 `getIntent` 方法获取传递的 Intent,并从 Intent 中提取参数。例如,要获取上面示例中传递的字符串参数,可以这样做: ```csharp string message = getIntent().getStringExtra("message"); Debug.Log(message); ``` 这里,`getIntent` 方法返回启动 UnityPlayerNativeActivity 的 Intent,可以从中获取传递的参数。使用 `getStringExtra` 方法获取字符串参数。注意,在服务启动 Unity 程序时,Unity 引擎只会运行在后台,没有界面。如果需要在服务中显示 Unity 程序的界面,可以参考 Unity 官方文档中的相关说明进行实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值