service类型
android中service分为background service,bound service,foreground servce,其中background service运行在后台与ui没有交互,bound service通过service connect可以跟activity等通过binder进行数据交互,也可以通过messenger,aidl等进行多进程通信。 Foreground service与notification绑定,通过notification通知用户。
1、foreground service
foreground service 需要和notification结合使用,一般用在音乐播放器,和应用的推送等。Android 9以上前台service必须进行权限配置,并且foreground service必须要和notification一起使用。
manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
< uses-permission android:name = "android.permission.FOREGROUND_SERVICE" />
<application ...>
...
</application>
</manifest>
1、创建foreground service
可以在service的onStartCommand方法中创建notification,如下:
@RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val pendingIntent: PendingIntent =
Intent(this, LaunchActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(
this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE
)
}
createNotificationChannel()
val notification: Notification = Notification.Builder(this, channel_default_importance)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
.build()
startForeground(12, notification)
return START_NOT_STICKY
}
//createNotificationChannel
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
channel_default_importance,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(
NotificationManager::class.java
)
manager.createNotificationChannel(serviceChannel)
}
}
注意当sdk版本大于O时一定要创建NotificationChannel,不然会报下面错误
可以通过foreground service设置跳转的activity,设置标题和点击事件等。
2、启动foreground service
val foreSeIntent = Intent(this, ForegroundsService::class.java)
applicationContext.startForegroundService(foreSeIntent)
3、forground service使用场景
1、音乐播放器
qq音乐,网易云音乐,喜马拉雅等都会有foreground service 提供给用户进行操作。
2、应用推送
4、Foreground service 版本限制
在Android 12 (API level 31) 及以上,不能在应用处于后台时启动foreground service,否则会抛出:
ForegroundServiceStartNotAllowedException
在Android 12及以上应用要在后台启动前台service,可以通过workManager来实现,也可以在一下场景下解决应用后台时启动foreground service.
1、用户对与应用相关的UI元素执行操作。例如,bubble, notification, widget, 或者 activity。
2、通过监听 ACTION_BOOT_COMPLETED
, ACTION_LOCKED_BOOT_COMPLETED
, ACTION_MY_PACKAGE_REPLACED
ACTION_TIMEZONE_CHANGED
, ACTION_TIME_CHANGED
, ACTION_LOCALE_CHANGED
等广播事件。
3、引导用户关闭电量优化。
2、background service
Background service 运行在后台mainfest配置
<service android:name=".service.BackgroundService" />
创建background service,继承service必须重写service中的onBind方法,
class BackgroundService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
1、startService
Intent intent = new Intent(this,BackgroundService.class);
startService(intent);
2、多次调用startService
只有首次调用startService时才会调用onCreate方法,后面调用startService只会调用onStartCommand方法。如下连续调用三次startService,startid每次增大1,
val intent = Intent(this, BackgroundService::class.java)
startService(intent)
startService(intent)
startService(intent)
输出log如下,只有调用一次onCreate,后面的startService都直接调用onStartCommand,只是startid有增加
D/backgroundService: onCreate
D/backgroundService: onStartCommand startid 1 flags 0
D/backgroundService: onStart startid 1
D/backgroundService: onStartCommand startid 2 flags 0
D/backgroundService: onStart startid 2
D/backgroundService: onStartCommand startid 3 flags 0
D/backgroundService: onStart startid 3
3、退出应用后再返回(不杀应用进程)
D/backgroundService: onCreate
D/backgroundService: onStartCommand startid 1 flags 0
D/backgroundService: onStart intent startid 1
//退出应用,后返回
-------------------
D/backgroundService: onStartCommand startid 2 flags 0
D/backgroundService: onStart startid 2
由上面log可知退出应用后再启动应用,service会重新走onStartCommand,不会走onCreate,但是startid不相同。
4、停止background service
val intent = Intent(this, BackgroundService::class.java)
stopService(intent)
3、bound service
Bound service以client server形式,允许activity等组件跟service进行交互。activity可以通过bindService来绑定一个service,当所有的activity等组件都执行unBindService时 bound service会执行destroy。bound service的生命周期如下:
1、Bound service使用
1、创建bound service并且重写onBind方法
class BoundService : Service() {
private val TAG: String = "BoundService"
private var binder: Binder = MyImplementor()
override fun onBind(intent: Intent?): IBinder {
Log.d(TAG, "onBind")
return binder
}
}
2、定义ServiceConnection关联service和activity或者其他组件。
connect = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
//connect
aidlInterface = IMyAidlInterface.Stub.asInterface(service)
Log.d(TAG, "onServiceConnected ${aidlInterface?.message}")
slogin.text = aidlInterface?.message
}
override fun onServiceDisconnected(name: ComponentName?) {
//disconnect
Log.d(TAG, "onServiceDisconnected")
aidlInterface = null
}
}
3、bindService
applicationContext.bindService(boundIntent, connect, BIND_AUTO_CREATE)
使用bound service可以通过binder来从service中获取数据。
2、bound service使用注意
使用bound service时必须先bindservice,然后再unBindService,不然会报如下错误。
3、activity获取Bound service交互方式
1、binder
这里binder只适用于本地service,也就是默认进程创建的service,如果是remote指定的service,不能使用localBinder的方式。(也就是不支持多进程)
创建一个LocalBinder,在onBinder的时候返回
class LocalService : Service() {
// Binder given to clients
private val binder = LocalBinder()
// Random number generator
private val mGenerator = Random()
/** method for clients */
val randomNumber: Int
get() = mGenerator.nextInt(100)
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
inner class LocalBinder : Binder() {
// Return this instance of LocalService so clients can call public methods
fun getService(): LocalService = this@LocalService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
}
在ServiceConnect中获取binder,并且通过binder获取service实例。
/** Defines callbacks for service binding, passed to bindService() */
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
val binder = service as LocalService.LocalBinder
mService = binder.getService()
mBound = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
mBound = false
}
}
通过service实例来获取service中的变量
if (mBound)
{
val num: Int = mService.randomNumber
Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
}
2、Messenger
messenger内部也是通过aidl来支持跟remote进程中的service通信,也就是跨进程通信。
值service中创建一个Messenger,并且创建一个handler内部类来处理msg。
/** Command to the service to display a message */
private const val MSG_SAY_HELLO = 1
class MessengerService : Service() {
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
private lateinit var mMessenger: Messenger
/**
* Handler of incoming messages from clients.
*/
internal class IncomingHandler(
context: Context,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO ->
Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
else -> super.handleMessage(msg)
}
}
}
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
override fun onBind(intent: Intent): IBinder? {
Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
mMessenger = Messenger(IncomingHandler(this))
return mMessenger.binder
}
}
在serviceConnect中去获取binder,并且封装成一个Messenger,并且通过Messenger来发送数据。
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = Messenger(service)
bound = true
}
override fun onServiceDisconnected(className: ComponentName) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null
bound = false
}
}
fun sayHello(v: View) {
if (!bound) return
// Create and send a message to the service, using a supported 'what' value
val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
try {
mService?.send(msg)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
3、aidl
aidl是支持进程间通信的,所以使用aidl可以跨进程通信使用aidl通信方式可以参考hanking.blog.csdn.net/article/det…
4、bindservice vs startService
- 先startservice后再进行bindService,此时进行unBindServide,service并不会执行destroy。如下
- bindservice->startService->unBindService->bindService
- 多次连续bindservice,只有首次会执行。
- 多次startService首次执行onCreate后面只会执行onStartCommand
service生命周期
service相关面试题
1、Service 的 onStartCommand 方法有几种返回值及意义
1、START_STICKY
如果在执行完 onStartCommand 后,service被异常 kill 掉,系统会重启service并且重走onStartCommand方法,但是onStartCommand中的intent为null。(在轮询中可以使用这种方式,轮询时客户端可以定时的从服务端获取数据)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
2、START_NO_STICKY
如果在执行完 onStartCommand 后,service被异常 kill 掉,系统不会自动重启该服务。
3、START_REDELIVER_INTENT
如果在执行完 onStartCommand 后,service被异 常 kill 掉,系统会自动重启该服务,并将 Intent 的值传入。(常用于后台下载资源,比如资源下载到一半时service被kill了,后面service重启时会可以根据intent的值下载后面没有下载的资源)
2、Service 和 IntentService 的区别?
IntentService继承service,并且IntentService在onCreate的时候创建了一个HandlerThread,并且创建一个mServiceHandler,
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
在service执行onStart的时候,通过handler发送一个message
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
然后在serviceHandler中handlerMessage中处理这个msg,执行onHandleIntent((Intent)msg.obj);方法,并且stopSelf.
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
所以继承IntentService时要继承onHandleIntent(@Nullable Intent intent)并且可以在onHandleIntent中处理耗时操作。注意由于ServiceHandler中Looper是从ThreadHandler中获取的,所以onHandleIntent方法不是执行在主线程中。
3、service 里面是否 能执行耗时的操作?
service默认是在主线程中执行,所以service不能执行耗时操作,如果要执行耗时操作可以继承IntentService并且重写onHandleIntent来执行,也可以通过配置service所在的进程,如下
<service
android:name="com.example.aidld.service.BoundService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:process=":remote">
通过配置android:process=":remote"来让service在其他进程中执行。
4、Service里面能弹toast吗?
service中可以弹toast,如下makeText时候传入this,service继承ContextWrapper。
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG,"onStartCommand intent $intent startid $startId flags $flags")
Toast.makeText(this,"this is toast", Toast.LENGTH_LONG).show()
return START_STICKY
}
5、Service 的 onRebind(Intent)方法执行时机?
- 同一个activity中如果直接执行bindService,并且执行unBindService,这时再执行bindService,service的生命周期如下:
- 同一个activity中如果先执行startService,再进行bindService,然后unBindService 再进行bindService生命周期如下:
-
startService之后同一个activity多次执行bindService,只有首次bindService会调用Service中的onBind方法,后面bindService不会再执行onBind方法。
-
多次执行startService方法
通过下面的adb指令打印services,然后查看自定义的service
adb shell dumpsys activity services
如下可以看到service的信息,
* ServiceRecord{38c5581 u0 com.example.greedwebview/com.example.aidld.service.BoundService}
intent={cmp=com.example.greedwebview/com.example.aidld.service.BoundService}
packageName=com.example.greedwebview
processName=com.example.greedwebview:remote
permission=android.permission.BIND_ACCESSIBILITY_SERVICE
baseDir=/data/app/~~6XYMsYPqHpQ-rzLcM8zU6A==/com.example.greedwebview-HogsH4v4cZdX5ktjemR8tg==/base.apk
dataDir=/data/user/0/com.example.greedwebview
app=ProcessRecord{a56288b 12472:com.example.greedwebview:remote/u0a167}
allowWhileInUsePermissionInFgs=true
recentCallingPackage=com.example.greedwebview
createTime=-4m48s864ms startingBgTimeout=--
lastActivity=-4m47s482ms restartTime=-4m48s795ms createdFromFg=true
startRequested=true delayedStop=false stopIfKilled=false callStart=true lastStartId=3
- startService ->bindService->stopService->unbindService->bindService
执行startService后bindService然后再执行stopService,再执行unBindService此时service会执行destory方法,再次bindService时,会重新走onCreate->onBind
6、能不能多次调用unbindService?
同一个activity中如果没有bindService时调用unBindService,会有如下报错:
2022-05-28 16:20:03.044 10686-10686/com.example.aidl E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.aidl, PID: 10686
java.lang.IllegalArgumentException: Service not registered: com.example.aidld.LaunchActivity$createConnect$1@162849d
at android.app.LoadedApk.forgetServiceDispatcher(LoadedApk.java:1757)
at android.app.ContextImpl.unbindService(ContextImpl.java:1874)
at android.content.ContextWrapper.unbindService(ContextWrapper.java:792)
at com.example.aidld.LaunchActivity.unBindService(LaunchActivity.kt:81)
7、service保活
1、设置service为前台service,提高service优先级。
<intent-filter android:priority="1000">
<action android:name="BoundService" />
</intent-filter>
其中1000为最高优先级。
2、双进程service保护,在一个service被回收时启动另一个service。
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//链接上
Log.d("test","GuardService:建立链接");
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
//断开链接
startService(new Intent(GuardService.this,StepService.class));
//重新绑定
bindService(new Intent(GuardService.this,StepService.class),
mServiceConnection, Context.BIND_IMPORTANT);
}
3、加入电量白名单,跳过电量优化
参考
1、developer.android.com/guide/compo…