2. Service
文档
Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件.服务可由其他应用组件启动, 而且即使用户切换到其他应用, 服务仍将在后台继续运行.此外, 组件可通过绑定到服务与之进行交互, 甚至是执行进程间通信 (IPC).例如, 服务可在后台处理网络事务 播放音乐, 执行文件 I/O 或与内容提供程序进行交互.
以下是三种不同的服务类型:
-
前台 前台服务执行一些用户能注意到的操作.例如, 音频应用会使用前台服务来播放音频曲目.前台服务必须显示通知.即使用户停止与应用的交互, 前台服务仍会继续运行.
-
后台 后台服务执行用户不会直接注意到的操作.例如, 如果应用使用某个服务来压缩其存储空间, 则此服务通常是后台服务.
注意: 如果您的应用面向 API 级别 26 或更高版本, 当应用本身未在前台运行时, 系统会对运行后台服务施加限制.在诸如此类的大多数情况下, 您的应用应改为使用计划作业.
- 绑定
当应用组件通过调用
bindService()绑定到服务时, 服务即处于绑定状态.绑定服务会提供客户端-服务器接口, 以便组件与服务进行交互 发送请求 接收结果, 甚至是利用进程间通信 (IPC) 跨进程执行这些操作.仅当与另一个应用组件绑定时, 绑定服务才会运行.多个组件可同时绑定到该服务, 但全部取消绑定后, 该服务即会被销毁.
基础/生命周期
简单地说, 服务是一种即使用户未与应用交互也可在后台运行的组件, 因此, 只有在需要服务时才应创建服务.
如果您必须在主线程之外执行操作, 但只在用户与您的应用交互时执行此操作, 则应创建新线程.例如, 如果您只是想在 Activity 运行的同时播放一些音乐, 则可在 onCreate() 中创建线程, 在 onStart() 中启动线程运行, 然后在 onStop() 中停止线程.您还可考虑使用 AsyncTask 或 HandlerThread, 而非传统的 Thread 类.如需了解有关线程的详细信息, 请参阅进程和线程文档.
请记住, 如果您确实要使用服务,则默认情况下, 它仍会在应用的主线程中运行, 因此, 如果服务执行的是密集型或阻止性操作, 则您仍应在服务内创建新线程.
启动服务由另一个组件通过调用 startService() 启动, 这会导致调用服务的 onStartCommand() 方法.
服务启动后, 其生命周期即独立于启动它的组件.即使系统已销毁启动服务的组件, 该服务仍可在后台无限期地运行.因此, 服务应在其工作完成时通过调用 stopSelf() 来自行停止运行, 或者由另一个组件通过调用 stopService() 来将其停止.
onStartCommand()
当另一个组件 (如 Activity) 请求启动服务时, 系统会通过调用 startService() 来调用此方法.执行此方法时, 服务即会启动并可在后台无限期运行.如果您实现此方法, 则在服务工作完成后, 您需负责通过调用 stopSelf() 或 stopService() 来停止服务. (如果您只想提供绑定, 则无需实现此方法.)
onBind()
当另一个组件想要与服务绑定 (例如执行 RPC) 时, 系统会通过调用 bindService() 来调用此方法.在此方法的实现中, 您必须通过返回 IBinder 提供一个接口, 以供客户端用来与服务进行通信.请务必实现此方法; 但是, 如果您并不希望允许绑定, 则应返回 null.
onCreate()
首次创建服务时, 系统会 (在调用 onStartCommand() 或 onBind() 之前) 调用此方法来执行一次性设置程序.如果服务已在运行, 则不会调用此方法.
onDestroy()
当不再使用服务且准备将其销毁时, 系统会调用此方法.服务应通过实现此方法来清理任何资源, 如线程 注册的侦听器 接收器等.这是服务接收的最后一个调用.
注册
首先在 AndroidManifest.xml 注册
<manifest ... >
...
<application ... >
<service android:name=".serivce.LinstenService"
<!-- 前台服务类型 安卓14 必须-->
android:foregroundServiceType="location"
/>
...
</application>
</manifest>如果应用以 Android 9(API 级别 28)或更高版本为目标平台并使用前台服务,则需要在应用清单中请求 FOREGROUND_SERVICE,如以下代码段所示。这是普通权限,因此,系统会自动为请求权限的应用授予此权限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application ...>
...
</application>
</manifest>前台 Service
如果您希望用户无法关闭通知,请在使用 Notification.Builder 创建通知时将 true 传入 setOngoing() 方法。
在该服务内(通常在 onStartCommand() 中),您可以请求在前台运行您的服务。为此,请调用 ServiceCompat.startForeground()(在 androidx-core 1.12 及更高版本中提供)。此方法采用以下参数:
- 服务
- 一个正整数,用于唯一标识状态栏中的通知
Notification对象本身- 前台服务类型,用于标识服务完成的工作
这些类型可能是清单中声明的类型的子集,具体取决于特定用例。然后,如果需要添加更多服务类型,可以再次调用 startForeground()。
以下是前台服务的示例:
class ForegroundService : Service() {
private var notificationManager: NotificationManager? = null
companion object{
private const val NOTIFICATION_ID = 1
private const val CHANNEL_ID = "my_foreground_service_channel"
}
override fun onCreate() {
super.onCreate()
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
}
@SuppressLint("ForegroundServiceType")
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
XLog.i("前台服务 onStartCommand ")
val channel = NotificationChannel(
CHANNEL_ID,
"前台服务已启用",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager?.createNotificationChannel(channel)
// 构建服务
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("服务标题")
.setContentText("This is a foreground service running.")
.setSmallIcon(R.drawable.btn_star)
.build()
startForeground(NOTIFICATION_ID, notification)
return START_STICKY
}
.........
启用
private fun initService() {
var requeset = PermissionRequest.Builder(this, 101, Manifest.permission.FOREGROUND_SERVICE)
.setRationale("需要通知权限")
.setPositiveButtonText("允许")
.setNegativeButtonText("拒绝")
.build()
requestPermission(requeset){
//前台通知
val serviceIntent = Intent(
this,
ForegroundService::class.java
)
startForegroundService(serviceIntent)
}
}
public fun requestPermission(request: PermissionRequest, callback: Consumer<Boolean>) {
val requestCode = request.requestCode.toString()
if (EasyPermissions.hasPermissions(this, *request.perms) ){
//已有权限
callback.accept( true)
}else{
EasyPermissions.requestPermissions( request);
permissionCallbacks[requestCode] = callback
}
}后台 Service
https://developer.android.google.cn/develop/background-work/services?hl=en
// 启动服务
Log.i(TAG," === initService")
val serviceIntent = Intent(this, LatlngService::class.java)
startService(serviceIntent)绑定服务
https://developer.android.google.cn/develop/background-work/services/bound-services?hl=zh-cn
绑定服务是客户端-服务器接口中的服务器。它允许 activity 等组件绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。
关键方法:
onBind()
当另一个组件想要与服务绑定 (例如执行 RPC) 时, 系统会通过调用 bindService() 来调用此方法.在此方法的实现中, 您必须通过返回 IBinder 提供一个接口, 以供客户端用来与服务进行通信.请务必实现此方法; 但是, 如果您并不希望允许绑定, 则应返回 null.
您可以将多个客户端同时连接到某项服务。但是,系统会缓存 IBinder 服务通信通道。换句话说,只有在第一个客户端绑定时,系统才会调用服务的 [onBind()](https://developer.android.google.cn/reference/android/app/Service?hl=zh-cn#onBind(android.content.Intent)) 方法以生成 IBinder。然后,系统会将该 IBinder 传递至绑定到同一服务的所有其他客户端,无需再次调用 onBind()。
在你的 Activity 中
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private var myService: MyService? = null
private var isBound: Boolean = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as MyService.MyBinder
myService = binder.getService()
isBound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
isBound = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent = Intent(this, MyService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
// 在需要调用服务的地方调用此方法
private fun callServiceMethod() {
if (isBound) {
val result = myService?.performTask()
// 处理服务返回的结果
}
}
override fun onDestroy() {
super.onDestroy()
if (isBound) {
unbindService(connection)
isBound = false
}
}
}
class MyService : Service() {
// 内部类用于绑定服务的通信
inner class MyBinder : Binder() {
fun getService(): MyService = this@MyService
}
private val binder = MyBinder()
override fun onBind(intent: Intent?): IBinder? {
return binder
}
// 服务中的方法,可以在 Activity 中调用
fun performTask(): String {
return "Task performed by the service"
}
}完整前台通知服务代码
前台通知 实现
public class LinstenService extends Service implements Runnable {
private static final String TAG = "LinstenService";
private static final int F_NOTIFY_ID = 1;
private static final String CHANNEL_ID = "LinstenService";
private static final String CHANNEL_NAME = "Channel One";
....
/**
* 电源管理 保证CPU 不休眠 需要 WAKE_LOCK 权限 很耗电量 ...
* https://developer.android.google.cn/reference/kotlin/android/os/PowerManager
*/
private PowerManager.WakeLock wakeLock = null;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
/**
* 前台服务
* foreground service
*/
Notification.Builder localBuilder = new Notification.Builder(this);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT > 26) {//android 8+
NotificationChannel notificationChannel = null;
notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setShowBadge(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationChannel.setSound(null, null);
notificationManager.createNotificationChannel(notificationChannel);
localBuilder.setChannelId(CHANNEL_ID);
}
localBuilder.setAutoCancel(false);
localBuilder.setContentTitle("LinstenService");
localBuilder.setContentText("正在运行...");
localBuilder.setSmallIcon(R.mipmap.ic_launcher);
// localBuilder.setDefaults(Notification.DEFAULT_ALL); // notify sound
// localBuilder.setLights(Color.GREEN, 1000, 2000); // notify light
startForeground(F_NOTIFY_ID, localBuilder.build());//foreground service 为前台
}
....
/**
* 简单 通知
*/
public void takeNotify(SampleBean bean) {
final String channel_id = "takeNotify";
final String channel_name = "Channel_2";
//PARTIAL_WAKE_LOCK only cpu,
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, LinstenService.class.getName());
wakeLock.acquire();//保持 点亮屏幕 CPU 不休眠
wakeLock.release();//释放
Notification.Builder localBuilder = new Notification.Builder(this);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT > 26) {//android 8+
NotificationChannel notificationChannel = null;
notificationChannel = new NotificationChannel(channel_id, channel_name, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setShowBadge(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationChannel.setSound(null, null);
notificationManager.createNotificationChannel(notificationChannel);
localBuilder.setChannelId(channel_id);
}
localBuilder.setDefaults(Notification.DEFAULT_ALL); // notify sound
localBuilder.setLights(Color.GREEN, 1000, 2000);// notify light
localBuilder.setSmallIcon(R.mipmap.ic_launcher);
localBuilder.setAutoCancel(false);
localBuilder.setContentTitle("出库通知");
localBuilder.setContentText(bean.getName() + ":出库待确认");
notificationManager.notify(bean.getId(), localBuilder.build());
}
.....
}
注意: android 9+ 需FOREGROUND_SERVICE权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
Context context = getApplicationContext();
final Intent intent = new Intent(MainActivity.this, AgentxService.class);
context.startForegroundService(intent);系统通知
通知是指 Android 在应用界面之外显示的消息, 旨在向用户提供提醒 来自他人的通信信息或应用中的其他及时信息.用户可以点按通知来打开应用, 或直接从通知中执行操作.
普通通知
final String channel_id = "returnNotify";
final String channel_name = "Channel_3";
//PARTIAL_WAKE_LOCK only cpu,
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, LinstenService.class.getName());
wakeLock.acquire();//保持 点亮屏幕 CPU 不休眠
wakeLock.release();//释放
Notification.Builder localBuilder = new Notification.Builder(this);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT > 26) {//android 8+
NotificationChannel notificationChannel = null;
notificationChannel = new NotificationChannel(channel_id, channel_name, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setShowBadge(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationChannel.setSound(null, null);
notificationManager.createNotificationChannel(notificationChannel);
localBuilder.setChannelId(channel_id);
}
localBuilder.setDefaults(Notification.DEFAULT_ALL); // notify sound
localBuilder.setLights(Color.GREEN, 1000, 2000);// notify light
localBuilder.setAutoCancel(false);
localBuilder.setContentTitle("入库通知");
localBuilder.setContentText(bean.getName() + ":入库待确认");
localBuilder.setSmallIcon(R.mipmap.ic_launcher);
notificationManager.notify(bean.getId(), localBuilder.build());前台服务通知
如果您的应用正在运行”前台服务” (一种长时间在后台运行且用户可察觉到的 Service, 如媒体播放器) , 则需要发出通知.此通知不能像其他通知一样取消.如要移除通知, 则必须停止服务或从”前台”状态中移除该服务.
/**
* 前台服务
* foreground service
*/
Notification.Builder localBuilder = new Notification.Builder(this);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (android.os.Build.VERSION.SDK_INT > 26) {//android 8+
NotificationChannel notificationChannel = null;
notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setShowBadge(true);
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationChannel.setSound(null, null);
notificationManager.createNotificationChannel(notificationChannel);
localBuilder.setChannelId(CHANNEL_ID);
}
localBuilder.setAutoCancel(false);
localBuilder.setContentTitle("LinstenService");
localBuilder.setContentText("正在运行...");
localBuilder.setSmallIcon(R.mipmap.ic_launcher);
// localBuilder.setDefaults(Notification.DEFAULT_ALL); // notify sound
// localBuilder.setLights(Color.GREEN, 1000, 2000); // notify light
startForeground(F_NOTIFY_ID, localBuilder.build());//foreground service 为前台
参考上 四大组件 > Service