Android Q 后台启动Activity限制

本文针对Android Q 后台启动Activity简单总结

Android Q对应用程序何时可以启动Activity进行了限制。

变化

这种行为的改变有助于减少用户的干扰,并让用户更好地控制屏幕上显示的内容。尤其是运行在Android Q上的应用程序,只有满足以下一个或多个条件才能启动Activity:

  • 应用程序有一个可见的窗口,比如在前台一个的Activity。
  • 一个在前台不同的应用程序发送了属于该应用程序的一个PendingIntent, 示例包括自定义选项卡提供者发送菜单项待定意图。
  • 系统发送了一个属于该应用程序的PendingIntent,比如点击一条通知,只有应用程序可以启动UI的待处理意图才可以免除。
  • 系统向应用程序发送一个广播,如SECRET_CODE_ACTION到应用程序。只有特定的广播,应用程序预计在UI启动时才会被豁免。

【注】出于Activity启动的目的,前台服务不将应用程序限定在前台。

影响

这个行为变化影响到所有运行在Android Q上的应用程序,即使是那些target版本是Android 9(API 28) 或者更低的,即使您的应用程序的target版本是Android 9或更低版本,并且最初安装在运行Android 9或更低版本的设备上,在设备升级到Android Q之后,行为更改仍然会生效。

但是,只要您的应用程序作为用户交互的直接结果启动Actvity,您的应用程序就很可能不会受到此更改的影响。

警告信息

在Beta 1中,如果你的应用程序运行在Android Q上,并试图从后台启动某个活动,平台允许该活动启动,但它会向logcat发送一条警告消息,并显示以下警告toast消息:

1
This background activity start from package-name will be blocked in future Q builds.

这则消息意思是: 该后台activity从某包下启动,将在之后构建的Q版本中被屏蔽

Android Q中与在后台启动Activity相关的限制与设备进入屏幕固定状态后系统阻止Activity启动的方式类似。

改进方式

几乎在所有情况下,后台应用程序都应创建通知,以便向用户提供信息,而不是直接启动Activity。

示例

复现警告

为了测试复现警告消息,这里定义MainActivityBackgroundActivity,在MainActivityonPause方法中如下实现

1
2
3
4
5
6
7
8
9
10
override fun onPause() {
super.onPause()
val timer = Timer()
val timerTask = object : TimerTask() {
override fun run() {
startActivity(Intent(this@MainActivity,BackgroundActivity::class.java))
}
}
timer.schedule(timerTask, 5000)
}

MainActivity启动之后,按Home键切换到Launcher,此时MainActivity将会执行onPause方法,在此方法中开启了一个定时器,模拟后台启动Activity

然后后台输出如下日志

1
2
3
4
2019-03-16 10:03:10.599 1192-7709/? I/ActivityTaskManager: START u0 {cmp=com.xw.androidqtest/.BackgroundActivity} from uid 10505
2019-03-16 10:03:10.600 1192-7709/? W/ActivityTaskManager: Background activity start [callingPackage: com.xw.androidqtest; callingUid: 10505; isCallingUidForeground: false; isCallingUidPersistentSystemProcess: false; realCallingUid: 10505; isRealCallingUidForeground: false; isRealCallingUidPersistentSystemProcess: false; originatingPendingIntent: null; isBgStartWhitelisted: false; intent: Intent { cmp=com.xw.androidqtest/.BackgroundActivity }; callerApp: ProcessRecord{b6f89ff 29017:com.xw.androidqtest/u0a505}]
2019-03-16 10:03:10.619 29017-29017/com.xw.androidqtest W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy@8611b07
2019-03-16 10:03:10.700 1192-1238/? I/ActivityTaskManager: Displayed com.xw.androidqtest/.BackgroundActivity: +96ms

界面弹出了警告消息

warningMsg

参考官方文档,做如下处理

处理

创建前台服务类,并在onStartCommand方法中创建一个前台通知,

以下是完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
 package com.xw.androidqtest

import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat

