掌握Android的多线程通信机制,我们首先应该掌握Android中进程与线程是什么。
###1. 进程
在Android中,一个应用程序就是一个独立的进程(应用运行在一个独立的环境中,可以避免其他应用程序/进程的干扰)。一般来说,当我们启动一个应用程序时,系统会创建一个进程(从Zygote中fork出来的,这个进程会有独立的ID),并为这个进程创建一个主线程(UI线程),然后就可以运行MainActivity了,应用程序的组件默认都是运行在它的进程中,但我们可以通过指定应用的组件(四大组件)的运行进程:`android:process`来让组件运行在不同的进程中;让组件运行在不同的进程中,会带来好处,也会带来坏处:

  • 好处是:因为每一个应用程序(也就是每一个进程)都会有一个内存预算,所有运行在这个这个进程中的程序使用的总内存不能超过这个值,让组件运行不同的进程中,可以让主进程可以拥有更多的空间资源。

  • 坏处是:每个进程都会有自己的虚拟机实例,因此让在进程间共享一些数据变得困难;(当然,我们可以采用多进程间的通信来实现数据的共享)

    当我们的应用程序比较大,需要的内存资源比较多时(也就是用户会抱怨应用经常出现OutOfMemory时),可以考虑使用多进程

2. 线程

在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主线程中做耗时的操作,如网络连接,文件的IO;

  • 只能在UI主线程中做更新UI的操作;

    在Android中,我们把除UI线程外的,其他所有的线程都叫做工作线程,也就是说Android只会存在两种线程:UI主线程(UI thread)和工作线程(work thread).我们不能在UI主线程中做耗时的操作,因此我们可以把耗时的操作放在另一个工作线程中去做。操作完成后,再通知UI主线程做出相应的响应。这就需要掌握线程间通信的方式了。在Android中提供了两种线程间的通信方式:一种是AsyncTask机制,另一种是Handler机制;

2.1 Android线程间通信方式之AsyncTask机制

AsyncTask,异步任务,也就是说在UI线程运行的时候,可以在后台的执行一些异步的操作;AsyncTask可以很容易且正确地使用UI线程,AsyncTask允许进行后台操作,并在不显示使用工作线程或Handler机制的情况下,将结果反馈给UI线程。但是AsyncTask只能用于短时间的操作(最多几秒就应该结束的操作),如果需要长时间运行在后台,就不适合使用AsyncTask了,只能去使用Java提供的其他API来实现。
2.1.1 AsyncTask的使用

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 <Params, Progress, Result>

  • Params..要执行的任务的参数类型;
  • Progress,在后台执行的任务的进度;
  • Results,后台执行的任务的最后结果;

当一个AsyncTask任务执行时,它会经历四个步骤

1. onPreExecute(),在任务执行前调用,用来做一些UI的初始化工作,在UI线程中执行,如执行一个AsyncTask的下载任务时,初始化类似“正在下载中”的窗口;

2. doInBackground(Params...),在后台执行任务,是在工作线程中进行的;

3. onProgressUpdate(Progress...),更新正在后台执行任务的进度,在UI线程中工作,可用来向通知用户任务现在的完成进度(可更新UI),在调用这个方法前,需要在第2个步骤里调用 publishProgress(Progress...)将进度传递给这个方法;

4. onPostExecute(Result),在后台任务完成后,会将结果返回给这个方法,在UI线程中调用,可以更新UI;

在使用AsyncTask的时候,需要注意的地方

  • AsyncTask类必须在UI线程中加载,这在在Android Jelly_Bean版本后这些都是自动完成的;
  • AsyncTask类必须在UI线程中实例化;
  • 不要手动去调用 onPreExecute(),doInBackground(Params...),publishProgress(Progress...),onPostExecute(Result)方法,这些方法都会由系统自动去调用。
  • execute(Params...)方法必须在UIt线程中调用,如在UI线程中执行这个语句:new DownloadFilesTask().execute(url1, url2, url3);我们不需要做其他的工作,这个下载任务就会自动在后台执行了,并且AsynacTask任务只能执行一次;

2.2 Android线程间通信方式之Handler机制;

 使用Handler机制,也就是通过使用Handler,Looper,MessageQueue,和Message这几个类协调来完成。我们先来看使用Handler机制完成线程间通信的原理,然后再详细介绍这几个类;先看一下这几个类的作用:
  • Handler,发送和处理Message对象和Runnable对象;

  • Looper,用于从MessageQueue中取出消息(Message)对象,并发送给Handler处理;

  • MessageQueue,消息队列,用于存放通过Handler发布的消息;

  • Message,消息的类型,里面包含几个实例对象:

  • what,用户定义的int型消息代码,用来描述消息;

  • obj,随消息发送的用户用户指定对象;

  • 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线程间异步通信的原理,下面我们再来详细了解下这四个核心类;

2.2.1 Handler
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对象发送到消息队列中,它的方法有: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主线程中的);

2.2.2 Message
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 属性作用相同);

2.2.3 MessageQueue

MessageQueue保存由Looper调度的消息列表,消息通过与Looper相关联的Handler对象添加进MessageQueue。

