Android service

944 阅读6分钟

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…

2、developer.android.com/guide/compo…

3、developer.android.com/training/no…