class ForegroundService : Service() {

private val tag: String = ForegroundService::class.java.simpleName

override fun onBind(intent: Intent): IBinder? {
return null;
}

override fun onCreate() {
super.onCreate()
Log.d(tag, "ForegroundService OnCreate called")
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(tag, "ForegroundService onStartCommand called")

createNotificationChannel("channelId", "channelName")

val fullScreenIntent = Intent(this, BackgroundActivity::class.java)
val fullScreenPendingIntent = PendingIntent.getActivity(
this, 0,
fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT
)

val notificationBuilder = NotificationCompat.Builder(this, "channelId")
.setSmallIcon(R.mipmap.ic_launcher_round)
.setContentTitle(getString(R.string.app_name))
.setContentText("启动BackgroundActivity")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_CALL)

// Use a full-screen intent only for the highest-priority alerts where you
// have an associated activity that you would like to launch after the user
// interacts with the notification. Also, if your app targets Android Q, you
// need to request the USE_FULL_SCREEN_INTENT permission in order for the
// platform to invoke this notification.
.setFullScreenIntent(fullScreenPendingIntent, true)

val incomingCallNotification = notificationBuilder.build()


// The integer ID that you give to startForeground() must not be 0.
startForeground(1, incomingCallNotification)

return START_STICKY
}


@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String {
val channel = NotificationChannel(
channelId,
channelName, NotificationManager.IMPORTANCE_NONE
)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(channel)
return channelId
}

override fun onDestroy() {
super.onDestroy()
stopForeground(true);
}
}

清单文件注册权限

1
2
3
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- New Android Q permission -->
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>

最后更新onPause方法

1
2
3
4
5
6
7
8
9
10
override fun onPause() {
super.onPause()
val timer = Timer()
val timerTask = object : TimerTask() {
override fun run() {
startService(Intent(this@MainActivity, ForegroundService::class.java))
}
}
timer.schedule(timerTask, 5000)
}

运行输出日志:

1
2
2019-03-16 10:25:22.308 9112-9112/com.xw.androidqtest D/ForegroundService: ForegroundService OnCreate called
2019-03-16 10:25:22.309 9112-9112/com.xw.androidqtest D/ForegroundService: ForegroundService onStartCommand called

通知栏收到了发出的通知

warningMsgFix

点击通知进入了需要后台启动的BackgroundActivity
日志内容 :

1
2
3
4
5
2019-03-16 10:29:04.074 1192-4994/? I/ActivityTaskManager: START u0 {cmp=com.xw.androidqtest/.BackgroundActivity} from uid 10505
2019-03-16 10:29:04.074 1192-4994/? W/ActivityTaskManager: startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: Intent { cmp=com.xw.androidqtest/.BackgroundActivity }
2019-03-16 10:29:04.082 9112-9112/com.xw.androidqtest W/ActivityThread: handleWindowVisibility: no activity for token android.os.BinderProxy@46d3c48
2019-03-16 10:29:04.084 1192-1251/? W/WindowManager: TaskSnapshotSurface.create: Failed to find main window for token=AppWindowToken{6b40830 token=Token{fb2f173 ActivityRecord{7d079e2 u0 com.xw.androidqtest/.BackgroundActivity t800}}}
2019-03-16 10:29:04.143 1192-1238/? I/ActivityTaskManager: Displayed com.xw.androidqtest/.BackgroundActivity: +69ms

开启行为变化

即使这种行为变化在默认情况下不会在Android Q Beta 1中生效,您也可以通过完成以下任务之一来模拟这种行为变化:

  • 导航到“设置>开发人员选项”,并禁用“允许后台活动启动”选项
  • 在终端中执行如下命令
    1
    adb shell settings put global background_activity_starts_enabled 0

模拟测试

将代码还原成直接调用从后台启动Activity,设置好如上所叙述的操作,测试
效果图

warningQMsg

与文章一开始测试不同之处就是: 目前在Q Beta1上默认还是会启动起后台的Activity展示并显示toast消息,这次测试模拟了最终完全屏蔽的情况,不会调起后台的Activity

本文参考官方文档进行简单总结,感谢您的阅读。如有不足,欢迎指正

if (本文对您有用) { Pay (请随意¥打赏) } else { Commit feedback (底部评论区提交建议、反馈) } 感谢支持!