Android系统编程入门系列之服务Service齐头并进多线程任务
在上篇文章中初步了解了Android系统的四大组件之一的服务Service
,在服务内可以执行无用户交互的耗时操作任务,但是包括之前关于界面系列文章在内,生命周期方法都是在主线程内被系统回调的。如果直接在生命周期方法中执行耗时操作,同样可能会在主线程5s内无响应而触发系统对应用程序的ANR异常。为了解决这个问题,就需要使用多线程开发来执行耗时任务,在任务执行结束后将结果返回到主线程中响应。
什么是线程呢?每个应用程序在初始化时,默认运行在以其包名命名的进程中,同一进程中的内存是可以共享使用的。而每个进程在创建时,都会随之创建一个java.lang.Thread实例的线程,用以执行Android系统对当前应用程序的生命周期方法回调,也就是通常意义上的UI界面绘制等任务,这也就是所谓的主线程。而每个进程在主线程中,可以继续创建多个线程,理论上只要硬件内存支持,线程是可以无限创建的。这些新创建的线程,被称为子线程。子线程中可以执行耗时任务,以此使得主线程中的界面任务保持与用户的及时响应。
值得注意的是,子线程中是不允许执行更新界面相关操作,必须切换回主线程绘制界面。
任务创建
新任务类需要实现java.lang.Runnable接口,在实现的run()
方法中处理需要操作的任务。
任务执行
在开发中启动新任务的方式主要有三种,其一是直接创建最基础的线程类,单独管理并执行耗时任务;其二是创建一个由一堆线程组成的线程池,将耗时任务放进去执行,剩下的由线程池管理;其三则是使用成熟的并发库,根据不同的并发库创建及启动任务的方式也将不仅限于Runnable
类型的实例。另外在Android R即API30以前,还可以使用android.os.AsyncTask创建异步任务,但是该方式在API30之后已废弃,故不推荐使用。下面将简单介绍以上三种主流方式。
单独线程
通常使用Thread(Runnable target)
构造方法创建子线程,参数 target 作为要执行的任务对象。
之后在需要执行任务的位置调用子线程对象的start()
方法启动运行该线程即可。
由于创建的线程是依托于某个界面Activity
或服务Service
的一个组件,所以当该组件的生命周期方法销毁后,其中创建的子线程也就销毁了。所以子线程必须要在被销毁之前调用interrupt()
方法中断运行并释放其占用的资源,以防止发生内存泄漏等问题。
线程池
通过线程池管理类java.util.concurrent.Executors的newCachedThreadPool()
等系列静态方法,可以直接创建java.util.concurrent.Executor接口定义的线程池类实例化对象。
之后在需要执行任务的位置调用线程池对象的execute(Runnable command)
方法即可执行一次任务。
线程池类的优点在于当该组件的生命周期方法销毁后,该线程池及其中的线程都会被强制销毁,不需要手动管理。
并发库
关于Android系统的多线程开发,目前已有多个成熟的并发库可以直接使用,包括基于Java的RxJava、基于Kotlin的协程等,然而他们的底层原理都是与上述类似的。至于如何使用现有的多线程开发库,将在后续文章中详细介绍。
任务间通信(多线程通信)
由于不同任务是运行在不同线程中的,所以任务间通信实际上也是线程间的通信。这主要通过android.os.Handler类来实现的。说到通信的话就是一方发送内容和另一方接收内容的过程,Android系统将要通信的内容封装为android.os.Message类,其中有 int arg1
和int arg2
两个属性储存简单的数值内容、Object obj
属性存储任意类型的对象、int what
属性可以标记区分不同的Message
类型。
通信接收线程
在需要处理通信内容的线程中,创建Handler
实例化对象。
可以使用Handler(Looper looper)
构造方法或者createAsync(Looper looper)
静态方法,创建处理任意内容的实例化对象。其中的参数 looper 标记当前Handler
对象中的处理操作是在哪个线程,如果是主线程可以使用Looper.getMainLooper()
静态方法获取android.os.Looper对象,如果是子线程,可以在子线程的run()
方法中使用Looper.myLooper()
静态方法获取当前线程的Looper
对象。
或者使用Handler(Looper looper, Handler.Callback callback)
构造方法或者createAsync(Looper looper, Handler.Callback callback)
静态方法,创建需要接收Message
消息处理的实例化对象。参数 looper 同样是标记当前Handler
对象中的处理操作是在哪个线程。参数 callback 是android.os.Handler.Callback接口实现的实例化对象,其中实现的handleMessage(Message msg)
方法可以接收并处理通信的结果。这里的参数 msg 就是收到的Message
消息内容。
如果是通过Looper.myLooper()
静态方法获取的Looper
对象,也就是在子线程中处理通信结果的话,在创建Handler
对象前后还要特别调用两个方法。
在上面初始化Handler
对象之前,必须在子线程中先调用Looper.prepare()
静态方法以初始化Looper
对象,以此保证在调用Looper.myLooper()
方法时获取到的对象非空。
以及在初始化Handler
对象之后,必须在当前子线程中及时调用Looper.loop()
静态方法以准备Message
消息队列供当前子线程使用。
通信发送线程
在需要发送通信内容的线程中,需要首先接收到上文创建的Handler
实例化对象。
在切换线程处理的位置,调用Handler
对象的post(Runnable r)
系列方法,参数 r 就是要在Handler
对象所在线程中处理的Runnable
任务对象。
或者在需要发送消息的位置,调用Handler
对象的obtainMessage()
系列方法,可以获取到空闲可以使用的Message
消息对象,将要发送的消息体内容赋值给Message
对象的不同属性。最后再调用Handler
对象的sendMessage(Message msg)
系列方法,将消息体Message
对象发送即可。