当前位置: 首页 > article >正文

[Android]让APP进入后台后不被杀掉(保活)

在 Android 系统中,应用在进入后台后,系统可能会因为各种原因(如内存不足、电池优化等)对其进行强制退出。虽然无法完全保证应用永远不会被系统强制退出,但可以采取一些措施来减少这种情况的发生。以下是几种常见的方法:

1. 使用前台服务 (Foreground Service)

前台服务可以让系统知道你的应用正在执行一些重要的任务,即使在后台也不会轻易被杀掉。你可以在应用启动时创建一个前台服务。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.mofang.call.callphone">

    <!-- 声明前台服务的权限 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <application
        android:name=".App"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:extractNativeLibs="true"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true"
        android:roundIcon="@mipmap/ic_launcher"
        android:supportsRtl="true"
        android:theme="@style/Theme.MFCallPhone"
        android:usesCleartextTraffic="true"
        tools:targetApi="31">

        <service
            android:name=".MyForegroundService"
            android:foregroundServiceType="location|dataSync|mediaPlayback"
            android:permission="android.permission.FOREGROUND_SERVICE"
            tools:ignore="ForegroundServicePermission" />
  
    </application>
</manifest>

MyForegroundService

import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat

class MyForegroundService : Service() {
    companion object {
        const val CHANNEL_ID = "ForegroundServiceChannel"
    }

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
        startForegroundService()
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val serviceChannel = NotificationChannel(
                CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val manager = getSystemService(NotificationManager::class.java)
            manager.createNotificationChannel(serviceChannel)
        }
    }

    @SuppressLint("ForegroundServiceType")
    private fun startForegroundService() {
        val notificationIntent = Intent(this, StartPageActivity::class.java) // MainActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
            PendingIntent.FLAG_IMMUTABLE) // 0
        val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
            //.setContentTitle("前台服务")
            //.setContentText("前台保活服务") // 你的应用程序在前台运行
            .setSmallIcon(R.mipmap.icon_home_2_s)
            .setContentIntent(pendingIntent)
            .build()

        startForeground(1, notification)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 在这里处理服务启动请求
        // START_NOT_STICKY,表示如果服务被系统杀掉,不会自动重启。如果系统因为内存不足而杀掉了服务,它不会自动重启服务。这适用于不需要保证服务持续运行的场景。
        // START_STICKY,如果系统因为内存不足而杀掉了服务,它会在资源可用时自动重启服务,但不重新传递最后一个 Intent。这适用于不需要传递数据给服务的场景,例如音乐播放服务。
        // START_REDELIVER_INTENT,如果系统因为内存不足而杀掉了服务,它会在资源可用时自动重启服务,并重新传递最后一个 Intent 给服务。这适用于需要保证任务完成的场景,例如文件下载服务。
        return START_STICKY
    }

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

启动前台服务:

/// 保活
private fun baohuo() {
    // 启动前台服务
    val serviceIntent = Intent(this, MyForegroundService::class.java)
    startService(serviceIntent)
    // 绑定服务
    val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            NSLog("Service connected")
        }
        override fun onServiceDisconnected(name: ComponentName?) {
            NSLog("Service disconnected")
        }
    }
    bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
}   

2. 使用 WorkManager

WorkManager 是一个推荐的处理后台任务的工具,特别是需要确保任务在系统重启后或在长时间间隔后还能继续执行的任务。它可以自动处理不同版本的设备和电池优化策略。

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.work.Constraints
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.mofang.call.callphone.utils.NSLog
import java.util.concurrent.TimeUnit

/// 2. 使用 WorkManager
/// 应用被杀掉,WorkManager 仍然会继续执行已调度的任务。WorkManager 旨在提供可靠的后台执行,即使应用被杀掉或设备重启,它也会尝试重新执行任务。
var curCountt: Double = 0.0
class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    override fun doWork(): Result {
        // 这里是你需要执行的任务
        curCountt += 1
        NSLog("MyWorker is running = $curCountt")
        // 调度下一个任务
        scheduleNextWork()
        // 如果任务成功完成,返回 Result.success()
        return Result.success()
    }

    private fun scheduleNextWork() {
        val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
            .setInitialDelay(1, TimeUnit.MINUTES)  // 设置延迟时间
            .setConstraints(
                Constraints.Builder()
                    .setRequiresCharging(false) // 不需要设备充电
                    .setRequiresBatteryNotLow(false) // true电池电量不低
                    .build()
            )
            .build()

        WorkManager.getInstance(applicationContext).enqueue(workRequest)
    }

}
/// 保活
private fun baohuo() {
    // 请求忽略电池优化
    requestIgnoreBatteryOptimizations(this)

    // WorkManager
    val sharedPreferences = getSharedPreferences(KEY_PREFS_NAME, Context.MODE_PRIVATE)
    val workDispatched = sharedPreferences.getBoolean(KEY_WORK_DISPATCHED, false)
    if (!workDispatched) {
        NSLog("111")
        // 创建初始任务请求
        val initialWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
            .setInitialDelay(1, TimeUnit.MINUTES)  // 设置延迟时间
            .setConstraints(
                Constraints.Builder()
                    .setRequiresCharging(false) // 不需要设备充电
                    .setRequiresBatteryNotLow(false) // true电池电量不低
                    .build()
            )
            .build()
        // 调度初始任务
        WorkManager.getInstance(this).enqueue(initialWorkRequest)

        // 更新状态
        sharedPreferences.edit().putBoolean(KEY_WORK_DISPATCHED, true).apply()
    } else {
        //NSLog("222")
    }
}

