参考: 《第一行代码 第三版》
10.1 service 是什么
Service是实现程序后台运行的解决方案,适合执行非交互,后台预先的任务,即使用户打开其他应用,Service也能够正常运行
Service需要内部手动创建子线程
10.2 多线程编程
- 用法:
(1) 继承的方式(耦合较高,不推荐)
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()
更加简化的写法:
thread {
// 编写具体的逻辑
}
- 在子线程中更新UI
Android 的UI 也是线程不安全的, 更新应用程序的UI元素, 必须在主线程中进行, 否则会出现异常
如果在子线程中直接更新UI,会出现崩溃,提示如下错误
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
那子线程如何更新UI呢?
通过异步消息传递给主线程, 在主线程更新UI
修改MainActivity.kt
class MainActivity : AppCompatActivity() {
val sign = 1
val handler = object: Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
sign -> textView.text = "Nice to meet you 2"
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener{
thread {
val msg = Message()
msg.what = sign
handler.sendMessage(msg)
}
}
}
}
定义一个Handler对象,重写handleMessage方法
如果Message(android.os.Message)的what字段等于sign,就将UI更新
- 异步消息处理机制
(1) Message 线程中传递少量消息,使用arg1和arg2携带整型数据, obj字段携带Obejct对象
(2) Handler,用于发送和接收消息
发送: 使用sendMessage() post() 方法
接受: 最终会传递到handleMessage方法中
(3) MessageQueue, 消息队列,存放Handler发送的消息,等待被处理,每个线程只会有一个MessageQueue
(4) Looper, 是每个线程中MessageQueue的管家, 调用Looper的loop()
方法后,会进入无限循环中,每当发现MessageQueue中存在一条消息,就取出,并传递到Handler的handleMessage方法中,每个线程用一个Looper对象
异步消息处理流程:
1 主线程创建handler对象, 重写handleMessage方法,
2 当子线程中需要进行UI操作,就创建一个Message对象,通过Handler 的sandMessage方法将消息发送出去,消息被添加到MessageQueue中等待
3 Looper一直尝试从MessageQueue中取消息,最后分发给Handler的handlerMessage方法中,由于Handler函数中传入了Looper.getMainLooper(), 此时handleMessage() 方法中的代码会在主线程中运行
- 使用AsyncTask
为了方便子线程对UI操作, Android提供了一些好用的工具如AsyncTask,原来也是基于异步消息处理
(1)基本用法:
AsyncTask是一个抽象类,如果想使用它,需要一个子类继承,可以在继承时指定3个泛型参数:
params: 可在后台任务中使用
progress :在后台任务执行时, 如果需要在界面上显示的进度,使用泛型作为进度单位
Result 任务执行完后, 对结果进行返回, 返回泛型类型
最简单的形式:
class DownloadTask :AsyncTask<Unit, Int, Boolean> () {
}
当前是一个空任务,无任何实际操作,需要重写4个方法:
1 onPreExecute() 在任务执行前调用,用于初始化操作
2 doInBackground(Params…) 在子线程中执行, 执行具体耗时任务
3 onProgressUpdate(Progress…) 后台任务调用,进行UI操作
4 onPostExecute(Result) 后台任务执行完毕并通过return返回时, 收尾工作
10.3 Service 基本用法
1.定义一个Service
新建一个ServiceTest项目
右击 com.example.servicetest -> New -> Service -> Service
类名改成MyService, Exported表示将Service暴露给外部访问
Enable表示启用这个Service
生成如下代码:
class MyService : Service() {
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
MyService 继承自Service类, 有一个onBind方法,是Service唯一抽象方法,需要在子类实现
重写一些方法:
onCreate() service创建调用
onStartCommand() 每次service启动调用
onDestory() 销毁调用
ps: Service需要在AndroidManifest.xml文件中注册(在创建service中会自动注册)
- 启动和停止Service
要借助Intent实现,在ServiceTest中启动停止MyService
添加两个按钮:
startServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
startService(intent) // 启动Service
}
stopServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent) // 停止Service
}
startService
和stopService
都定义在Context类中,可以直接调用
另外可以自我停止:
在service内部调用stopSelf()
方法
启动后可以在: 应用 -》显示系统应用 中找到
Android8.0后,应用在前台,service才能稳定运行,否则随时可能被系统回收
- Activity 与 Service通信: onBind 方法
查看下载进度:,创建一个专门的Binder对象管理下载功能:
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
}
当一个Activity 和Service 绑定了之后,就可以调用该Service 里的Binder提供的方法了。
在activity中,修改:
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) {
}
}
...
// 绑定service
bindServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE) // 绑定Service
}
// 解绑service
unbindServiceBtn.setOnClickListener {
unbindService(connection) // 解绑Service
}
bindService()方法接收3个参数
第一个是Intent对象
第二个是ServiceConnection的实例
第三个是一个标志位 BIND_AUTO_CREATE 表示在Activity 和Service 进行绑定后
自动创建Service
这会使得MyService 中的onCreate()方法得到执行