问题
关于前台服务
Foreground Service
描述:前台服务执行一些用户可以注意到的操作。例如,音频应用程序将使用前台服务播放音频轨。前台服务必须显示通知。即使用户不与应用程序交互,前台服务也会继续运行。
我的代码
foreground service
真正是一个简单的过程,所以我将访问并解释构建永不停止的前台服务所需的所有步骤。添加一些依赖项
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
需要显示通知,以便用户知道应用程序仍在运行。如果你考虑一下,这是有道理的。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", Toast.LENGTH_SHORT).show()
}
private fun startService() {
if (isServiceStarted) return
log("Starting the foreground service task")
Toast.makeText(this, "Service starting its task", Toast.LENGTH_SHORT).show()
isServiceStarted = true
setServiceState(this, ServiceState.STARTED)
// we need this lock so our service gets not affected by Doze Mode
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply {
acquire()
}
}
// we're starting a loop in a coroutine
GlobalScope.launch(Dispatchers.IO) {
while (isServiceStarted) {
launch(Dispatchers.IO) {
pingFakeServer()
}
delay(1 * 60 * 1000)
}
log("End of the loop for the service")
}
}
private fun stopService() {
log("Stopping the foreground service")
Toast.makeText(this, "Service stopping", Toast.LENGTH_SHORT).show()
try {
wakeLock?.let {
if (it.isHeld) {
it.release()
}
}
stopForeground(true)
stopSelf()
} catch (e: Exception) {
log("Service stopped without being started: ${e.message}")
}
isServiceStarted = false
setServiceState(this, ServiceState.STOPPED)
}
private fun pingFakeServer() {
val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.mmmZ")
val gmtTime = df.format(Date())
val deviceId = Settings.Secure.getString(applicationContext.contentResolver, Settings.Secure.ANDROID_ID)
val json =
"""
{
"deviceId": "$deviceId",
"createdAt": "$gmtTime"
}
"""
try {
Fuel.post("https://jsonplaceholder.typicode.com/posts")
.jsonBody(json)
.response { _, _, result ->
val (bytes, error) = result
if (bytes != null) {
log("[response bytes] ${String(bytes)}")
} else {
log("[response error] ${error?.message}")
}
}
} catch (e: Exception) {
log("Error making the request: ${e.message}")
}
}
private fun createNotification(): Notification {
val notificationChannelId = "ENDLESS SERVICE CHANNEL"
// depending on the Android API that we're dealing with we will have
// to use a specific method to create the notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
val channel = NotificationChannel(
notificationChannelId,
"Endless Service notifications channel",
NotificationManager.IMPORTANCE_HIGH
).let {
it.description = "Endless Service channel"
it.enableLights(true)
it.lightColor = Color.RED
it.enableVibration(true)
it.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
it
}
notificationManager.createNotificationChannel(channel)
}
val pendingIntent: PendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
val builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Notification.Builder(
this,
notificationChannelId
) else Notification.Builder(this)
return builder
.setContentTitle("Endless Service")
.setContentText("This is your favorite endless service working")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker("Ticker text")
.setPriority(Notification.PRIORITY_HIGH) // for under android 26 compatibility
.build()
}
}
是时候处理Android Manifest了
FOREGROUND_SERVICE
,INTERNET
和WAKE_LOCK
。请确保您不要忘记包含它们,因为它不会起作用。<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.robertohuertas.endless">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".EndlessService"
android:enabled="true"
android:exported="false">
</service>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
启动这项服务
MainActivity
,只有一个屏幕,有两个按钮来启动和停止服务。这就是您开始我们永不停止的服务所需的一切。class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "Endless Service"
findViewById<Button>(R.id.btnStartService).let {
it.setOnClickListener {
log("START THE FOREGROUND SERVICE ON DEMAND")
actionOnService(Actions.START)
}
}
findViewById<Button>(R.id.btnStopService).let {
it.setOnClickListener {
log("STOP THE FOREGROUND SERVICE ON DEMAND")
actionOnService(Actions.STOP)
}
}
}
private fun actionOnService(action: Actions) {
if (getServiceState(this) == ServiceState.STOPPED && action == Actions.STOP) return
Intent(this, EndlessService::class.java).also {
it.action = action.name
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
log("Starting the service in >=26 Mode")
startForegroundService(it)
return
}
log("Starting the service in < 26 Mode")
startService(it)
}
}
}
效果:在Android启动时启动服务
StartReceiver
。class StartReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED && getServiceState(context) == ServiceState.STARTED) {
Intent(context, EndlessService::class.java).also {
it.action = Actions.START.name
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
log("Starting the service in >=26 Mode from a BroadcastReceiver")
context.startForegroundService(it)
return
}
log("Starting the service in < 26 Mode from a BroadcastReceiver")
context.startService(it)
}
}
}
}
Android Manifest
并添加一个新的权限(RECEIVE_BOOT_COMPLETED
)和我们新的BroadCastReceiver。<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.robertohuertas.endless">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".EndlessService"
android:enabled="true"
android:exported="false">
</service>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:enabled="true" android:name=".StartReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
adb root
# If you get an error then you're not running the proper emulator.
# Be sure to stop the service
# and force a system restart:
adb shell stop
adb shell start
# wait for the service to be restarted!
推荐↓↓↓
长
按
关
注
?【16个技术公众号】都在这里!
涵盖:程序员大咖、源码共读、程序员共读、数据结构与算法、黑客技术和网络安全、大数据科技、编程前端、Java、Python、Web编程开发、Android、iOS开发、Linux、数据库研发、幽默程序员等。