Oreo系统中前台服务被杀死问题

24

我编写了一个前台服务,对于低于Oreo版本的所有操作系统都可以正常工作。从Oreo开始,在关闭并从最近使用应用程序中删除应用程序5分钟后,应用程序进程会被杀死。

根据Android开发者文档中有关后台执行限制的规定,操作系统不应杀死正在运行前台服务并在通知窗口中显示通知的应用程序。

按照开发者文档上的指南,我遵循以下步骤启动前台服务:

  1. 使用startForegroundService()方法启动前台服务。
  2. 在启动服务后的5秒内,使用startForeground()显示服务通知。
  3. serviceonStartCommand()方法返回START_STICKY

我在以下手机上遇到了这个问题:

  1. OnePlus 5T
  2. Vivo
  3. Oppo
  4. Mi

我尝试了什么来防止前台服务被销毁?

  1. 通过向用户显示系统对话框来禁用应用程序的电池优化以禁用深度睡眠模式。

我尝试了什么来重新启动前台服务?

  1. 使用AlarmManageronTaskRemoved()方法重新启动服务。请参见此链接获取详细信息。

据我了解,这些制造商已经定制了AOSP,并且没有遵守允许运行前台服务的操作系统规定。也许这些制造商之所以这样做是为了让用户获得更长的电池使用时间。

前台服务类

    class DataCaptureService : Service() {

        private var isServiceStarted = false

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

override fun onCreate() {
        super.onCreate()
        wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
                    newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WakelockTag123").apply {
                        acquire()
                    }
                }
    }


        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            val serviceAction = intent?.action
            LogUtils.logD("OnStartCommand(). Action=$serviceAction")
            if (Constants.INTENT.ACTION_STOP_SERVICE == serviceAction) {
                LogUtils.logD("Stopping data capture service")
                stopForeground(true)
                stopSelf()
            } else if (Constants.INTENT.ACTION_START_SERVICE == serviceAction && !isServiceStarted) {
                LogUtils.logD("Starting data capture service")
                isServiceStarted = true
                // Here showing notification using a utility method (startForeground(id, notification))
                createNotification(this)
                // Doing some stuff here
                ----------------------
                //
            }
            return START_STICKY
        }

        override fun onDestroy() {
            super.onDestroy()
            if (isServiceStarted) {
                LogUtils.logD("onDestroy of DataCaptureService method is invoked")
                // Doing some stuff here
                ----------------------
                //
                isServiceStarted = false
                if (wakeLock.isHeld) {
                    wakeLock.release()
                }
            }
        }

        override fun onTaskRemoved(rootIntent: Intent?) {
            LogUtils.logD("onTaskRemoved of DataCaptureService method is invoked")
            ensureServiceStaysRunning()
            super.onTaskRemoved(rootIntent)
        }

        private fun ensureServiceStaysRunning() {
            val restartAlarmInterval = 60 * 1000
            val resetAlarmTimer = 30 * 1000L
            // From this broadcast I am restarting the service
            val restartIntent = Intent(this, ServiceRestartBroadcast::class.java)
            restartIntent.action = "RestartedViaAlarm"
            restartIntent.flags = Intent.FLAG_RECEIVER_FOREGROUND
            val alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
            val restartServiceHandler = @SuppressLint("HandlerLeak")
            object : Handler() {
                override fun handleMessage(msg: Message) {
                    val pendingIntent = PendingIntent.getBroadcast(applicationContext, 87, restartIntent, PendingIntent.FLAG_CANCEL_CURRENT)
                    val timer = System.currentTimeMillis() + restartAlarmInterval
                    val sdkInt = Build.VERSION.SDK_INT
                    if (sdkInt < Build.VERSION_CODES.KITKAT)
                        alarmMgr.set(AlarmManager.RTC_WAKEUP, timer, pendingIntent)
                    else if (Build.VERSION_CODES.KITKAT <= sdkInt && sdkInt < Build.VERSION_CODES.M)
                        alarmMgr.setExact(AlarmManager.RTC_WAKEUP, timer, pendingIntent)
                    else if (sdkInt >= Build.VERSION_CODES.M) {
                        alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timer, pendingIntent)
                    }
                    sendEmptyMessageDelayed(0, resetAlarmTimer)
                    stopSelf()
                }
            }
            restartServiceHandler.sendEmptyMessageDelayed(0, 0)
        }

    }
