这些天我一直在努力寻找在Android中运行永不停止服务的方法。这只是追求同一目标的指南。希望能帮助到你!
问题
由于Android 8.0(API级别26)中引入了Android电池优化,后台服务现在有一些重要的限制。基本上,一旦应用程序处于后台运行一段时间,它们就会被杀死,这使得它们对于运行永不停止运行的服务毫无价值。
根据Android的建议,我们应该使用JobScheduler,在作业运行时让手机保持清醒状态它似乎运行得很好,并且会为我们处理唤醒锁。
不幸的是,这是行不通的。最重要的是,JobScheduler将根据Android自行决定运行工作,一旦手机进入打盹模式,这些工作的运行频率将不断增加。甚至最糟糕的是,如果你想要访问网络 (你需要将数据发送到你的服务器)你将无法做到。查看打盹模式所施加的限制列表。
如果您不介意无法访问网络并且您不关心不控制周期性,JobScheduler也可以正常工作。在我们的例子中,我们希望我们的服务以非常特定的频率运行,永远不会停止,所以我们需要其他东西。
关于前台服务
如果你一直在寻找解决这个问题的互联网,你很可能最终从Android的文档到达了这个页面。
在那里,我们介绍了Android提供的不同类型的服务。看一下Foreground Service描述:
前台服务执行一些用户可以注意到的操作。例如,音频应用程序将使用前台服务播放音频轨。前台服务必须显示通知。即使用户不与应用程序交互,前台服务也会继续运行。
这似乎正是我们正在寻找的......确实如此!
我的代码
创建一个foreground service真正是一个简单的过程,所以我将访问并解释构建永不停止的前台服务所需的所有步骤。
像往常一样,我已经创建了一个包含所有代码的存储库,以防您想要查看它并跳过帖子的其余部分。
添加一些依赖项
我在这个例子中使用Kotlin,因此我们将利用协同程序和Fuel库来处理HTTP请求。
为了添加这些依赖项,我们必须将它们添加到我们的build.gradle文件中:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.jaredrummler:android-device-names:1.1.8' implementation 'com.github.kittinunf.fuel:fuel:2.1.0' implementation 'com.github.kittinunf.fuel:fuel-android:2.1.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M1' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'}
我们的service
Foreground Services需要显示通知,以便用户知道应用程序仍在运行。如果你考虑一下,这是有道理的。
请注意,我们必须覆盖一些处理服务生命周期关键方面的Service回调方法(callback methods)。
我们使用部分唤醒锁也非常重要,因此我们的服务永远不会受到打盹模式的影响。请记住,这会对我们手机的电池寿命产生影响,因此我们必须评估我们的用例是否可以通过Android提供的任何其他替代方案来处理,以便在后台运行流程。
代码中有一些实用函数调用(log,setServiceState)和一些自定义枚举(ServiceState.STARTED),但不要太担心。如果您想了解它们的来源,请查看示例存储库。
class EndlessService : Service() { private var wakeLock: PowerManager.WakeLock? = null private var isServiceStarted = false override fun onBind(intent: Intent): IBinder? { log("Some component want to bind with the service") // We don't provide binding, so return null return null } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { log("onStartCommand executed with startId: $startId") if (intent != null) { val action = intent.action log("using an intent with action $action") when (action) { Actions.START.name -> startService() Actions.STOP.name -> stopService() else -> log("This should never happen. No action in the received intent") } } else { log( "with a null intent. It has been probably restarted by the system." ) } // by returning this we make sure the service is restarted if the system kills the service return START_STICKY } override fun onCreate() { super.onCreate() log("The service has been created".toUpperCase()) var notification = createNotification() startForeground(1, notification) } override fun onDestroy() { super.onDestroy() log("The service has been destroyed".toUpperCase()) Toast.makeText(this, "Service destroyed