掌握Android的多线程通信机制,我们首先应该掌握Android中进程与线程是什么。
在Android中,一个应用程序就是一个独立的进程(应用运行在一个独立的环境中,可以避免其他应用程序/进程的干扰)。一般来说,当我们启动一个应用程序时,系统会创建一个进程(从Zygote中fork出来的,这个进程会有独立的ID),并为这个进程创建一个主线程(UI线程),然后就可以运行MainActivity了,应用程序的组件默认都是运行在它的进程中,但我们可以通过指定应用的组件(四大组件)的运行进程:`android:process`来让组件运行在不同的进程中;让组件运行在不同的进程中,会带来好处,也会带来坏处:
坏处是:每个进程都会有自己的虚拟机实例,因此让在进程间共享一些数据变得困难;(当然,我们可以采用多进程间的通信来实现数据的共享)
当我们的应用程序比较大,需要的内存资源比较多时(也就是用户会抱怨应用经常出现OutOfMemory时),可以考虑使用多进程
在Java中,线程会有那么几种状态:创建,就绪,运行,阻塞,死亡。当应用程序有组件在运行时,UI线程是处于运行状态的。默认情况下,应用的所有组件的操作都是在UI线程里完成的,包括响应用户的操作(触摸,点击等),组件生命周期方法的调用,UI的更新等。因此如果UI线程处理阻塞状态时(在线程里做一些耗时的操作,如网络连接等),就会不能响应各种操作,如果阻塞时间达到5秒,就会让程序处于ANR(application not response)状态,这时用户就可能退出你的应用甚至卸载你的应用,这是我们不能接受的。 这时,有人就会说,我们在其他线程中更新UI不就可以了吗,就不会导致UI线程处于阻塞状态了。但答案是否定的。
因为Android的UI线程是非线程安全的,应用更新UI,是调用invalidate()方法来实现界面的重绘,而invalidate()方法是非线程安全的,也就是说当我们在非UI线程来更新UI时,可能会有其他的线程或UI线程也在更新UI,这就会导致界面更新的不同步。因此我们不能在非UI主线程中做更新UI的操作。也就是说我们在使用Android中的线程时,要保证:
只能在UI主线程中做更新UI的操作;
在Android中,我们把除UI线程外的,其他所有的线程都叫做工作线程,也就是说Android只会存在两种线程:UI主线程(UI thread)和工作线程(work thread).我们不能在UI主线程中做耗时的操作,因此我们可以把耗时的操作放在另一个工作线程中去做。操作完成后,再通知UI主线程做出相应的响应。这就需要掌握线程间通信的方式了。在Android中提供了两种线程间的通信方式:一种是AsyncTask机制,另一种是Handler机制;
AsyncTask,异步任务,也就是说在UI线程运行的时候,可以在后台的执行一些异步的操作;AsyncTask可以很容易且正确地使用UI线程,AsyncTask允许进行后台操作,并在不显示使用工作线程或Handler机制的情况下,将结果反馈给UI线程。但是AsyncTask只能用于短时间的操作(最多几秒就应该结束的操作),如果需要长时间运行在后台,就不适合使用AsyncTask了,只能去使用Java提供的其他API来实现。
AsyncTask只能通过继承来使用,如:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { //处理异步任务的方法,是在工作线程中执行的,必须实现这个方法
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
//更新后台任务的完成进度,可随时向UI线程反馈执行进度,方法是在UI线程中执行的
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
//任务的最终结果,这个方法是在UI线程中执行的,
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
AsyncTask的几个参数,AsyncTask :
当一个AsyncTask任务执行时,它会经历四个步骤:
1.onPreExecute()
,在任务执行前调用,用来做一些UI的初始化工作,在UI线程中执行,如执行一个AsyncTask的下载任务时,初始化类似“正在下载中”的窗口;
doInBackground(Params...)
,在后台执行任务,是在工作线程中进行的;onProgressUpdate(Progress...)
,更新正在后台执行任务的进度,在UI线程中工作,可用来向通知用户任务现在的完成进度(可更新UI),在调用这个方法前,需要在第2个步骤里调用 publishProgress(Progress...)
将进度传递给这个方法;onPostExecute(Result)
,在后台任务完成后,会将结果返回给这个方法,在UI线程中调用,可以更新UI;在使用AsyncTask的时候,需要注意的地方:
onPreExecute()
,doInBackground(Params...)
,publishProgress(Progress...)
,onPostExecute(Result)
方法,这些方法都会由系统自动去调用。new DownloadFilesTask().execute(url1, url2, url3)
;我们不需要做其他的工作,这个下载任务就会自动在后台执行了,并且AsynacTask任务只能执行一次; 使用Handler机制,也就是通过使用Handler,Looper,MessageQueue,和Message这几个类协调来完成。我们先来看使用Handler机制完成线程间通信的原理,然后再详细介绍这几个类;先看一下这几个类的作用:
Message,消息的类型,里面包含几个实例对象:
target,处理消息的Handler;
一个Handler对象仅与一个Looper相关联,一个Message也仅与一个目标Handler对象相关联,一个Looper对象拥有一个MessageQueue。但多个不同的Handler对象可以与同一个对象相关联,也就是说多个Handler可以共享一个MessageQueue,从而达到消息共享的目的,这也是Android通过Handler机制实现多线程间通信的核心原理;
上面说到Handler对象仅与一个Looper相关联,那么这个关联是什么时候实现的呢?答案是:Handler对象创建的时候。UI主线程在创建的时候就会拥有一个handler对象和一个Looper对象(工作线程需要自己调用Looper.prepare()来创建一个Looper对象),然后任何在UI主线程中创建的Handler对象默认都会与UI主线程的Looper对象相关联(Handler对象创建的时候,会与这个线程的Looper对象相关联)。进而我们就可以把在UI主线程中创建的Handler对象传递给(依赖注入或引用形式)工作线程,那么在工作线程中用这个Handler对象处理的消息就是在UI主线程的MessageQueue中处理的,从而达到线程间通信的目的。
了解了使用Handler机制来实现Android线程间异步通信的原理,下面我们再来详细了解下这四个核心类;
Handler,继承自Object类,用来发送和处理Message对象或Runnable对象;Handler在创建时会与当前所在的线程的Looper对象相关联(如果当前线程的Looper为空或不存在,则会抛出异常,此时需要在线程中主动调用Looper.prepare()来创建一个Looper对象)。使用Handler的主要作用就是在后面的过程中发送和处理Message对象和让其他的线程完成某一个动作(如在工作线程中通过Handler对象发送一个Message对象,让UI线程进行UI的更新,然后UI线程就会在MessageQueue中得到这个Message对象(取出Message对象是由其相关联的Looper对象完成的),并作出相应的响应)。
Handler用post
体系来完成发送Runnable对象的工作,用sendMessage
体系 来完成发送Message对象的工作;
post(Runnable)
,postDelayed(Runnable,long)
,postAtTime(Runnable,long)
;sendMessage体系,把Message对象发送给消息队列,它的方法有:sendEmptyMessage(int)
,sendMessage(Message)
,sendMessageDelayed(Message,long)
,sendMessageAtTime(Message,long)
;
如果Handler是通过post体系将Runnable对象发送到MessageQueue队列中,则这个Runnable对象的run()
方法是运行在Handler对象创建时所在线程;
如果Handler是通过sendMessage体系将Message发送到MessageQueue中,则需要重写handleMessage()方法来获取工作线程传递过来的Message对象,handleMessage()
方法是工作在Handler对象建立时所在的线程的(一般我们会在UI线程中建立Handler对象,然后传递给子线程,此时这个Handler对象的handleMessage()
方法就是运行在UI主线程中的);
Message用来定义一个包含任意数据的消息对象,这个消息对象是可以被发送给Handler处理的。我们最好通过Message.obtain()和Handler.obtatinMessage()来得到一个Message对象(通过这两个方法得到的对象是从对象回收池中得到,也就是说是复用已经处理完的Message对象,而不是重新生成一个新对象),如果通过Message的构造方法得到一个Message对象,则这个Message对象是重新生成的(不建议使用这种方法)。
Message对象用来封装需要传递的消息,Message的数据结构为:
Message{
int arg1;//如果我们只需要存储一些简单的Integer数据,则可通过设置这个属性来传递
int agr2;//使用同arg1
Object obj; //设置需要发送给接收方的对象,这个对象需要实现序列化接口
int what; //描述这个消息的标识;
//设置与这个消息对应的任意数据,这个数据是用Bundle封装的;
void setData(Bundle data);
Bundle getData(); 得到与这个消息对应的数据信息;
//省略了方法和可选的属性
......
如果需要通过Message对象传递一些比较复杂的数据,则需要使用将数据封装成Bundle对象,然后通过setData(Bundle)方法来传递,用getData()来得到与这个消息对应的数据(这方法与设置Message的Object 属性作用相同);
MessageQueue保存由Looper调度的消息列表,消息通过与Looper相关联的Handler对象添加进MessageQueue。
Looper为线程运行一个消息的循环队列,主要就是为了完成MessageQueue与Handler交互的功能;需要注意的是线程默认并不会给我们提供一个一个Looper实例来管理消息队列,我们需要在线程中主动调用Looper.prepare()方法来实例化一个Looper对象,用于管理消息队列;Looper对象会不断去判断MessageQueue是否为空,如果不空,则将Message取出给相应的Handler进行处理;如果MessageQueue为空,则Looper对象会进行阻塞状态,直到有新的消息进入MessageQueue;
其实,说白了,Android中通过Handler机制来异步处理多线程间的通信就是多个线程间共享一个MessageQueue,工作线程将消息发送到MessageQueue,然后UI线程或其他工作线程在MessageQueue在取出消息,进行相应的处理;