请分享您的建议,如果您遇到类似问题并成功找到解决方案。

你是否正在为你的通知创建通知渠道? - Sahil
@Sahil 是的,我正在为Oreo创建通知渠道。 - Sagar Trehan
@SagarTrehan,你能解决这个问题吗? - FreakyAli
@SagarTrehan 你找到任何解决方案了吗? - Boris Salimov
你得到了任何解决方案吗? - SAOUD SALIH HASSAN
6个回答

25

我已经分析了OnePlus的问题,因为我和您处于同样的情况。目前看来,没有解决方案。OnePlus显然遵循了不好的做法。

由于他们没有发布以这种方式终止进程的源代码,所以我下载了一个OnePlus ROM(我选择了OnePlus 5T 5.1.5),将其提取出来,找到执行此操作的.class文件(services.vdex中的OnePlusHighPowerDetector.class),对其进行反编译,并试图弄清楚发生了什么。

您可以在此处找到此类的版本(它不是我创建的,也可能不是我使用的相同版本):https://github.com/joshuous/oneplus_blobs_decompiled/blob/master/com/android/server/am/OnePlusHighPowerDetector.java

不幸的是,最重要的函数无法成功反编译。但是我们仍然可以分析字节码。以下是我发现的内容:

  • OnePlus无情地终止后台进程(似乎无论其是否具有前台服务)。几乎所有进程都会被终止。不管使用CPU资源有多少,都没关系。我的应用程序使用的CPU资源不到1%,但也会被终止。
  • 如果应用程序在最近的应用程序列表中被固定(由com_oneplus_systemui_recent_task_lockd_list列表确定),则不会被终止。因此,用户可以通过将其固定来保存应用程序。但这对他们来说很不方便。
  • Oneplus提供了一个他们不会关闭的进程列表。该列表在oneplus-framework-res.apk/res/values/array.xml中,其键为string-array name="backgroundprocess_detection_app_whitelist"。该列表主要包含地图和健身应用程序1
  • 也许还有其他因素可以保存进程不被杀死,我没有进一步分析该问题。如果您有时间,请查看OnePlusHighPowerDetector.java。

  • 以下是反编译.vdex文件所需的步骤:

    • 使用Vdex Extractor从.vdex创建.dex文件(也许您需要使用--ignore-crc-error选项)
    • 使用JADX反编译.dex文件(目前我认为这是最好的反编译工具),或使用dex2jar从.dex文件创建.jar文件,并使用任何反编译工具来反编译.jar文件

    1抱怨:这显示出当前情况有多糟糕。 OnePlus,这真的是解决方案吗?你选择了10-15个可以在后台运行的应用程序,而其他所有应用程序都不在乎吗?这样如何创建一个在您的设备上安全工作的新健身/地图/音乐播放器应用程序?


1
我想进行类似的调查。你使用什么工具将 .vdex 反编译为 Java?请帮忙。 - Ranjith KP
你有得到任何解决方案吗? - SAOUD SALIH HASSAN

10

我花了几个小时解决了这个问题。小米手机,Oreo系统版本。我的解决方案有两个步骤:

  1. 小米手机需要额外的权限:设置->权限->自启动管理。这里列出了可以自动启动的应用程序列表。BOOT_COMPLETED和START_STICKY是不够的。Facebook、Outlook、Skype和我的应用程序都在列表中。我不知道如何通过程序向用户请求授予此权限。

  2. 服务会被重新创建但不会被重新启动,即使你返回START_STICKY。当你杀掉应用程序时(从正在运行的应用程序中滑出),onCreate会再次被调用,但不会调用onStartCommand。尝试使用Log.d()进行查看。我将所有逻辑都移到了onCreate中,除了创建通知、调用startForeground()和返回START_STICKY之外,这些必须保留在onStartCommand()中。

电池优化也必须对该应用程序禁用,但这已经是Android之前的版本所要求的。

现在,我的服务保持运行或在几秒钟内被重新创建并成功运行。


