一 基本概念
- Service是Android中实现程序后台运行的解决方案,适用于执行不需要和用户交互而且还要求长期运行的任务
- Service的运行不依赖于任何用户界面,即使程序被切换至后台,或者用户打开了另外一个应用程序,Service仍然能够保持正常运行
- Service是依赖于创建Service时所在的应用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行
- Service是运行在主线程中的,也就是说我们需要在Service的内部手动创建子线程,并在里面执行具体的任务,否则可能阻塞主线程
二 Android多线程
- 基本的线程实现方式与Java是一致的
- 继承Thread类
class MyThread: Thread(){
override fun run(){
}
}
MyThread().start()
2.实现Runnable接口
class MyThread: Runnable{
override fun run(){
}
}
val myThread = MyThread()
Thread(myThread).start()
当然 如果不想专门定义一个类去实现Runnable接口,也可以使用Lambda的方式,这种写法更常见:
Thread{
//耗时操作
}.start()
kotlin还提供了一种更加简单的开启线程的方式:
thread{
//具体逻辑
}
thread
是一个kotlin内置的顶层函数,我们只需要在Lambda表达式中编写具体的逻辑就可以了,连start()
方法thread
函数在内部帮我们全部都处理好了
以上都与java一致,接下来看看Android多线程与Java多线程不同的地方:
- 异步消息处理机制:Handler
class MainActivity : AppCompatActivity() {
private lateinit var changeTextBtn: Button
private lateinit var mTextView: TextView
val handler = object : Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
when(msg.what){
1 -> mTextView.text = "Hello World"
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
setListener()
}
private fun setListener() {
changeTextBtn.setOnClickListener {
thread {
val msg = Message()
msg.what = 1
handler.sendMessage(msg)
}
}
}
private fun initView() {
changeTextBtn = findViewById(R.id.changTextBtn)
mTextView = findViewById(R.id.text)
}
}
- AsyncTask
这是Android提供的一个线程切换工具类,它的原理也是使用的handler,只不过做了封装。
AsyncTask是一个抽象类,如果我们想使用它,就必须创建一个子类去继承它,在继承时我们可以为AsyncTask类指定3个泛型参数,这三个参数的用途如下: - Params
在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。 - Progress
在后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位 - Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
class DownloadTask : AsyncTask<Unit, Int, Boolean>(){
...
}
这里把AsyncTask的第一个泛型参数指定为Unit,表示在执行AsyncTask的时候不需要传入参数给后台任务;第二个泛型参数指定为Int,表示使用整型数据来作为进度显示单位;第三个泛型指定为Boolean,则表示使用布尔型数据来反馈执行结果。
-
onPreExecute()
这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2.doInBackground(Params...)
这个方法中的代码都会在子线程运行,在这里做耗时操作,任务一旦完成,就可以通过return语句返回任务执行结果,当然如果第三个泛型参数指定为Unit就可以不用返回,这里面是不能进行UI操作的,如果需要更新UI元素,比如反馈当前任务的执行进度,可以调用publishProgress(Progress...)
方法来完成。
3.onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)
方法后,onProgressUpdate(Progress...)
方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的,在这个方法中可以更新UI。
4.onPostExecute(Result)
当后台任务执行完毕并通过return语句返回时,这个方法就会很快被调用,返回的数据会作为参数传递到此方法中,可以进行一些UI操作,比如根据结果关闭进度对话框等
如果想要启动这个异步任务,只需要DownloadTask().execute()
,当然也可以传入任意数量的参数,这些参数将会传递到DownloadTask的doInBackground()
方法。
三 Service基本用法
class MyService : Service(){
private val mBinder = DownloadBinder()
class DownloadBinder : Binder(){
fun startDownload(){
Log.e("MyService", "MyService DownloadBinder ---startDownload")
}
fun getProgress(): Int{
Log.e("MyService", "MyService DownloadBinder ---getProgress")
return 0
}
}
override fun onBind(intent: Intent) : IBinder{
return mBinder
}
override fun onCreate(){
super.onCreate()
Log.e("MyService", "MyService---onCreate")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int{
Log.e("MyService", "MyService---onStartCommand")
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy(){
super.onDestroy()
Log.e("MyService", "MyService---onDestroy")
}
}
-
onCreate()
会在Service创建的时候调用 -
onStartCommand()
会在每次Service启动的时候调用 -
onDestroy()
会在Service销毁时调用 - 启动Service
第一次启动:onCreate()
、onStartCommand()
第二次启动:onStartCommand()
val intent = Intent(this, MyService::class.java)
startService(intent)
- 停止Service---回调
onDestroy()
val intent = Intent(this, MyService::class.java)
stopService(intent)
当然Service也可以自我停止,只需在Service内部调用stopSelf()
方法即可。
Android8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台,Service随时都可能被系统回收。
- Activity与Service通信
- 绑定Service
class MainActivity : AppCompatActivity(){
laterinit var downloadBinder: MyService.DownloadBinder
private val connection = object : ServiceConnection{
override fun onServiceConnected(name: ComponentName, service: IBinder){
downloadBinder = service as MyService.DownloadBinder
downloadBinder.startDownload()
...
}
override fun onServiceDisconnected(name: ComponentName){}
}
override fun onCreate(savedInstanceState: Bundle?){
val intent = Intent(this, MyService::class.java)
//绑定Service
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
override fun onDestroy(){
super.onDestroy()
unbindService(connection)
}
}
onServiceConnected()
方法会在Activity与Service成功绑定的时候调用,而onServiceDisconnected()
方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用。这里传入Context.BIND_AUTO_CREATE
表示在Activity和Service进行绑定后自动创建Service。这会使得MyService中的onCreate()
方法得到执行,但onStartCommand()
不会执行
假设既调用了startService()
又调用了bindService()
,根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件同时不满足,Service才能被销毁,所以,这种情况下要同时调用stopService()
和unbindService()
方法,onDestroy()
方法才会执行。
四 使用前台Service
- 如果不想应用进入后台后,Service随时都有可能被系统回收的风险,而是希望Service能够一直保持运行状态,就可以考虑使用前台Service。
- 前台Service和普通Service最大的区别就在于前台Service一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,用户可以清楚的知道当前什么应用正在运行。
- 由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,因此系统不会倾向于回收前台Service。
- 创建前台Service
class MyService : Service(){
...
override fun onCreate(){
super.onCreate()
Log.e("MyService", "MyService---onCreate")
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.0){
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("title")
.setContentText("text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon()
.setContentIntent(pi)
.build()
startForeground(1, notification)
}
...
}
startForeground()
方法接收两个参数:第一个参数是通知的id;第二个参数是则是构建的Notification对象
调用startForeground()
方法后就会让MyService变成一个前台Service,并在系统状态栏显示出来
另外,从Android9.0系统开始,使用前台Service必须在AndroidManifest.xml文件中进行权限声明:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- Service一旦启动,就会一直处于运行状态,必须调用stopService()或stopSelf()或被系统回收,Service才会停止
- 如果想要创建一个异步的、会自动停止的Service,Android专门提供了一个IntentService类:
class MyIntentService : IntentService("MyIntentService"){
override fun onHandleIntent(intent: Intent?){
//打印当前线程id
Log.e("MyIntentService", "Thread id is ${Thread.currentThread().name}")
}
override fun onDestroy(){
super.onDestroy()
Log.e("MyIntentService", "onDestroy")
}
}
这里首先必须要调用父类的构造函数,并传入一个字符串,这个字符串可以随意指定,只在调试的时候有用。
然后可以在onHandleIntent()
方法里处理一些耗时逻辑,而不用担心ANR,因为这个方法已经是在子线程中运行的了,并且,根据IntentService的特性,这个Service在onHandleIntent()
方法运行结束后是会自动停止的,接下来证明一下根据打印结果:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e("MainActivity", "Thread id is ${Thread.currentThread().name}")
val intent = Intent(this, MyIntentService::class.java)
startService(intent)
}
}
当然不要忘记在manifest里面注册一下