Android多线程通信机制
掌握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多线程通信机制的更多相关文章
- Android 进程通信机制之 AIDL
什么是 AIDL AIDL 全称 Android Interface Definition Language,即 安卓接口描述语言.听起来很深奥,其实它的本质就是生成进程间通信接口的辅助工具.它的存在 ...
- Android多线程消息处理机制
(1)主线程和ANR 主线程:UI线程,界面的修改只能在主线程中,其它线程对界面进行修改会造成异常.这样就解决了多线程竞争UI资源的问题. 一旦主线程的代码阻塞,界面将无法响应,这种行为就是Appli ...
- Python-全局解释器锁GIL原理和多线程产生原因与原理-多线程通信机制
GIL 全局解释器锁,这个锁是个粗粒度的锁,解释器层面上的锁,为了保证线程安全,同一时刻只允许一个线程执行,但这个锁并不能保存线程安全,因为GIL会释放掉的并且切换到另外一个线程上,不会完全占用,依据 ...
- android ipc通信机制之二序列化接口和Binder
IPC的一些基本概念,Serializable接口,Parcelable接口,以及Binder.此核心为最后的IBookManager.java类!!! Serializable接口,Parcelab ...
- Android 多线程通信 访问网络
package org.rongguang.testthread; import android.app.Activity; import android.os.Bundle; import andr ...
- android ipc通信机制之之三,进程通讯方式。
IPC通讯方式的优缺点: IPC通讯方式的对比 名称 优点 缺点 适用场景 Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件的进程通信 文件共享 简单易用 不适合高并发场景,并无法 ...
- Android多线程通信之Handler
主线程 public class MainActivity extends ActionBarActivity { private Handler handler; // private Thread ...
- android 多线程Thread,Runnable,Handler,AsyncTask
先看两个链接: 1.http://www.2cto.com/kf/201404/290494.html 2. 链接1: android 的多线程实际上就是java的多线程.android的UI线程又称 ...
- Binder通信机制介绍
1.Binder通信机制介绍 这篇文章会先对比Binder机制与Linux的通信机制的差别,了解为什么Android会另起炉灶,采用Binder.接着,会根据 Binder的机制,去理解什么是Serv ...
随机推荐
- Android与Struts2简单json通信
具体要求是: 服务器端得到客户端传递来的数据,并返回给客户端一条json格式的字符串 闲话不多说,直接上代码 首先是服务器端代码:建立一个web工程,导入struts2和json的jar包,并在web ...
- angular的跨域(angular百度下拉提示模拟)和angular选项卡
1.angular中$http的服务: $http.get(url,{params:{参数}}).success().error(); $http.post(url,{params:{参数}}).su ...
- Intellij Idea中定制getter setter的模板
Alt + Ins, 调出快捷菜单后选择 Getter and Setter, 在对话框里, 选择对应的template, 右侧点开后, 可以新建自己的模板并编辑 将getter和setter都生成到 ...
- c语言游戏推箱子
前两天做了推箱子小游戏,看似简单的一个小游戏背后却 有巨大的秘密,这秘密就是一大堆逻辑. 自从学习了函数过后,的确是解决了很多问题,而且调用很方便,尽管我现在都不是很会调用. 写完一个函数,准备测试一 ...
- ST教学分析:相同行为连续数
转载自:http://zhidao.baidu.com/link?url=cm8k1RuA3fZ8FQCHYXXiY7xseVs5IJo63A8oq3FZwhSgaGNe4c2U2ZDpBToGYn5 ...
- 详解CSS中:nth-child的用法
前端的哥们想必都接触过css中一个神奇的玩意,可以轻松选取你想要的标签并给与修改添加样式,是不是很给力,它就是“:nth-child”. 下面我将用几个典型的实例来给大家讲解:nth-child的实际 ...
- Android开发自学笔记(Android Studio)—4.2TextView及其子类
一.引言 TextView是我们最常用的一个控件了,它类似于C# Winform程序中的Lable,Java Swing编程中的JLable,不过相对功能更强大些,但从功能上看,它其实就是个文字编辑器 ...
- 如何指定个别属性进行transition过渡
transition是CSS3新增的动画属性,可以实现属性的平滑过渡,大大提高用户体验,对于多个属性进行过渡的话很多人会这样写 .tr{ transition:all 1s} 很不幸的是如果我只需要对 ...
- .Net Core Linux centos7行—.net core json 配置文件
.net core 对配置系统做出了大幅度更新,不在局限于之前的*.xml配置方式.现在支持json,xml,ini,in memory,环境变量等等.毫无疑问的是,现在的json配置文件是.net ...
- cosbench 异常 FreeMarker template error: The following has evaluated to null or missing
问题现象: 使用Cosbench 0.4.2.c4 版本测试Ceph RGW read test失败,遇到异常如下: FreeMarker template error: The following ...