1
第一步在编程上是否可行?如何检测自动启动已启用或已禁用? - Azay Gupta

3

我在我的三星Galaxy S20+ 5G设备上通过在清单文件中为我的服务添加foregroundServiceType属性来解决了这个问题:

<service android:name=".RunningService"
        android:foregroundServiceType="dataSync"/>

现在我的服务一直在线。


0

我在我的OnePlus3手机上遇到了同样的问题。然后我在logcat中注意到了这些行,每次我的应用程序和前台服务被杀死时:

10-12 18:30:40.644  1439  1519 I OHPD    : [BgDetect]force stop com.mycompany.MyAndroidApp (uid 10905) level 0
10-12 18:30:40.645  1439  1519 I ActivityManager: Force stopping com.mycompany.MyAndroidApp appid=10905 user=0: from pid 1439
10-12 18:30:40.645  1439  1519 I ActivityManager: Killing 23139:com.mycompany.MyAndroidApp/u0a905 (adj 200): stop com.mycompany.MyAndroidApp
10-12 18:30:40.647  1439  1519 W ActivityManager: Scheduling restart of crashed service com.mycompany.MyAndroidApp/md55852635cfc9dced970aa39ae09e74ead.MyForegroundService in 1000ms
10-12 18:30:40.651  1439  1519 I ActivityManager:   Force finishing activity ActivityRecord{a5c8d39 u0 com.mycompany.MyAndroidApp/md55852635cfc9dced970aa39ae09e74ead.MainActivity t3108}
10-12 18:30:40.655  1439  1519 I ActivityManager:   Force stopping service ServiceRecord{6052f94 u0 com.mycompany.MyAndroidApp/md55852635cfc9dced970aa39ae09e74ead.MyForegroundService}
10-12 18:30:40.660  1439  1519 I OHPD    : [BgDetect]chkExcessCpu level: 1 doKills: true critical false uptime: 300328
10-12 18:30:40.710  1439  8213 I WindowManager: WIN DEATH: Window{c381342 u0 com.mycompany.MyAndroidApp/md55852635cfc9dced970aa39ae09e74ead.MainActivity}

然后我在互联网上搜索关键词“OHPD: [BgDetect]chkExcessCpu level: 1 doKills: true”,找到了这个SO答案:

防止后台服务因“检测到分叉进程的过度CPU”而被杀死

如该答案和GitHub问题所建议的,锁定/固定我的应用程序可以防止氧气OS杀死我的应用程序(MainActivity和Forground Service)。


0

在你的服务中使用startForeground(id, notification)方法。

用户应该知道,有些东西正在后台运行,无法被系统杀死,因此Android系统应该通过通知告知用户,以便用户知道是什么在消耗他的电池。

顺便提一下,你可以使用WakeLock

 PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
 PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PARTIAL_WAKE_LOCK, 
 TAG_FOR_DEBUG);
 wakeLock.acquire()

在使用之前,请先了解一下WakeLock。并且在工作完成后不要忘记将其禁用。
您可以在终端中使用adb shell dumpsys power命令来检查当前启用的唤醒锁。

感谢您的帮助。我正在服务启动后立即调用startForeground()函数。 - Sagar Trehan
关于您的问题,我添加了更多的建议。 - Viktor Vasyliev
顺便问一下,你在终端中检查了你的唤醒锁吗? - Viktor Vasyliev
2
关于“BroadcastReceiver上的WakeLock”:一旦进程被操作系统杀死,我就无法接收广播。我将使用您分享的命令检查wakelock,并告诉您结果。 - Sagar Trehan
@ViktorVasyliev 在我的情况下,当服务终止时,我必须显示通知,例如“您的任务已终止,请在电池设置中将应用程序加入白名单”。 - Thom
显示剩余3条评论

-1
在OnePlus 6T上,运行以下ADB命令:
setting put system com_oneplus_systemui_recent_task_lockd_list {com.topjohnwu.magisk/a.c#0}{com.mycompany.MyAndroidApp/com.mycompany.MyAndroidApp.MainActivity#0}
这将防止杀死com.topjohnwu.magisk和com.mycompany.MyAndroidApp。

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