2.2.4 Looper
Looper为线程运行一个消息的循环队列,主要就是为了完成MessageQueue与Handler交互的功能;需要注意的是线程默认并不会给我们提供一个一个Looper实例来管理消息队列,我们需要在线程中主动调用Looper.prepare()方法来实例化一个Looper对象,用于管理消息队列;Looper对象会不断去判断MessageQueue是否为空,如果不空,则将Message取出给相应的Handler进行处理;如果MessageQueue为空,则Looper对象会进行阻塞状态,直到有新的消息进入MessageQueue;

其实,说白了,Android中通过Handler机制来异步处理多线程间的通信就是多个线程间共享一个MessageQueue,工作线程将消息发送到MessageQueue,然后UI线程或其他工作线程在MessageQueue在取出消息,进行相应的处理;



参考:

http://www.cnblogs.com/plokmju/p/android_handler.html

Google官方文档

Android多线程通信机制的更多相关文章

  1. Android 进程通信机制之 AIDL

    什么是 AIDL AIDL 全称 Android Interface Definition Language,即 安卓接口描述语言.听起来很深奥,其实它的本质就是生成进程间通信接口的辅助工具.它的存在 ...

  2. Android多线程消息处理机制

    (1)主线程和ANR 主线程:UI线程,界面的修改只能在主线程中,其它线程对界面进行修改会造成异常.这样就解决了多线程竞争UI资源的问题. 一旦主线程的代码阻塞,界面将无法响应,这种行为就是Appli ...

  3. Python-全局解释器锁GIL原理和多线程产生原因与原理-多线程通信机制

    GIL 全局解释器锁,这个锁是个粗粒度的锁,解释器层面上的锁,为了保证线程安全,同一时刻只允许一个线程执行,但这个锁并不能保存线程安全,因为GIL会释放掉的并且切换到另外一个线程上,不会完全占用,依据 ...

  4. android ipc通信机制之二序列化接口和Binder

    IPC的一些基本概念,Serializable接口,Parcelable接口,以及Binder.此核心为最后的IBookManager.java类!!! Serializable接口,Parcelab ...

  5. Android 多线程通信 访问网络

    package org.rongguang.testthread; import android.app.Activity; import android.os.Bundle; import andr ...

  6. android ipc通信机制之之三,进程通讯方式。

    IPC通讯方式的优缺点: IPC通讯方式的对比 名称 优点 缺点 适用场景 Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件的进程通信 文件共享 简单易用 不适合高并发场景,并无法 ...

  7. Android多线程通信之Handler

    主线程 public class MainActivity extends ActionBarActivity { private Handler handler; // private Thread ...

  8. android 多线程Thread,Runnable,Handler,AsyncTask

    先看两个链接: 1.http://www.2cto.com/kf/201404/290494.html 2. 链接1: android 的多线程实际上就是java的多线程.android的UI线程又称 ...

  9. Binder通信机制介绍

    1.Binder通信机制介绍 这篇文章会先对比Binder机制与Linux的通信机制的差别,了解为什么Android会另起炉灶,采用Binder.接着,会根据 Binder的机制,去理解什么是Serv ...

随机推荐

  1. JS中的“!!”

    var o={flag:true};  var test=!!o.flag;//等效于var test=o.flag||false;  alert(test); 由于对null与undefined用! ...

  2. [No000054] Windows 下Python3.5, NoteBook增强版安装

    接着上周继续,没看的童鞋.请移步: http://www.cnblogs.com/Chary/p/No00004B.html 这里,假设你已经能够看到这个画面了: 接下来,我们继续 给药 : 安装no ...

  3. 直线的参数方程ABC

    直线的参数方程的来源 如图所示, 直线\(l\)的倾斜角为\(\theta\),经过定点\(P_0(x_0,y_0)\),在直线上有一动点\(P(x,y)\),如果我们取直线的单位方向向量\(\vec ...

  4. scalatest的userguide

    http://www.scalatest.org/user_guide 感觉功能很强大.这门语言有前途.

  5. AudioRecord类获取录音音量分贝数

    转自:http://www.jb51.net/article/64806.htm   public class AudioRecordDemo {     private static final S ...

  6. MyBatis的Mapper文件的foreach标签详解

    MyBatis的Mapper文件的foreach标签用来迭代用户传递过来的Lise或者Array,让后根据迭代来拼凑或者批量处理数据.如:使用foreach来拼接in子语句. 在学习MyBatis M ...

  7. PHP处理0e开头md5哈希字符串缺陷/bug

    PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他 ...

  8. Map工具系列-06-销售营改增历史数据处理工具

    所有cs端工具集成了一个工具面板 -打开(IE) Map工具系列-01-Map代码生成工具说明 Map工具系列-02-数据迁移工具使用说明 Map工具系列-03-代码生成BySQl工具使用说明 Map ...

  9. <<< 三大框架简短介绍

    Struts 搞业务 Spring 主要是AOP(面向方面编程)和IOC(控制反转)它里面用到很多的设计模式Hibernate Orm映射工具 实现面向对象的方式操作数据库hibernate 封装了j ...

  10. Google Map API V3开发(2)

    Google Map API V3开发(1) Google Map API V3开发(2) Google Map API V3开发(3) Google Map API V3开发(4) Google M ...