-
Notifications
You must be signed in to change notification settings - Fork 389
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[example][android] Add foreground service example
- Loading branch information
1 parent
49e55bf
commit 32af54a
Showing
7 changed files
with
277 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
example/android/app/src/main/kotlin/io/agora/agora_rtc_flutter_example/ExampleService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package io.agora.agora_rtc_ng_example | ||
|
||
import android.app.NotificationChannel | ||
import android.app.NotificationManager | ||
import android.app.PendingIntent | ||
import android.app.Service | ||
import android.content.Context | ||
import android.content.Intent | ||
import android.os.Binder | ||
import android.os.Build | ||
import android.os.Bundle | ||
import android.os.IBinder | ||
import androidx.core.app.NotificationCompat | ||
import androidx.core.content.ContextCompat | ||
|
||
class ExampleService: Service() { | ||
|
||
private val binder: Binder by lazy { | ||
Binder() | ||
} | ||
|
||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { | ||
return if (intent?.action == ACTION_START_FOREGROUND_SERVICE) { | ||
startForegroundService() | ||
START_REDELIVER_INTENT | ||
} else { | ||
stopForegroundService() | ||
START_NOT_STICKY | ||
} | ||
} | ||
|
||
override fun onTaskRemoved(rootIntent: Intent?) { | ||
super.onTaskRemoved(rootIntent) | ||
// If we receive the `onTaskRemoved`, we consider that the user want to close the APP, so | ||
// destroy the `FlutterEngine` here. | ||
(applicationContext as MyApplication).destroyFlutterEngine() | ||
|
||
stopForegroundService() | ||
} | ||
|
||
override fun onBind(intent: Intent): IBinder? { | ||
return binder | ||
} | ||
|
||
private fun createNotificationChannel( | ||
context: Context, | ||
channelId: String, | ||
name: String, | ||
des: String | ||
) { | ||
val notificationManager = | ||
ContextCompat.getSystemService( | ||
context, | ||
NotificationManager::class.java | ||
) as NotificationManager | ||
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
if (notificationManager.getNotificationChannel(channelId) == null) { | ||
val notificationChannel = NotificationChannel( | ||
channelId, | ||
name, | ||
NotificationManager.IMPORTANCE_DEFAULT | ||
) | ||
notificationChannel.setSound(null, null) | ||
notificationChannel.enableLights(false) | ||
notificationChannel.enableVibration(false) | ||
notificationChannel.description = des | ||
notificationManager.createNotificationChannel(notificationChannel) | ||
} | ||
} | ||
} | ||
|
||
private fun startForegroundService() { | ||
createNotificationChannel( | ||
applicationContext, | ||
"MyChannelId", | ||
"agora_rtc_engine_example", | ||
"agora_rtc_engine_example running" | ||
) | ||
|
||
val notificationBuilder = | ||
NotificationCompat | ||
.Builder(this, "MyChannelId") | ||
.setSmallIcon(R.mipmap.ic_launcher) | ||
.setContentTitle("agora_rtc_engine_example") | ||
.setContentText("agora_rtc_engine_example running") | ||
.setWhen(System.currentTimeMillis()) | ||
.setPriority(NotificationCompat.PRIORITY_LOW) | ||
.setContentIntent(getContentIntent()) | ||
.setShowWhen(false) | ||
|
||
startForeground(NOTIFICATION_ID_DAEMON_SERVICE, notificationBuilder.build()) | ||
} | ||
|
||
private fun getContentIntent(): PendingIntent? { | ||
val intent = Intent(this, MainActivity::class.java).apply { | ||
action = "NOTIFICATION_OPEN" | ||
} | ||
return PendingIntent.getActivity( | ||
this, | ||
0, | ||
intent, | ||
PendingIntent.FLAG_IMMUTABLE | ||
) | ||
} | ||
|
||
private fun stopForegroundService() { | ||
stopForeground(true) | ||
stopSelf() | ||
} | ||
|
||
companion object { | ||
const val ACTION_START_FOREGROUND_SERVICE = "ACTION_START_FOREGROUND_SERVICE" | ||
private const val ACTION_STOP_FOREGROUND_SERVICE = "ACTION_STOP_FOREGROUND_SERVICE" | ||
|
||
const val NOTIFICATION_ID_DAEMON_SERVICE = 123 | ||
|
||
private var isServiceRunning = false | ||
|
||
fun startDaemonService(context: Context) { | ||
if (isServiceRunning) { | ||
return | ||
} | ||
try { | ||
val intent = Intent(context, ExampleService::class.java) | ||
intent.action = ACTION_START_FOREGROUND_SERVICE | ||
|
||
|
||
val bundle = Bundle() | ||
intent.putExtras(bundle) | ||
|
||
// Crash: startForegroundService must call start startForeground | ||
// This crash usually happens when background global configuration changes(like: Debug DisplayCut configuration changes) | ||
context.startService(intent) | ||
isServiceRunning = true | ||
} catch (e: Exception) { | ||
e.printStackTrace() | ||
} | ||
} | ||
|
||
fun stopDaemonService(context: Context) { | ||
if (!isServiceRunning) { | ||
return | ||
} | ||
try { | ||
val intent = Intent(context, ExampleService::class.java) | ||
intent.action = ACTION_STOP_FOREGROUND_SERVICE | ||
|
||
// Crash: startForegroundService must call start startForeground | ||
// This crash usually happens when background global configuration changes(like: Debug DisplayCut configuration changes) | ||
context.startService(intent) | ||
isServiceRunning = false | ||
} catch (e: Exception) { | ||
e.printStackTrace() | ||
} | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
example/android/app/src/main/kotlin/io/agora/agora_rtc_flutter_example/MainActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,41 @@ | ||
package io.agora.agora_rtc_ng_example | ||
|
||
import android.content.Context | ||
import io.flutter.embedding.android.FlutterActivity | ||
import io.flutter.embedding.engine.FlutterEngine | ||
import io.flutter.plugin.common.MethodChannel | ||
|
||
class MainActivity: FlutterActivity() { | ||
private lateinit var methodChannel: MethodChannel | ||
|
||
override fun provideFlutterEngine(context: Context): FlutterEngine? { | ||
return (context.applicationContext as MyApplication).getFlutterEngine() | ||
} | ||
|
||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { | ||
super.configureFlutterEngine(flutterEngine) | ||
|
||
methodChannel = MethodChannel(flutterEngine.dartExecutor, "agora_rtc_ng_example/foreground_service") | ||
methodChannel.setMethodCallHandler { call, result -> | ||
when (call.method) { | ||
"start_foreground_service" -> { | ||
ExampleService.startDaemonService(MainActivity@this.applicationContext) | ||
result.success(true) | ||
} | ||
"stop_foreground_service" -> { | ||
ExampleService.stopDaemonService(MainActivity@this.applicationContext) | ||
result.success(true) | ||
} | ||
else -> { | ||
result.notImplemented() | ||
} | ||
} | ||
} | ||
} | ||
|
||
override fun detachFromFlutterEngine() { | ||
super.detachFromFlutterEngine() | ||
|
||
methodChannel.setMethodCallHandler(null) | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
example/android/app/src/main/kotlin/io/agora/agora_rtc_flutter_example/MyApplication.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package io.agora.agora_rtc_ng_example | ||
|
||
import io.flutter.app.FlutterApplication | ||
import io.flutter.embedding.engine.FlutterEngine | ||
|
||
class MyApplication : FlutterApplication() { | ||
private var flutterEngine: FlutterEngine? = null | ||
|
||
fun getFlutterEngine(): FlutterEngine { | ||
return flutterEngine ?: FlutterEngine(this).also { flutterEngine = it } | ||
} | ||
|
||
fun destroyFlutterEngine() { | ||
flutterEngine?.destroy() | ||
flutterEngine = null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
example/lib/components/android_foreground_service_widget.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/services.dart'; | ||
|
||
class AndroidForegroundServiceWidget extends StatefulWidget { | ||
const AndroidForegroundServiceWidget({Key? key, required this.child}) : super(key: key); | ||
|
||
final Widget child; | ||
|
||
@override | ||
State<AndroidForegroundServiceWidget> createState() => _AndroidForegroundServiceWidgetState(); | ||
} | ||
|
||
class _AndroidForegroundServiceWidgetState extends State<AndroidForegroundServiceWidget> { | ||
|
||
final MethodChannel _channel = const MethodChannel('agora_rtc_ng_example/foreground_service'); | ||
|
||
@override | ||
void initState() { | ||
super.initState(); | ||
|
||
_channel.invokeMethod('start_foreground_service'); | ||
} | ||
|
||
@override | ||
void dispose() { | ||
_channel.invokeMethod('stop_foreground_service'); | ||
super.dispose(); | ||
} | ||
|
||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return widget.child; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters