4.3 异步任务
Posted qkeyar
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了4.3 异步任务相关的知识,希望对你有一定的参考价值。
- 在子线程(new Thread)需要修改UI时:二者的作用都是将你正在子线程的操作抛到UI线程上!
View.post(Runnable) View.postDelayed(Runnable, long) Activity.runOnUiThread(Runnable)
- 在主线程中新建异步任务:Handler 和 AsyncTask
4.3.1 Handler
Andriod 提供了Handler 和 Looper 来满足线程间的通信。
1) Looper: 一个线程可以产生一个Looper 对象,管理(轮询)此线程里的MessageQueue,是一个消息分发器。
2) Handler: 在主线程中创建,与Looper 沟通,往消息队列放消息和接收从Looper从消息队列取出的消息。
3) Message Queue(消息队列):用来存放线程放入的消息,先进先出原则。
4) 线程:UIthread 通常就是main thread,而android 启动程序时会替它建立一个MessageQueue。
举个例子来说明一下:在子线程中更新UI
因为UI只能在主线程中更新,如果我们想在子线程中更新UI,就要用到这一套机制了,在主线程和子线程之间传递信息。首先,现在主线程中创建一个handler对象,Looper对象和messageQueue对象是不用创建的,因为在系统源码中这个是在主线程创建的时候就创建好的。然后重写handlerMessage()方法,这个方法是在handler接收到message之后执行的方法,我们把更新UI的代码放到这个方法中。然后再子线程中用sendEmptyMessage()方法来发送一个消息到消息队里。在发送完这个消息之后,Looper这个轮询器因为一直在轮询消息队列。获得到这个消息之后就会执行handlerMessage()方法,因为这个方法是在主线程中实现的。所以UI就可以更新了。
handler机制:可以再主线程中给子线程发送消息,也可以再子线程中给主线程发送消息:
- 在子线程中给主线程发送消息:
在主线程中创建一个handler对象,重写handlmessage()方法,在子线程中调用handler.sendEmptyMessage()方法,然后在handlermessage()中就可以执行想要执行的操作了,比如说更新UI
- 在主线程中给子线程发送消息:
在子线程中new出来一个handler对象,然后重写handlerMessage方法,在主线程sendemptymessage();只是这样是不行了,因为handler要处理消息,但是在子线程中是没有Looper对象的,我们要通过Looper.prepar()来创建一个Looper对象, 在这里Looper对像被创建的时候会创建一个MessageQueue,这样就有Looper了,然后再执行完操作之后要执行以下Looper.loop()方法。去轮询这些消息,让handler来处理这些消息。在安卓的源代码中,Looper.loop()方法是一个永远为true的while循环,所以我们要在子线程中执行的动作,都要在这个方法前去执行,如果在这个方法后去执行的话,就不能被执行到。
创建handler对象和调用都写在Loop中:
Loop.prepare() new handler(… handleMessage()… Loop.loop()
- Handler中分发消息的方法:(两类)
1) post类方法允许你排列一个Runnable对象到主线程队列中:post/ postAtTime/ postDelayed (Runnable long)
2) sendMessage类方法, 允许你安排一个带数据的Message对象到队列中,等待更新:
sendEmptyMessage(int) sendMessage(Message)
sendMessageAtTime(Message,long) sendMessageDelayed(Message,long)
- Handler实例:继承Hendler类,并重写handleMessage(Message msg) 方法
class MyHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }
【ThreadLocal】
Looper中还有一个特殊的概念,那就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。大家知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。在日常开发中用到ThreadLocal的地方较少,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
比如对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取,如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。
ThreadLocal另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢?其实就可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。
而如果不采用ThreadLocal,那么我们能想到的可能是如下两种方法:第一种方法是将监听器通过参数的形式在函数调用栈中进行传递;第二种方法就是将监听器作为静态变量供线程访问。
上述这两种方法都是有局限性的。第一种方法的问题时当函数调用栈很深的时候,通过函数参数来传递监听器对象这几乎是不可接受的,这会让程序的设计看起来很糟糕。第二种方法是可以接受的,但是这种状态是不具有可扩充性的,比如如果同时有两个线程在执行,那么就需要提供两个静态的监听器对象,如果有10个线程在并发执行呢?提供10个静态的监听器对象?这显然是不可思议的,而采用ThreadLocal每个监听器对象都在自己的线程内部存储,就不会有方法二的这种问题。
4.3.2 HandlerThread
HandlerThread继承自Thread,当线程开启时,也就是它run方法运行起来后,线程同时创建了一个含有消息队列的Looper,并对外提供自己这个Looper对象的get方法,这就是它和普通Thread唯一不同的地方。优势有:
1) 开发中如果多次使用类似new Thread(){...}.start(),这种方式开启一个子线程,会创建多个匿名线程,使得程序运行起来越来越慢,而HandlerThread自带Looper使他可以通过消息来多次重复使用当前线程,节省开支;
2) android系统提供的Handler类内部的Looper默认绑定的是UI线程的消息队列,对于非UI线程又想使用消息机制,那么HandlerThread内部的Looper是最合适的,它不会干扰或阻塞UI线程。(Handler+Thread)
HandlerThread既然本质是Thread,为何前面加了一个Handler?
android中Handler类本质上就是从它内部的Looper中不断取消息,然后触发它内部的Callback接口的handleMessage方法,让用户去实现对消息的具体处理。
而HandlerThread本身自带Looper,只要它实现了Callback接口,那么HandlerThread也可以在自己线程内处理自己线程发出的消息,充分实现非UI线程中较低开支下的消息处理。
class MyHandlerThread extends HandlerThread implements Callback {……}
4.3.3 AsyncTask
Android为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。AsyncTask比Handler更轻量级一些,是对Thread+Handler的良好封装,本质是个线程池。AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask异步加载数据最少要重写以下这两个方法:
- doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,在执行过程中可以调用public progress(Progress…)来更新任务的进度。
- onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
使用AsyncTask类,以下是几条必须遵守的准则:
- Task的实例必须在UI thread中创建; execute方法必须在UI thread中调用;
- 不要手动的调用onPreExecute(), onPostExecute(),doInBackground(), onProgressUpdate()这几个方法;
- 该task只能被执行一次,否则多次调用时将会出现异常;
ProgressBarAsyncTask asyncTask = new ProgressBarAsyncTask(textView, progressBar); asyncTask.execute(1000); // 参数在params中获得,如果要取消任务asyncTask.cancle(true);
public class ProgressBarAsyncTask extends AsyncTask<Integer, Integer, String> { }
- AsyncTask实现的原理,和适用的优缺点
AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程。
使用的优点: 简单,快捷;过程可控
使用的缺点: 在使用多个异步操作和并需要进行UI变更时,就变得复杂起来.
- Handler异步实现的原理和适用的优缺点
在Handler 异步实现时,涉及到 Handler,Looper,Message,Thread四个对象,实现异步的流程是主线程启动Thread(子线程),thread(子线程)运行并生成Message;Looper获取Message并传递给Handler;Handler逐个获取Looper中的Message,并进行UI变更。
使用的优点: 结构清晰,功能定义明确;对于多个后台任务时,简单,清晰
AsyncTask这个类感觉使用比较简单,就是实现其中几个方法,onPreExecute()方法是在任务刚开始运行时执行的一些初始化操作,比如初 始化一个进度条等等,然后就执行doInBackground()方法这里面主要放业务操作,比如查询数据库等,在这个方法执行的时候会调用 onProgressUpdate(),可以在这个方法中更新UI界面,最后是调用onPostExecute()方法,当得到业务结果后就可以在这个方 法中返回给UI线程,也可以关闭一些执行这个业务时开的一些资源。大家可以看得出AsyncTask这个类是一个泛型类,这个类的三个参数以此对应 doInBackground(String... params),onProgressUpdate(String... values),onPostExecute(String result)的参数。如果不需要传参和返回值,可以用Void代替。而doInBackground(String... params)方法的返回值也就是onPostExecute(String result)方法的参数值,因为doInBackground方法执行后返回的值是在onPostExecute(String result)中处理的。
Android平台很多应用使用的都是AsyncTask,而并非Thread和Handler去更新UI,这里给大家说下他们到底有什 么区别,我们平时应该使用哪种解决方案。从Android 1.5开始系统将AsyncTask引入到android.os包中,过去在很早1.1和1.0 SDK时其实官方将其命名为UserTask,其内部是JDK 1.5开始新增的concurrent库,做过J2EE的网友可能明白并发库效率和强大性,比Java原始的Thread更灵活和强大,但对于轻量级的使 用更为占用系统资源。Thread是Java早期为实现多线程而设计的,比较简单不支持concurrent中很多特性在同步和线程池类中需要自己去实现 很多的东西,对于分布式应用来说更需要自己写调度代码,而为了Android UI的刷新Google引入了Handler和Looper机制,它们均基于消息实现,有时可能消息队列阻塞或其他原因无法准确的使用。
用AsyncTask代替Thread+Handler的方式,不仅调用上更为简单,经过实测更可靠一些,Google在Browser中大量使用了异步任务作为处理耗时的I/O操作,比如下载文件、读写数据库等等,它们在本质上都离不开消息,但是AsyncTask相比Thread加 Handler更为可靠,更易于维护,但AsyncTask缺点也是有的比如一旦线程开启即dobackground方法执行后无法给线程发送消息,仅能通过预先设置好的标记来控制逻辑,当然可以通过线程的挂起等待标志位的改变来通讯,对于某些应用Thread和Handler以及Looper可能更灵活。
4.3.4 三种定时器的写法
一、 使用Timer 和 TimerTask Timer timer = new Timer(); MyTimerTask timerTask = new MyTimerTask(); timer.schedule(timerTask, 3000, 1); // 延迟3秒钟,执行1次 //timer.cancel(); // 取消 class MyTimerTask extends TimerTask { @Override public void run() { // do something 这里不能处理UI操作 } }
二、采用Handler的sendMessageDelayed(Message, long) public void startAutoFlowTimer() { handler = new Handler() { @Override public void handleMessage(Message msg) { // do something Message message = handler.obtainMessage(0); sendMessageDelayed(message, 1000); } }; Message message = handler.obtainMessage(0); handler.sendMessageDelayed(message, 1000); } handler.removeMessages(0) //结束调用
三、采用Handler的postDelayed(Runnable, long) 方法,这个实现比较简单一些: 1. Handler handler=new Handler(); Runnable runnable=new Runnable(){ //(多定义Runnable对象) @Override public void run() { // do something handler.postDelayed(this, 2000); //在这里实现每两秒执行一次 } }; handler.postDelayed(runnable, 2000);//启动,两秒后执行runnable. handler.removeCallbacks(runnable); //停止
//延迟执行 设置异步线程, Handler handler = new Handler() { public void handleMessage(Message msg) { // handle message switch (msg.what) { case 1: //ui 方法 } super.handleMessage(msg); } }; Message message = handler.obtainMessage(1); handler.sendMessageDelayed(message, 3000); // 发送message,3秒后隐藏提示框
以上是关于4.3 异步任务的主要内容,如果未能解决你的问题,请参考以下文章
我应该在 Fragment 中的啥生命周期状态下执行异步任务?