3. 避免电池优化

你可以请求用户将你的应用加入电池优化白名单。用户可以手动在设置中添加,也可以通过代码请求用户取消电池优化。

import android.content.Intent
import android.os.Build
import android.provider.Settings
import android.content.Context
import android.content.pm.PackageManager

/// 3. 避免电池优化
/// 请求用户将你的应用加入电池优化白名单。用户可以手动在设置中添加,也可以通过代码请求用户取消电池优化。
fun requestIgnoreBatteryOptimizations(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val packageName = context.packageName
        // 通过 Context 获取 PowerManager 实例,用于检查和请求忽略电池优化。
        val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
        // 检查当前应用是否被忽略电池优化. 使用 PowerManager 的 isIgnoringBatteryOptimizations 方法检查当前应用是否已被忽略电池优化。如果未被忽略,则继续执行请求忽略电池优化的操作。
        if (!pm.isIgnoringBatteryOptimizations(packageName)) {
            // 创建一个 Intent,其动作为 Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,并将数据设置为当前应用的包名。然后启动这个 Intent,请求用户允许应用忽略电池优化。
            val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
            intent.data = Uri.parse("package:$packageName")
            NSLog("请求用户允许应用忽略电池优化")
            context.startActivity(intent)
        }
    }
}
/// 保活
private fun baohuo() {
    // 请求忽略电池优化
    requestIgnoreBatteryOptimizations(this)
}        

4. 使用 JobScheduler

JobScheduler 是另一个适用于 Android 5.0 及以上版本的后台任务工具。

import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.content.ComponentName
import android.content.Context

fun scheduleJob(context: Context) {
    val componentName = ComponentName(context, MyJobService::class.java)
    val jobInfo = JobInfo.Builder(1, componentName)
        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
        .setPersisted(true) // 确保任务在设备重启后继续存在
        .build()

    val jobScheduler = context.getSystemService(JobScheduler::class.java)
    jobScheduler.schedule(jobInfo)
}

5. 使用 AlarmManager

在极端情况下,你可以使用 AlarmManager 定期触发一些操作,确保应用在后台不会被完全忽视。

import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent

fun scheduleAlarm(context: Context) {
    val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    val intent = Intent(context, MyBroadcastReceiver::class.java)
    val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)

    alarmManager.setRepeating(
        AlarmManager.RTC_WAKEUP,
        System.currentTimeMillis() + 60000, // 设定延迟时间
        60000, // 重复间隔
        pendingIntent
    )
}

6. 定期保存应用状态

即使采取了以上措施,系统仍可能在某些情况下杀掉你的应用。确保在应用被杀掉时能够适当地保存和恢复关键任务和状态是很重要的。

override fun onSaveInstanceState(outState: Bundle) {
    // 保存关键数据
    outState.putString("key", "value")
    super.onSaveInstanceState(outState)
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)
    // 恢复关键数据
    val value = savedInstanceState.getString("key")
}

注意:

通过以上方法,你可以尽量减少应用在后台被系统强制退出的可能性,但无法完全避免。确保应用在被杀掉后能够正确恢复是一个更为实际和稳妥的方案。


http://www.kler.cn/a/558286.html

相关文章:

  • 小游戏-记忆卡牌
  • Golang中如何正确close channel
  • PHP 安全与加密:守护 Web 应用的基石
  • Python常见面试题的详解15
  • 从零到一:构建现代 React 应用的完整指南
  • GO系列-IO 文件操作
  • wordpress adrotate插件 文件上传漏洞
  • 最新版本Exoplayer扩展FFmpeg音频软解码保姆级教程
  • 23种设计模式之《桥接模式(Bridge)》在c#中的应用及理解
  • SpringBoot3使用RestTemplate请求接口忽略SSL证书
  • 蓝桥备赛(一)- C++入门(上)
  • Unity贴图与模型相关知识
  • PostgreSQL‘会用‘到‘精通‘,学习感悟
  • 《论云上自动化运维及其应用》审题技巧 - 系统架构设计师
  • [原创](Modern C++)现代C++的关键性概念: std::span, 低内存开销的方式来操作大数据.
  • C语言--正序、逆序输出为奇数的位。
  • Spring Boot:开启快速开发新时代
  • 23种设计模式之《组合模式(Composite)》在c#中的应用及理解
  • ETL工具: Kettle入门(示例从oracle到oracle的数据导入)
  • 51单片机学习——动态数码管显示