Android异步消息处理之Thread+Handler
前言:之前看了网上很多关于Android异步消息处理机制的文章,对于这块知识从一般性的应用上升到了内部机制的理解,受益匪浅。本着“看过没总结过等于没收获过”的原则,也对Android异步消息处理这块做个总结,内容如有错误之处还望指正。
通过该blog可以了解到开发中为什么需要异步消息处理;怎样创建线程以及创建Handler;通过Handler和Looper的源码了解Handler与消息循环的内部沟通过程,Handler发送消息的两种方式以及如果获取消息并处理消息;最后是介绍线程造成内存泄露的情况。Android异步消息处理概述
Android Developers中Keeping Your App Response一文中: No response to an input event (such as key press or screen touch events) within 5 seconds. ABroadcastReceiver
hasn't finished executing within 10 seconds.如果输入事件(点击或触屏)在5秒之内没有响应,BroadcastReceiver在10秒之内没有执行完毕,应用程序就会报ANR错误,用户只能选择等待或强制关闭,这种用户体验是非常差的。 主线程(main thread/UI thread),它有一个消息队列(message queue),如果屏幕上发生了点击/触屏事件就会把它转化为一个消息(message)放到消息队列里,由looper不断地从message queue里取出消息派送给相应的handler进行处理。而如果某个消息执行时间非常耗时,就会阻碍其他消息的处理,如果该条消息在5秒之内没有得到响应的话,就会报ANR。
避免ANR的方法是创建子线程,由子线程来完成耗时任务。例如点击某条新闻查看新闻详情这一事件,它的实现过程是用户点击了某条新闻,这个点击事件会通过网络请求去获取服务器端的手机接口数据(耗时操作),获取到的新闻详情的数据内容会显示到新闻详情页面中(更新用户界面)。由子线程完成耗时任务,再由子线程处理返回结果来更新界面,这种做法是错误的,因为应用只允许在主线程里对UI做修改,子线程没有权利对用户界面做任何修改。因此子线程处理完耗时操作,其获取到修改UI的内容需要在主线程里做处理。
之前已经提到创建子线程处理耗时任务,那么怎样将子线程获取到的数据放到主线程中处理呢?这里就需要Handler+Message来做主线程和子线程之间的沟通。整个的异步消息处理的步骤如下:
1. 在main thread里创建worker thread来异步执行耗时任务
2.在main thread里创建Handler,并让worker thread持有handler的引用
3.将worker thread执行结束后,创建Message,将获取到的数据结果存放到message中
4.由worker thread持有的的handler引用将message发送出去
5.main thread中的handler接受到message并处理消息,UI更新
创建子线程
1. 创建线程的两种方式
一、继承(extends)Thread类(适用于单继承) 二、实现(implements)Runnable接口(当一个类已经继承某个类,就只能用Runnable来创建线程类)通过run()方法来执行代码,通过调用start()方法启动线程。
实现Runnable接口创建线程:
/** * 点击按钮,创建子线程 */ private void excuteLongTimeOperation() { Thread workerThread = new Thread(new MyNewThread()); workerThread.start(); } class MyNewThread implements Runnable{ @Override public void run() { //执行耗时操作 ThreadUtil.logThreadSignature(); } }
通过继承Thread类来创建线程:
/** * 点击按钮,创建子线程 */ private void excuteLongTimeOperation() { Thread workerThread = new MyNewThread(); workerThread.start(); } public class MyNewThread extends Thread{ @Override public void run() { super.run(); } }
可以简化为匿名内部类,更加简洁: /** * 点击按钮,创建子线程 */ private void excuteLongTimeOperation() { new Thread(){ @Override public void run() { super.run(); //执行耗时操作 } }.start(); }
在以下情况下可以使用匿名内部类: 1.只用到类的一个实例 2.类在定以后马上用到 3.类非常小 4.给类名并不会导致代码更容易被理解2. 利用ThreadUtil查看运行的哪个线程
通过当前的线程id来查看执行代码运行在哪个线程里,下面的代码段中有一个按钮“点击创建子线程”,点击该按钮会创建一个workerThread,并打印出子线程的部分信息。package com.aliao.myandroiddemo.view.handler;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;import com.aliao.myandroiddemo.R;import com.aliao.myandroiddemo.utils.ThreadUtil;/** * Created by liaolishuang on 14-4-9. */public class TestHandlerActivity extends Activity implements View.OnClickListener{ private TextView didSthTxt; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); //打印当前线程的部分信息 ThreadUtil.logThreadSignature(); Button anrBtn = (Button) findViewById(R.id.btn_createthread); anrBtn.setOnClickListener(this); didSthTxt = (TextView) findViewById(R.id.tv_showsth); } @Override public void onClick(View view) { switch (view.getId()){ case R.id.btn_createthread: excuteLongTimeOperation(); break; } } /** * 点击按钮,创建子线程 */ private void excuteLongTimeOperation() { Thread workerThread = new Thread(new MyNewThread()); workerThread.start(); } class MyNewThread implements Runnable{ @Override public void run() { //打印子线程的部分信息 ThreadUtil.logThreadSignature(); //执行耗时操作 } }}
我们在onCreate()中调用ThreadUtil.logThreadSignature();执行上面的代码后会打印出当前线程的信息:04-11 18:40:55.529 21410-21410/com.aliao.myandroiddemo D/ThreadUtils﹕ main:(id)1:(priority)5:(group)main
当前的线程名是main,即主线程,其线程id是1。 同样在子线程的run()方法中调用ThreadUtil.logThreadSignature();点击“点击创建子线程”按钮后,可以看到: 04-11 18:41:02.019 21410-22024/com.aliao.myandroiddemo D/ThreadUtils﹕ Thread-74777:(id)74777:(priority)5:(group)main
子线程名是Thread-74777,id是74777 3. 使用线程的缺点
1.子线程如果要更新用户界面,需要在主线程中更新用户界面2.线程生命周期不可控 3.没有默认线程池 4.在Android中默认情况下不会处理配置改变(configuration changes)
从源码角度查看handler的内部机制
Handler扮演了往MessageQueue中添加消息和处理消息的角色。通过post(runnable)和sendMessage(msg)等函数向MesssageQueue中添加消息,再通过handleCallback(在Handler类中直接调用runnable的run方法实现)和handleMessage(需要由programer在代码中覆写,自己处理)这两个方法处理消息。1. Handler源码 我们常常会这样定义一个Handler对象:
Handler handler = new Handler();
Handler类中的构造函数public Handler()会做一些准备工作,先看下源码: package android.os;import android.util.Log;import android.util.Printer;import java.lang.reflect.Modifier;public class Handler { private static final boolean FIND_POTENTIAL_LEAKS = false; private static final String TAG = "Handler"; public Handler() { this(null, false); } public Handler(Callback callback) { this(callback, false); } public Handler(Looper looper) { this(looper, null, false); } public Handler(Looper looper, Callback callback) { this(looper, callback, false); } public Handler(boolean async) { this(null, async); } public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }//省略其他代码....}
构造函数handler(Callback callback, boolean async){}中该句代码: mLooper = Looper.myLooper();
继续查看Looper.myLooper() /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); }
myLooper返回的是与当前线程关联的looper。Handler与当前线程的Looper相关联。 Looper到底是什么,在异步消息处理机制中扮演什么角色? 2. Looper源码分析
Looper顾名思义是<循环者>的意思,他封装了消息循环。在Looper这个类中有一个for(;;)死循环,会在MessageQueue中不断的取出消息,然后派送给对应的handler进行处理。怎样派送到对应的handler呢?带着这个问题先看源码:package android.os;import android.util.Log;import android.util.Printer;import android.util.PrefixPrinter;public final class Looper { private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal sThreadLocal = new ThreadLocal(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue;//在looper内部维护了一个消息队列 final Thread mThread;//当前线程 private Printer mLogging; /** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //每个线程只能创建一个Looper,如果视图再次创建,报异常 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); }//创建looper,并把该looper和当前线程关联在一起。取出looper用对应sThreadLocal.get();方法 sThreadLocal.set(new Looper(quitAllowed)); }private Looper(boolean quitAllowed) { //创建了一个Looper,也就创建了一个消息队列 mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } /** * 主线程所关联的looper(application's main looper)由应用程序自动创建 */ public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } /** 返回运行在主线程中的looper—— the application's main looper */ public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } }/** * 返回当前线程关联的looper对象,如果调用的thread没有关联一个looper,返回null */ public static Looper myLooper() { return sThreadLocal.get(); } /** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { final Looper me = myLooper();//得到当前looper if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue;//得到当前lopper关联的MessageQueue // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) {//循环体 //从消息队列中取出消息 Message msg = queue.next(); // might block//如果消息为空,就退出循环 if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); }//非常关键的一句代码:将处理消息的工作交给msg.target即handler msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycle(); } } /** * Quits the looper. */ public void quit() { mQueue.quit(false); } /** * Quits the looper safely. */ public void quitSafely() { mQueue.quit(true); }//省略其他代码}
在一个线程中,looper用来跑一个消息循环。默认情况线程并没有关联一个消息循环。为一个消息创建消息循环必须在线程中调用prepare()方法去创建一个looper来运行消息循环,然后调用loop()方法来启动循环,直到loop被停止。并且消息循环的大部分交互都是通过Handler类来进行。源码中给了一个例子,自己查看吧。应用程序会为主线程自动创建关联的looper。 源代码中需要了解的地方: 1. 通过prepare()方法创建消息循环,通过loop()方法启动循环 2. 创建looper时也相应创建了消息队列 3. 进入for循环体,从消息队列中读取消息,如果消息为null,结束这次循环 4. msg.target.dispatchMessage(msg);msg.target就是handler,通过对应的handler把消息分发出去,由回调函数来处理消息。 5. msg.recycle();"当处理完一次消息后,对消息进行回收处理。在Message有一个消息池用来避免不停的创建删除消息对象,内部只是把消息设置为空闲状态,以便重复利用" 3. Handler发送消息和处理消息
第4点中有两处需要引起注意:一是,当消息被取出,怎么分发给"相应"的Handler?二是,dispatchMessage()是如何分发消息的? 对于第一个问题可以通过对第二点的源码分析过程中得知。 对于第二点,dispatchMessage()发方法在Handler类中: /** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { } /** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
dispatchMessage(msg)这个方式是用来分工的。handler通过以下两种方式发送消息:handler.post(runnable)以及handler.sendMessage(msg)。其中参数一个是runnable类型的,一个是Message类型的,所以如果msg.callback != null(在Message类中定义了Runnable callback;变量)即如果该消息是Runnable类型的话就交给handleCallback(msg)去处理。否则消息类型是一个纯粹的Message类型,就交给handleMessage(msg)去处理。之所以说纯粹是因为,post(runnable),最终还是会把runnable对象"包装"成一个Message对象。 通过源码进一步了解: handler调用post(runnable)后,会把runnable赋值给Message对象的callback成员变量,然后把这个message添加到MessageQueue中。再通过msg.callback是否为空来分别处理不同的消息类型。 public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
handler调用sendMessage(msg): public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
两个方法均调用了sendMessageDelayed(msg, 0);方法: public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
回头看在Handler的构造函数中有这样一句代码:mQueue = mLooper.mQueue;在sendMessageAtTime()中的引用的queue正式Looper中的message queue。作为参数传给了enqueueMessage() private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
之前留下一个问题:looper类中当消息被取出,怎么分发给"相应"的Handler?Message类中定义了target变量,其类型正式Handler。在该方法的源码中可以看出msg.target = this;是把当前handler与该handler发送的消息一 一对应起来,那么在取出消息的时候,也就可以通过msg.target获取到相对应的handler了。 queue.enqueueMessage(msg, uptimeMillis);将消息添加到消息队列里。 到目前为止我们已经通过源码了解了handler添加消息的过程有了一个大致的了解,下面是具体看handler是如何处理消息的 1. 通过handleCallback处理Runnable类型的消息: private static void handleCallback(Message message) { message.callback.run(); }
回调在代码中创建的Runnable类中的run方法。该实现Runnable的类作为Message的参数。2. 通过handleMessage(msg)处理消息:
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }
子类必须覆写handleMessage(msg)方法。 run()方法和handleMessage()方法中的具体逻辑都由开发者自己去实现。 4.handler与looper源码分析小结
1. 系统默认情况下,只有主线程绑定looper对象,所以在主线程中可以直接创建Handler,该handler关联的是主线程。 2. 每个handler都对应一个looper,一个looper对应一个线程和该线程的消息队列。 3. 一个线程只能有一个looper,但是可以有多个handler。一个looper可以有多个handler。 4. 如果要在一个线程里创建handler,需要创建looper实例(通过looper.prepare()和looper.loop()来实现),因为线程默认情况是没有消息循环的。 5.handler的最主要的两个工作是发送消息和处理消息。 6.当Handler被创建时,在Handler的构造函数中便持有了当前线程的Looper引用,通过该looper获取到消息队列的引用,当handler发送消息后,会通过该消息队列的引用调用MessageQueue类中的方法将消息插入到消息队列中。而Looper类中会循环取出该消息队列中的消息再派送给handler去处理。例子-利用Thread+Handler方式实现在子线程中发送消息通知主线程更新界面
Android采用UI单线程模型,只能够在主线程中对UI元素进行操作。如果在子线程中直接对UI进行了操作,回报: CalledFromWrongThreadException:only the original thread that created a view hierarchy can touch its views我们可以利用消息循环的机制来实现线程之间的通信。
package com.aliao.myandroiddemo.view.handler;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.widget.Button;import android.widget.TextView;import com.aliao.myandroiddemo.R;import com.aliao.myandroiddemo.utils.ThreadUtil;/** * Created by liaolishuang on 14-4-9. */public class TestHandlerActivity extends Activity implements View.OnClickListener{ private TextView didSthTxt; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); //打印当前线程的部分信息 ThreadUtil.logThreadSignature(); Button anrBtn = (Button) findViewById(R.id.btn_createthread); anrBtn.setOnClickListener(this); didSthTxt = (TextView) findViewById(R.id.tv_showsth); } @Override public void onClick(View view) { switch (view.getId()){ case R.id.btn_createthread: excuteLongTimeOperation(); break; } } /** * 点击按钮,创建子线程 */ private void excuteLongTimeOperation() { Thread workerThread = new Thread(new MyNewThread()); workerThread.start(); } class MyNewThread extends Thread{ @Override public void run() { //打印子线程的部分信息 ThreadUtil.logThreadSignature(); //模拟执行耗时操作 ThreadUtil.sleepForInSecs(5); Message message = handler.obtainMessage(); Bundle bundle = new Bundle(); bundle.putString("message","界面内容已更新"); message.setData(bundle); handler.sendMessage(message); } } /** * 以匿名类的形式创建handler */ private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { //修改界面中TextView中的内容 didSthTxt.setText(msg.getData().getString("message")); } };}
线程与内存泄露
摘自Android,谁动了我的内存 中第四点:线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
public class MyActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); new MyThread().start(); } private class MyThread extends Thread{ @Override public void run() { super.run(); //do somthing } } }
这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。
由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
这种线程导致的内存泄露问题应该如何解决呢?
第一、将线程的内部类,改为静态内部类。
第二、在线程内部采用弱引用保存Context引用。
解决的模型如下:
public abstract class WeakAsyncTask extends AsyncTask { protected WeakReference mTarget; public WeakAsyncTask(WeakTarget target) { mTarget = new WeakReference(target); } /** {@inheritDoc} */ @Override protected final void onPreExecute() { final WeakTarget target = mTarget.get(); if (target != null) { this.onPreExecute(target); } } /** {@inheritDoc} */ @Override protected final Result doInBackground(Params... params) { final WeakTarget target = mTarget.get(); if (target != null) { return this.doInBackground(target, params); } else { return null; } } /** {@inheritDoc} */ @Override protected final void onPostExecute(Result result) { final WeakTarget target = mTarget.get(); if (target != null) { this.onPostExecute(target, result); } } protected void onPreExecute(WeakTarget target) { // No default action } protected abstract Result doInBackground(WeakTarget target, Params... params); protected void onPostExecute(WeakTarget target, Result result) { // No default action } }
好文分享
Android Background Processing with Handlers and AsyncTask and Loaders - Tutorial Android异步消息处理更多相关文章
- Android线程与异步消息处理机制
- android的消息处理机制(Looper,Handler,Message)
- android ui线程和数据的分离
- Android的消息机制分析
- android中实现消息推送
- Android消息传递之EventBus 3.0使用详解