如何在Android O中检测进程前台?

5
在我们的应用程序中,有一个服务通常在Application.OnCreate期间启动(直接调用context.startService),还通过AlarmManager稍后启动(重构正在进行以将其部分工作迁移到JobScheduler)。
我们的应用程序还有一个BroadcastReceiver,它会使用其直接意图启动。
鉴于Android Oreo的新限制(https://developer.android.com/about/versions/oreo/android-8.0-changes.html),我们遇到了以下问题:
  • 应用程序/进程在后台/死亡状态
  • 操作系统触发BroadcastReceiver
  • Application.onCreate()在BroadcastReceiver之前执行
  • Application.onCreate()代码尝试运行服务
这会导致崩溃并显示“IllegalStateException:Not allowed to start service Intent”。

我知道CommonsWare在这里https://dev59.com/eVcP5IYBdhLWcg3wXI7Z#44505719回答了启动Service的新推荐方式,但对于这种特定情况,我只想要if(process in foreground) { startService }。我目前正在使用以下方法,它似乎可以工作:

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static boolean isProcessInForeground_V21(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
        List<ActivityManager.AppTask> tasks = am.getAppTasks();
        return tasks.size() > 0;
    }

但我找不到Android Oreo正在执行的确切检查(我已经到达https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ContextImpl.javastartServiceCommon方法,但从那里requireForeground标志似乎进入了一些本地实现)。
所以我的问题是:
针对Android Oreo新限制的特定目的,在调用startService之前如何检查我的进程是否在前台?

也许你不应该在自定义的“Application”的onCreate()方法中启动服务。只有在需要服务时才启动服务。如果你的应用程序在前台,那么服务是无用的。 - CommonsWare
1个回答

0
继续您的调查:(简而言之:请参阅底部的横线之间的内容)
免责声明,我对Android内部了解不多,只是喜欢研究源代码。 注意:如果您跳转到文件而不是类,您也可以在Android Studio中浏览代码: enter image description here 或者在项目和库中搜索文本。 enter image description here

IActivityManager是由AIDL定义的,所以没有它的源代码: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/app/IActivityManager.aidl#145

根据AIDL需要如何实现,我发现ActivityManagerService extends IActivityManager.Stub(感谢上帝保佑Google索引)。

注意,我还找到了这个链接,如果你真的对内部工作原理感兴趣,可能会很有意思。 https://programmer.group/android-9.0-source-app-startup-process.html

ActivityManagerService源码显示,在Oreo中,startService被转发到位于同一包中的ActiveServices

假设我们正在寻找这样的异常:

java.lang.IllegalStateException: Not allowed to start service Intent {...}: app is in background uid UidRecord{af72e61 u0a229 CAC bg:+3m52s273ms idle procs:1 seq(0,0,0)}

我们必须继续深入兔子洞: requireForeground被分配给fgRequired参数,消息在这里。允许此操作的条件取决于ActivityManagerService.getAppStartModeLocked(packageTargetSdk = 26或更高, disabledOnly = false, forcedStandby = false)返回的启动模式。
有4种启动模式:
  • APP_START_MODE_NORMAL(需要与此不同,即!=
  • APP_START_MODE_DELAYED(这是可以的,即return null
  • APP_START_MODE_DELAYED_RIGID
  • APP_START_MODE_DISABLED

短暂应用程序将立即返回APP_START_MODE_DISABLED,但假设这是一个正常的应用程序,我们最终会进入appServicesRestrictedInBackgroundLocked注意:这就是决定https://dev59.com/j1YO5IYBdhLWcg3wDNXI#46445436中提到的一些白名单的地方。 由于除了最后一个分支外,所有分支都返回APP_START_MODE_NORMAL,这将重定向到appRestrictedInBackgroundLocked,在那里我们找到了我们最有可能的嫌疑人

    int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
        // Apps that target O+ are always subject to background check
        if (packageTargetSdk >= Build.VERSION_CODES.O) {
            return ActivityManager.APP_START_MODE_DELAYED_RIGID;
        }

所以拒绝的原因就是针对O。我认为对于你关于操作系统如何判断你的应用程序是前台还是后台的问题,最终答案就是getAppStartModeLocked中的这个条件
UidRecord uidRec = mActiveUids.get(uid);
if (uidRec == null || alwaysRestrict || uidRec.idle) {

我的猜测是,缺少记录意味着它没有在运行(但是它又如何启动服务?!),而"idle"表示它被后台化了。请注意,在我的异常消息中,"UidRecord"表示它已经被后台化了3分钟52秒。
我查看了你的"getAppTasks"方法,它基于"TaskRecord.effectiveUid",所以我猜这很接近列出你的应用程序的"UidRecord"。
不确定这是否有帮助,但我还是会发布它,这样如果有人想要进一步调查,他们就有更多的信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接