`
844604778
  • 浏览: 551781 次
文章分类
社区版块
存档分类
最新评论

AsyncTask,Handler,Looper

 
阅读更多
AsyncTask的隐蔽陷阱,先来看一个实例,展示了AsyncTask的一种极端用法。

public class AsyncTaskTrapActivity extends Activity {
private SimpleAsyncTask asynctask;
private Looper myLooper;
private TextView status;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
asynctask = null;
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
myLooper = Looper.myLooper();
status = new TextView(getApplication());
asynctask = new SimpleAsyncTask(status);
Looper.loop();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
setContentView((TextView) status, params);
asynctask.execute();
}

@Override
public void onDestroy() {
super.onDestroy();
myLooper.quit();
}

private class SimpleAsyncTask extends AsyncTask<Void, Integer, Void> {
private TextView mStatusPanel;

public SimpleAsyncTask(TextView text) {
mStatusPanel = text;
}

@Override
protected Void doInBackground(Void... params) {
int prog = 1;
while (prog < 101) {
SystemClock.sleep(1000);
publishProgress(prog);
prog++;
}
return null;
}

// Not Okay, will crash, said it cannot touch TextView
@Override
protected void onPostExecute(Void result) {
mStatusPanel.setText("Welcome back.");
}

// Okay, because it is called in #execute() which is called in Main thread, so it runs in Main Thread.
@Override
protected void onPreExecute() {
mStatusPanel.setText("Before we go, let me tell you something buried in my heart for years...");
}

// Not okay, will crash, said it cannot touch TextView
@Override
protected void onProgressUpdate(Integer... values) {
mStatusPanel.setText("On our way..." + values[0].toString());
}
}
}


这个例子在Android2.3中无法正常运行,在执行onProgressUpdate()和onPostExecute()时会报出异常但在Android4.0及以上的版本中运行就正常(3.0版本未测试)。


从2.3运行时的Stacktrace来看原因是在非UI线程中操作了UI组件。不对呀,神奇啊,AsyncTask#onProgressUpdate()和AsyncTask#onPostExecute()的文档明明写着这二个回调是在UI线程里面的嘛,怎么还会报出这样的异常呢!


原因分析
AsyncTask设计出来执行异步任务却又能与主线程通讯,它的内部有一个InternalHandler是继承自Handler的静态成员sHandler,这个sHandler就是用来与主线程通讯的。看下这个对象的声明:private static final InternalHandler sHandler = new InternalHandler();而InternalHandler又是继承自Handler的。所以本质上讲sHandler就是一个Handler对象。Handler是用来与线程通讯用的,它必须与Looper和线程绑定一起使用,创建Handler时必须指定Looper,如果不指定Looper对象则使用调用栈所在的线程,如果调用栈线程没有Looper会报出异常。看来这个sHandler是与调用new InternalHandler()的线程所绑定,它又是静态私有的,也就是与第一次创建AsyncTask对象的线程绑定。所以,如果是在主线程中创建的AsyncTask对象,那么其sHandler就与主线程绑定,这是正常的情况。在此例子中AsyncTask是在衍生线程里创建的,所以其sHandler就与衍生线程绑定,因此,它自然不能操作UI元素,会在onProgressUpdate()和onPostExecute()中抛出异常。


以上例子有异常的原因就是在衍生线程中创建了SimpleAsyncTask对象。至于为什么在4.0版本上没有问题,是因为4.0中在ActivityThread.main()方法中,会进行BindApplication的动作,这时会用AsyncTask对象,也会创建sHandler对象,这是主线程所以sHandler是与主线程绑定的。后面再创建AsyncTask对象时,因为sHandler已经初始化完了,不会再次初始化。至于什么是BindApplication,为什么会进行BindApplication的动作不影响这个问题的讨论。

AsyncTask的缺陷及修改方法
这其实是AsyncTask的隐藏的Bug,它不应该这么依赖开发者,应该强加条件限制,以保证第一次AsyncTask对象是在主线程中创建:
1. 在InternalHandler的构造中检查当前线程是否为主线程,然后抛出异常,显然这并不是最佳实践。
[java] view plaincopyprint?
new InternalHandler() {
[java] view plaincopyprint?
if (Looper.myLooper() != Looper.getMainLooper()) {
ow new RuntimeException("AsyncTask must be initialized in main thread");
}
[plain] view plaincopyprint?
11-03 08:56:07.055: E/AndroidRuntime(890): FATAL EXCEPTION: Thread-10
11-03 08:56:07.055: E/AndroidRuntime(890): java.lang.ExceptionInInitializerError
11-03 08:56:07.055: E/AndroidRuntime(890): at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$1.run(AsyncTaskTrapActivity.java:55)
11-03 08:56:07.055: E/AndroidRuntime(890): at java.lang.Thread.run(Thread.java:1050)
11-03 08:56:07.055: E/AndroidRuntime(890): Caused by: java.lang.RuntimeException: AsyncTask must be initialized in main thread
11-03 08:56:07.055: E/AndroidRuntime(890): at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:455)
11-03 08:56:07.055: E/AndroidRuntime(890): at android.os.AsyncTask.<clinit>(AsyncTask.java:183)
11-03 08:56:07.055: E/AndroidRuntime(890): ... 2 more

2. 更好的做法是在InternalHandler构造时把主线程的MainLooper传给
[java] view plaincopyprint?
new IntentHandler() {
super(Looper.getMainLooper());
}
会有人这样写吗,你会问?通常情况是不会的,没有人会故意在衍生线程中创建AsyncTask。但是假如有一个叫Worker的类,用来完成异步任务从网络上下载图片,然后显示,还有一个WorkerScheduler来分配任务,WorkerScheduler也是运行在单独线程中,Worker用AsyncTask来实现,WorkScheduler会在接收到请求时创建Worker去完成请求,这时就会出现在WorkerScheduler线程中---衍生线程---创建AsyncTask对象。这种Bug极其隐蔽,很难发现。
如何限制调用者的线程
正常情况下一个Java应用一个进程,且有一个线程,入口即是main方法。安卓应用程序本质上也是Java应用程序,它的主入口在ActivityThread.main(),在main()方法中会调用Looper.prepareMainLooper(),这就初始化了主线程的Looper,且Looper中保存有主线程的Looper对象mMainLooper,它也提供了方法来获取主线程的Looper,getMainLooper()。所以如果需要创建一个与主线程绑定的Handler,就可以用new Handler(Looper.getMainLooper())来保证它确实与主线程绑定。

如果想要保证某些方法仅能在主线程中调用就可以检查调用者的Looper对象:
[java] view plaincopyprint?
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new RuntimeException("This method can only be called in main thread");
}
Handler,Looper,MessageQueue机制
线程与线程间的交互协作
线程与线程之间虽然共享内存空间,也即可以访问进程的堆空间,但是线程有自己的栈,运行在一个线程中的方法调用全部都是在线程自己的调用栈中。通俗来讲西线程就是一个run()方法及其内部所调用的方法。这里面的所有方法调用都是独立于其他线程的,由于方法调用的关系,一个方法调用另外的方法,那么另外的方法也发生在调用者的线程里。所以,线程是时序上的概念,本质上是一列方法调用。
那么线程之间要想协作,或者想改变某个方法所在的线程(为了不阻塞自己线程),就只能是向另外一个线程发送一个消息,然后return;另外线程收到消息后就去执行某些操作。如果是简单的操作可以用一个变量来标识,比如A线程主需要B线程做某些事时,可以把某个对象obj设置值,B则当看到obj != null时就去做事,这种线程交互协作在《Java编程思想》中有大量示例。
Android中的ITC-Inter Thread Communication
注意:当然Handler也可以用做一个线程内部的消息循环,不必非与另外的线程通信,但这里重点讨论的是线程与线程之间的事情。

Android当中做了一个特别的限制就是非主线程不能操作UI元素,而一个应用程序是不可能不创衍生线程的,这样一来主线程与衍生线程之间就必须进行通信。由于这种通信很频繁,所以不可能全用变量来标识,程序将变得十分混乱。这个时候消息队列就变得有十分有必要,也就是在每个线程中建立一个消息队列。当A需要B时,A向B发一个消息,此过程实质为把消息加入到B的消息队列中,A就此return,B并不专门等待某个消息,而是循环的查看其消息队列,看到有消息后就去执行。

整套ITC的基本思想是:定义一个消息对象,把需要的数据放入其中,把消息的处理的方法也定义好作为回调放到消息中,然后把这个消息发送另一个线程上;另外的线程在循环处理其队列里的消息,看到消息时就对消息调用附在其上的回调来处理消息。这样一来可以看出,这仅仅是改变了处理消息的执行时序:正常是当场处理,这种则是封装成一个消息丢给另外的线程,在某个不确定的时间被执行;另外的线程也仅提供CPU时序,对于消息是什么和消息如何处理它完全不干预。简言之就是把一个方法放到另外一个线程里去调用,进而这个方法的调用者的调用栈(call stack)结束,这个方法的调用栈转移到了另外的线程中。

那么这个机制改变的到底是什么呢?从上面看它仅是让一个方法(消息的处理)安排到了另外一个线程里去做(异步处理),不是立刻马上同步的做,它改变的是CPU的执行时序(execution sequence)。

那么消息队列存放在哪里呢?不能放在堆空间里(直接new MessageQueue()),这样的话对象的引用容易丢失,针对线程来讲也不易维护。Java支持线程的本地存储ThreadLocal,通过ThreadLocal对象可以把对象放到线程的空间上,每个线程都有了属于自己的对象。因此,可以为每个需要通信的线程创建一个消息队列并放到其本地存储中。

基于这个模型还可以扩展,比如给消息定义优先级等。

MessageQueue
以队列的方式来存储消息,主要是二个操作一个是入列enqueueMessage,一个是出列next(),需要保证的是线程安全,因为入列通常是另外的线程在调用。

MessageQueue是一个十分接近底层的机制,所以不方便开发者直接使用,要想使用此MessageQueue必须做二个方面工作,一个是目标线程端:创建,与线程关联,运转起来;另一个就是队列线程的客户端:创建消息,定义回调处理,发送消息到队列。Looper和Handler就是对MessageQueue的封装:Looper是给目标线程用的:用途是创建MessageQueue,将MessageQueue与线程关联起来,并让MessageQueue运转起来,且Looper有保护机制,让一个线程仅能创建一个MessageQueue对象;而Handler则是给队列客户端用的:用来创建消息,定义回调和发送消息。

因为Looper对象封装了目标队列线程及其队列,所以对队列线程的客户端来讲,Looper对象就代表着一个拥有MessageQueue的线程,和这个线程的MessageQueue。也即当你构建Handler对象时用的是Looper对象,而当你检验某个线程是否是预期线程时也用Looper对象。

Looper内幕
Looper的任务是创建消息队列MessageQueue,放到线程的ThreadLocal中(与线程关联),并且让MessageQueue运转起来,处于Ready的状态,并要提供供接口以停止消息循环。它主要有四个接口:
public static void Looper.prepare()
这个方法是为线程创建一个Looper对象和MessageQueue对象,并把Looper对象通过ThreadLocal放到线程空间里去。需要注意的是这个方法每个线程只能调用一次,通常的做法是在线程run()方法的第一句,但只要保证在loop()前面即可。

public static void Looper.loop()
这个方法要在prepare()这后调用,是让线程的MessageQueue运转起来,一旦调用此方法,线程便会无限循环下去(while (true){...}),无Message时休眠,有Message入队时唤醒处理,直到quit()调用为止。它的简化实现就是:
[java] view plaincopyprint?
loop() {
while (true) {
Message msg = mQueue.next();
if msg is a quit message, then
return;
msg.processMessage(msg)
}
}

public void Looper.quit()
让线程结束MessageQueue的循环,终止循环,run()方法会结束,线程也会停止,因此它是对象的方法,意即终止某个Looper对象。一定要记得在不需要线程的时候调用此方法,否则线程是不会终止退出的,进程也就会一直运行,占用着资源。如果有大量的线程未退出,进程最终会崩掉。

public static Looper Looper.myLooper()
这个是获得调用者所在线程所拥有的Looper对象的方法。

还有二个接口是与主线程有关的:
一个是专门为主线程准备的
public static void Looper.prepareMainLooper();
这个方法只给主线程初始化Looper用的,它仅在ActivityThread.main()方法中调用,其他地方或其他线程不可以调用,如果在主线程中调用会有异常抛出,因为一个线程只能创建一个Looper对象。但是如在其他线程中调用此方法,会改变mainLooper,接下来的getMainLooper就会返回它而非真正的主线程的Looper对象,这不会有异常抛出,也不会有明显的错误,但是程序将不能正常工作,因为原本设计在主线程中运行的方法将转到这个线程里面,会产生很诡异的Bug。这里Looper.prepareMainThread()的方法中应该加上判断:

[java] view plaincopyprint?
public void prepareMainLooper() {
if (getMainLooper() != null) {
throw new RuntimeException("Looper.prepareMainthread() can ONLY be called by Frameworks");
}
//...
}

以防止其他线程非法调用,光靠文档约束力远不够。

另外一个就是获取主线程Looper的接口:
public static Looper Looper.getMainLooper()

这个主要用在检查线程合法性,也即保证某些方法只能在主线程里面调用。但这并不保险,如上面所说,如果一个衍生线程调用了prepareMainLooper()就会把真正的mMainLooper改变,此衍生线程就可以通过上述检测,导致getMainLooper() != myLooper()的检测变得不靠谱了。所以ViewRoot的方法是用Thread来检测:mThread != Thread.currentThread();其mThread是在系统创建ViewRoot时通过Thread.currentThread()获得的,这样的方法来检测是否是主线程更加靠谱一些,因为它没有依赖外部而是相信自己保存的Thread的引用。

Message对象
消息Message是仅是一个数据结构,是信息的载体,它与队列机制是无关的,封装着要执行的动作和执行动作的必要信息,what, arg1, arg2, obj可以用来传送数据;而Message的回调则必须通过Handler来定义,为什么呢?因为Message仅是一个载体,它不能自己跑到目标MessageQueue上面去,它必须由Handler来操作,把Message放到目标队列上去,既然它需要Handler来统一的放到MessageQueue上,也可以让Handler来统一定义处理消息的回调。需要注意的是同一个Message对象只能使用一次,因为在处理完消息后会把消息回收掉,所以Message对象仅能使用一次,尝试再次使用时MessageQueue会抛出异常。

Handler对象
它被设计出来目的就是方便队列线程客户端的操作,隐藏直接操作MessageQueue的复杂性。Handler最主要的作用是把消息发送到与此Handler绑定的线程的MessageQueue上,因此在构建Handler的时候必须指定一个Looper对象,如果不指定则通过Looper获取调用者线程的Looper对象。它有很多重载的send*Message和post方法,可以以多种方式来向目标队列发送消息,廷时发送,或者放到队列的头部等等;

它还有二个作用,一个是创建Message对象通过obtain*系统方法,另一个就是定义处理Message的回调mCallback和handleMessage,由于一个Handler可能不止发送一个消息,而这些消息通常共享此Handler的回调方法,所以在handleMessage或者mCallback中就要区分这些不同的消息,通常是以Message.what来区分,当然也可以用其他字段,只要能区别出不同的Message即可。需要指明的是,消息队列中的消息本身是独立的,互不相干的,消息的命名空间是在Handler对象之中的,因为Message是由Handler发送和处理的,所以只有同一个Handler对象需要区别不同的Message对象。广义上讲,如果一个消息自己定义有处理方法,那么所有的消息都是互不相干的,当从队列取出消息时就调用其上的回调方法,不会有命名上的冲突,但由Handler发出的消息的回调处理方法都是Handler.handleMessage或Handler.mCallback,所以就会有影响了,但影响的范围也令局限在同一个Handler对象。

因为Handler的作用是向目标队列发送消息和定义处理消息的回调(处理消息),它仅是依赖于线程的MessageQueue,所以Handler可以有任意多个,都绑定到某个MessageQueue上,它并没有个数限制。而MessageQueue是有个数限制的,每个线程只能有一个,MessageQueue通过Looper创建,Looper存储在线程的ThreadLocal中,Looper里作了限制,每个线程只能创建一个。但是Handler无此限制,Handler的创建通过其构造函数,只需要提供一个Looper对象即可,所以它没有个数限制。
分享到:
评论

相关推荐

    Handler与AsyncTask,Looper使用示例

    Handler,AsyncTask,Looper自定义线程使用示例,自定义线程与UI线程交互,访问UI线程控件

    Handler与AsyncTask使用示例

    Handler与AsyncTask使用示例,Handler AsyncTask 示例 looper

    AsyncTask陷阱之:Handler,Looper与MessageQueue的详解

    本篇文章是对Handler,Looper与MessageQueue进行了详细的分析介绍,需要的朋友参考下

    Looper-Handler-AsyncTask

    Looper-Handler-AsyncTask

    android AsyncTask详细介绍

    AsyncTask和Handler对比 1 ) AsyncTask实现的原理,和适用的优缺点 AsyncTask,是android提供的轻量级的异步类,可以直接继承...在Handler 异步实现时,涉及到 Handler, Looper, Message,Thread四个对象,实现异步的流

    swt-async-handler-1.0

    由于,不是很了解android下的Handler机制,没有深层次的编写诸如looper,MessageQueue。 将在下一个版本中加入MessageQueue机制,实现多任务后台处理相应。 具体使用请参考test包下的Handler使用事例。

    Asynchronous Android

    介绍android 异步任务不可多得的好书,主要介绍了AsyncTask, handler,looper,loader,IntentService,AlarmManager等

    最全java面试题.zip

    Handler、Looper、Message、MessageQueue基础流程分析 Android性能优化 ListView详解 RecyclerView和ListView的异同 AsyncTask源码分析 插件化技术 自定义控件 事件分发机制 ANR问题 Art和Dalvik的区别 Android关于...

    Java最全面试题宝典.rar

    Handler、Looper、Message、MessageQueue基础流程分析 Android性能优化 ListView详解 RecyclerView和ListView的异同 AsyncTask源码分析 插件化技术 自定义控件 事件分发机制 ANR问题 Art和Dalvik的区别 Android关于...

    Android_Studio_Handler:Android Handler 执行绪教学

    (1)Activity.runOnUiThread(Runnable) (2)View.post(Runnable) (3)View.postDelayed(Runnable,long) (4)Handler (5)AsyncTask在此说明Handler 使用规则####★Outline执行绪间通讯(如:worker执行绪将UI更新程式片段...

    Android代码-Android 一些重要知识点解析整理

    Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系 Android消息循环分析 Android Activity developer 官网 (强烈推荐 dev guide) Android的启动模式(android:launchMode) ...

    Android并发开发

    第5-7章是本书的核心内容,深入探讨Android操作系统的细节,如Looper/Handler、Service、Binder、定时任务等。第8章介绍并发工具,如静态分析、注解、断言等。本书适合有Android开发经验的读者参考。如果你是一名...

    Mindroid.cpp:Mindroid.cpp是一个受Google Android操作系统启发的应用程序框架

    Mindroid.cpp C ++应用程序框架Mindroid是一个应用程序框架... 消息传递和并发框架主要基于Thread,Looper,Message,MessageQueue和Handler类。 为了方便起见,还有AsyncTask类,对于进程间通信,还有其他一些类,例

    老罗android视频开发源码和ppt经典

    以下为视频源码目录: 一、Android入门介绍 视频教程 1.1 android系统介绍 1.2 android系统框架的介绍 1.3 如何搭建android开发环境 1.4 android生命周期的...15.6 Handler和Looper 15.7 Handler综合练习(图文混排)

    积分管理系统java源码-alibaba-Interview-preparation:阿里巴巴实习生面试个人准备

    AsyncTask,内部使用FutureTask实现,通过Handler将结果转发到主线程,默认的Executor是共用的,如果同时执行多个AsyncTask,就可能需要排队,但是可以手动指定Executor解决这个问题,直接new匿名内部类会保存外部类的引用,...

    BAT 大厂Android研发岗必刷真题:Android异常与性能优化相关面试问题

    今天来讲一讲在面试中碰到的Android异常与性能优化相关问题: ... 没有使用子线程的Looper的Handler的handlerMessage,post(Runnable)是执行在主线程的 AsyncTask的回调中除了doInBackground,其它都

    百度地图开发java源码-blog-backup:学习文章,也是我博客的备份

    Handler 和 Message 以及 Looper 的基本用法和工作原理。 本篇介绍 AsyncTask 的使用方法和工作原理 本篇介绍 Android 中的线程池 ThreadPoolExecutor 相关概念。 本篇介绍 HandlerThread 和 IntentService 相

    Android 面试精华题目总结

    下面的题目都是楼主在android交流群大家面试时遇到的,如果大家有好的题目或者好的见解欢迎分享,楼主将长期维护此帖。...1、请解释下在单线程模型中Message,Handler,Message Queue,Looper之间的关系。 拿主线程

    Android开发艺术探索.任玉刚(带详细书签).pdf

    10.2.4 Handler的工作原理 385 10.3 主线程的消息循环 389 第11章 Android的线程和线程池 391 11.1 主线程和子线程 392 11.2 Android中的线程形态 392 11.2.1 AsyncTask 392 11.2.2 AsyncTask的工作原理 395 ...

Global site tag (gtag.js) - Google Analytics