1 Service概述
- Service是Android中实现程序后台运行的解决方案,适合执行那些不需要和用户交互且长期运行的任务。
- Service通常是运行在创建Service的那个应用进程中,而不是在单独的进程中运行。但是可以通过配置来让Service运行于单独的进程中。
- Service通常是需要自己创建线程来运行耗时操作,否则是在主线程中运行,会导致主线程被阻塞的情况出现。
2 Android多线程编程
2.1 多线程开启方式
- 方式一
是使用Java中的多线程写法。
创建类继承Thread类并重写run方法。
class MyThread : Thread() {
override fun run() {
//运行逻辑
}
}
调用start方法启动线程执行。
MyThread().start()
- 方式二
使用继承耦合性会比较高,更多的是选择Java中的另一种方式,实现Runnable接口
class MyThread : Runnale {
override fun run() {
//运行逻辑
}
}
启动线程
val myThread = MyThread()
Thread(myThread).start()
- 方式三
使用lambda表达式
Thread {
//执行逻辑
}
- 方式四
方式四是kotlin中的独有方式,调用thread这个顶层函数,传入的是一个lambda表达式,表达式中书写线程执行逻辑
thread {
//执行逻辑
}
2.2 子线程中更新UI
Android中的UI线程是非线程安全的,所以规定所有的UI操作都在主线程中执行,如果在子线程中执行UI的更新操作,则会报异常。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.bt1).setOnClickListener {
thread {
findViewById<TextView>(R.id.tv1).text = "Nice to meet you"
}
}
}
}
通过点击按钮触发TextView的文字替换,报错
只能在原始线程中才能触及这个View,所以规定只能在主线程中修改UI。
Android中提供了一种异步消息处理机制
val updateText = 1
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when(msg.what) {
updateText -> {
findViewById<TextView>(R.id.tv1).text = "Nice to meet you"
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.bt1).setOnClickListener {
thread {
val msg = Message()
msg.what = updateText
handler.sendMessage(msg)
}
}
}
使用Handler机制,进行线程间的消息传递。子线程构造Message,通过主线程的Handler进行发送,消息进入Looper中循环,主线程从Looper中取出消息进行处理。
2.3 异步消息处理机制
Android中的异步消息处理主要由4部分组成:Message、Handler、MessageQueue和Looper。
1、Message
Message是线程之间传递的消息,可以携带少量的信息在线程之前传递。
2、Handler
消息的处理者,主要用来发送和处理消息。
3、MessageQueue
消息队列,用来存放所有通过Handler发送的消息,一个线程只有一个MessageQueue。
4、Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop方法后,Looper会进入一个无限循环中,每当发现MessageQueue中有一个消息就会取出并传递给Handler进行处理。每个线程只有一个Looper对象。
3 Service基本用法
public class MyService extends Service {
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
Service继承自系统的Service类,这里重写了onCreate、onStartCommand、onDestroy和onBind方法。onCreate方法会在Service创建的时候调用,onStartCommand方法会在每次Service启动的时候调用,onDestroy会在Service销毁的时候调用。
除此之外,和Activity一样,Service要使用需要在AndroidMenifest.xml中注册。
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
在Android Studio中右键创建Service会自动在AndroidMenifest.xml中注册。
3.1 启动和停止Service
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.startServiceBtn).setOnClickListener {
val intent = Intent(this, MyService::class.java)
startService(intent)
}
findViewById<Button>(R.id.stopServiceBtn).setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent)
}
}
}
使用startService启动Service,使用stopService停止Service,传入的参数是Intent。
2022-09-25 23:03:45.232 3107-3107/com.xy.servicetest D/MyService: onCreate: executed
2022-09-25 23:03:45.240 3107-3107/com.xy.servicetest D/MyService: onStartCommand: executed
2022-09-25 23:03:48.108 3107-3107/com.xy.servicetest D/MyService: onDestroy: executed
startService调用时,会回调onCreate和onStartCommand方法。stopService调用时,会回调onDestroy方法。
Android8.0开始,应用的后台功能被大幅削减,现在只有在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台后,Service随时可能被系统回收。如果需要长期在后台执行一些任务,可以使用前台Service或者WorkManager。
onCreate方法是在Service第一次创建的时候调用,而onStartCommand方法则在每次启动Service的时候都会调用。
3.2 Activity与Service进行通信
Activity与Service之间通信需要依靠Binder来实现,模拟一个下载场景,service执行逻辑,在Activity中启动。
Service代码
class MyService : Service() {
private val mBinder = DownloadBinder()
class DownloadBinder : Binder() {
fun startDownload() {
Log.d("MyService", "startDownload: executed")
}
fun getProgress():Int {
Log.d("MyService", "getProgress: executed")
return 0
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
override fun onCreate() {
super.onCreate()
Log.d("MyService", "onCreate: executed")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("MyService", "onStartCommand: executed")
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
Log.d("MyService", "onDestroy: executed")
super.onDestroy()
}
}
Service中创建了Binder的子类,并定义了两个方法,启动下载和获取进度,这里由于是模拟,只是加上了打印。在onBind方法中返回了DownloadBinder 的实例。这样当Activity绑定Service后,就可以通过这个Binder来与Service进行通信了。
Activity代码
class MainActivity : AppCompatActivity() {
lateinit var downloadBinder: MyService.DownloadBinder
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
downloadBinder = service as MyService.DownloadBinder
downloadBinder.startDownload()
downloadBinder.getProgress()
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.bindService).setOnClickListener {
val intent = Intent(this, MyService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
findViewById<Button>(R.id.unBindService).setOnClickListener {
unbindService(connection)
}
}
}
Activity中创建一个ServiceConnection 的匿名内部类对象,并在这个类中重写了onServiceConnected会在bindService方法调用时回调。onServiceDisconnected这个方法不太常用,只有在Service的创建进程崩溃或者被杀掉时才会调用。BIND_AUTO_CREATE这个参数表示Activity和Service进行绑定后自动创建Service。
调用bindService和unbindService方法后的打印
2022-09-26 21:53:15.468 3317-3317/com.xy.servicetest D/MyService: onCreate: executed
2022-09-26 21:53:15.481 3317-3317/com.xy.servicetest D/MyService: startDownload: executed
2022-09-26 21:53:15.482 3317-3317/com.xy.servicetest D/MyService: getProgress: executed
2022-09-26 21:53:24.200 3317-3317/com.xy.servicetest D/MyService: onDestroy: executed
4 Service生命周期
调用startService方法后,Service就被启动了,如果之前没有启动过,就会调用onCreate方法,然后调用onStartCommand方法,如果已经启动过,则直接调用onStartCommand方法。Service启动后,一直处于运行状态,知道stopService或stopSelf方法被调用或是Service被系统回收,此时会回调onDestroy方法。
还可以调用Context的bindService方法来获取一个持久的连接,此时会回调onBind方法。如果Service还未创建,onCreate方法会在onBind方法之前调用,调用onBind会返回一个IBinder类型的对象用于和Service进行通信。只要Activity没有断开连接,Service就一直在运行,直到被系统回收。调用unbindService方法后会回调onUnbind方法,并调用onDestroy方法销毁Service。
startService与bindService并不冲突,启动Service之后也可以绑定Service进行通信,只是不会创建Service实例,Service实例只有一个。但是需要注意的是,当startService和bindService都调用后,必须等stopService和unbindService都调用后Service才会销毁。
5 Service的更多技巧
5.1 使用前台Service
由于普通Service进入后台之后随时有可能被系统所回收,所以如果想要你的Service一直运行,则需要将service设置为前台service,前台service会一直在状态栏有一个图标显示,且不会被系统回收。
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate: executed")
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"my_service", "前台Service通知",
NotificationManager.IMPORTANCE_DEFAULT
)
manager.createNotificationChannel(channel)
}
val intent = Intent(this, MainActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, 0)
val notification = NotificationCompat.Builder(this, "my_service")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.large_icon))
.setContentIntent(pi)
.build()
startForeground(1, notification)
}
调用startForeground方法使Service成为前台Service,该方法接收两个参数,第一个参数是通知的id,第二个是一个Notification对象。
Android9开始前台服务要求需要申请权限android.permission.FOREGROUND_SERVICE
java.lang.RuntimeException: Unable to create service com.xy.servicetest.MyService2: java.lang.SecurityException: Permission Denial: startForeground from pid=8960, uid=10125 requires android.permission.FOREGROUND_SERVICE
5.2 使用IntentService
Service是在主线程中执行的,如果执行一些耗时操作会导致ANR,所以应该开启子线程执行业务逻辑。但是Service一旦启动了就会一直处于运行状态,调用stopSelf方法才会停止运行,所以通常的写法为:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
thread {
//业务逻辑
stopSelf()
}
return super.onStartCommand(intent, flags, startId)
}
业务执行完成后调用stopSelf方法销毁Service。但是很多人会忘记调用该方法,所以出现了IntentService。
class MyService3 : IntentService("MyService3") {
override fun onHandleIntent(intent: Intent?) {
Log.d("MyService3", "onHandleIntent: ")
}
override fun onDestroy() {
super.onDestroy()
Log.d("MyService3", "onDestroy: ")
}
}
启动的方式是一样的,当startService启动MyService3时,可以看到打印
D/MyService3: onHandleIntent:
D/MyService3: onDestroy:
IntentService构造函数中传入的字符串只在调试的时候有用,可以随意指定。需要重写onHandleIntent方法,在其中写入耗时的业务逻辑。从打印中可以看出startService后,业务代码执行完成会自动调用onDestroy方法。onHandleIntent中执行的已经是在子线程中执行了,可以自行添加打印验证。
但是现在IntentService已经被废弃了,建议使用WorkManager或者JobIntentService来将业务逻辑看成是一项工作而不是一个服务执行。
6 补充
一些小知识点在这里进行补充,使用Java编写
6.1 ANR问题
前台Service 20s,后台Service 200s。所以Service的耗时处理逻辑需要放到子线程中运行,如果不开启子线程,则在主线程中运行耗时操作可能会导致ANR,可以做个测试:
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
这里在onCreate方法中延时60s,运行startService或者bindService,由于主线程阻塞,会报出ANR:
除了在子线程中执行耗时操作外,还可以让service以单独进程运行来解决。
<service
android:name=".MyService"
android:process=":remote" />
但此时,如果使用bindService将Activity与Service进行绑定通信就会出错,因为他们属于不同的进程了,报错如下:
Binder代理无法转换成自定义的Binder类型。此时就需要使用aidl在进程间进行通信。