Android Handler消息机制从原理到应用详解
来源:互联网 发布:天津航空机电知乎 编辑:程序博客网 时间:2024/06/06 22:16
对于跨进程IPC通信,Android提供了一个完整的框架Binder,而对于线程之间的消息传递,Android同样提供了一个强大的消息机制:Handler/Looper/MessageQueue,通过Handler我们很容易的实现在UI线程与其他线程之间的消息传递。这篇文章,就来看一看Android消息机制的具体应用以及原理。主要有3个方面:
- Android消息机制相关的基本概念;
- Android Handler的具体应用实例;
- 从源码的角度直接深入了解Handler/Looper/MessageQueue的机制;
首先来看下相关的概念。
基本概念
Handler
一个Handler用于发送或者处理与一个线程的消息队列(MessageQueue
)相关联的消息或者可执行对象(Runnable
)。每个Handler实例跟一个线程以及线程消息队列相关联。当新建一个Handler实例时,该Handler就跟创建它的线程以及线程队列绑定在一起。此后,它就会将消息以及可执行对象传送到消息队列中,并且处理从消息队列中出来的消息。
一般,Handler有两个用途:(1)对消息以及可执行对象进行调度,以便未来某个时间点执行之;(2)向另一个线程发送一个消息或者执行对象。
当应用程序进程创建完成后,主线程负责运行一个用于管理应用内的对象,如activities
,broadcast receivers
的消息队列。用户可以创建自己的线程,通过一个Handler
跟主线程进行通信。
Looper
Looper用于为线程运行一个消息循环;线程默认是没有消息循环的,要建立一个新的Looper,首先需要在线程内调用prepare
,然后调用loop
进入消息处理。
MessageQueue
MessageQueue用于保存被looper分发出来的消息。消息并不是直接添加到MessageQueue中的,而是通过跟looper相关联的Handler进行传递。
Android应用实例
在Android应用开发过程中,为了保持UI的响应,通常需要将一些耗时的操作放到非UI线程,然后将结果返回到UI线程。这里,假如我们需要从网络上下载一副图片,然后在ImageView
中显示。对于网络访问这种耗时的操作,启动一个新的线程来运行。因此,首先我们自定义一个线程类用于下载图片:
/** * thread to download image from a given URL */ public class DownloadThread extends Thread{ private static final String LOG_TAG = "DownloadThread"; private static final int MSG_DOWNLOAD_IMAGE = 0x01; private static final int DEFAULT_CONNECT_TIMEOUT = 10*1000; private static EventHandler mHandler = new EventHandler(); private Handler mMainHandler; // main thread handler public DownloadThread(Handler main){ super(LOG_TAG); mMainHandler = main; } @Override public void run(){ //启动线程消息循环 Looper.prepare(); // 新建一个线程Handler //mHandler = new EventHandler(); //进入消息循环 Looper.loop(); } public void addNewTask(String url){ if(URLUtil.isNetworkUrl(url)){ Message msg = mHandler.obtainMessage(MSG_DOWNLOAD_IMAGE,url); mHandler.sendMessage(msg); }else{ //向主线程发送错误消息 String error = "illegal format image url"; Message msg = mMainHandler.obtainMessage(MainActivity.MSG_IMAGE_DOWNLOAD_FAIL,error); msg.sendToTarget(); } } private void downloadImage(String imgUrl){ Log.v(LOG_TAG, "downloadImage(): url = " + imgUrl); // notify main thread that it starts Message msg = mMainHandler.obtainMessage(MainActivity.MSG_IMAGE_DOWNLOAD_START); msg.sendToTarget(); InputStream in = null; try { // configure Http connection URL myUrl = new URL(imgUrl); HttpURLConnection urlCnn = (HttpURLConnection)myUrl.openConnection(); urlCnn.setDoInput(true); urlCnn.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT); // get input stream from the given url in = urlCnn.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(in); Bundle bundle = new Bundle(); bundle.putParcelable("bitmap",bitmap); // send message containing data to the main thread Message msg1 = mMainHandler.obtainMessage(MainActivity.MSG_IMAGE_DOWNLOAD_SUCCESS); msg1.setData(bundle); msg1.sendToTarget(); }catch (MalformedURLException e){ e.printStackTrace(); String error = "wrong format url"; Message message = mMainHandler.obtainMessage(MainActivity.MSG_IMAGE_DOWNLOAD_FAIL,error); message.sendToTarget(); }catch (IOException e){ e.printStackTrace(); String error = "network connection error"; Message message = mMainHandler.obtainMessage(MainActivity.MSG_IMAGE_DOWNLOAD_FAIL,error); message.sendToTarget(); } } // 自定义一个事件处理Handler private final class EventHandler extends Handler{ @Override public void handleMessage(Message msg){ int w = msg.what; switch (w){ case MSG_DOWNLOAD_IMAGE: String url = (String)msg.obj; downloadImage(url); break; default: break; } } } }
注意,启动一个新的线程时,需要调用prepare
和loop
两个函数,以确保消息循环处于运行状态。接着,在MainActivity
中启动该线程用于下载图片:
public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); public static final int MSG_IMAGE_DOWNLOAD_START = 0x01; public static final int MSG_IMAGE_DOWNLOAD_FAIL = 0x02; public static final int MSG_IMAGE_DOWNLOAD_SUCCESS = 0x03; private Handler mH; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mIvFilm = (ImageView)findViewById(R.id.iv_film); mH = new H(); } @Override public void onResume(){ super.onResume(); DownloadThread downloadThread = new DownloadThread(mH); downloadThread.start(); String url = "http://image.baidu.com/search/redirect?tn=redirect&word=j&juid=9127CC&sign=ciwziioaoz&url=http%3A%2F%2Fwww.4493.com%2Fmotemeinv%2F6156%2F1.htm&objurl=http%3A%2F%2Fh.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2Fdc54564e9258d109a4d1165ad558ccbf6c814d23.jpg"; // 下载图片 downloadThread.addNewTask(url); } // 自定义一个Handler对象,用于处理UI线程的消息 private final class H extends Handler{ @Override public void handleMessage(Message msg){ int w = msg.what; switch (w){ case MSG_IMAGE_DOWNLOAD_START: Toast.makeText(MainActivity.this,"start download",Toast.LENGTH_SHORT).show(); break; case MSG_IMAGE_DOWNLOAD_SUCCESS: Bitmap bitmap = msg.getData().getParcelable("bitmap"); mIvFilm.setImageBitmap(bitmap); Toast.makeText(MainActivity.this,"download complete",Toast.LENGTH_SHORT).show(); break; case MSG_IMAGE_DOWNLOAD_FAIL: Toast.makeText(MainActivity.this,"download failure",Toast.LENGTH_SHORT).show(); break; default: break; } } } }
可见,对于一个新的线程来说,使用Handler来处理消息或者可执行对象时,需要做如下几件事情:
- 自定义一个Handler,用于处理
Looper
发送过来的消息; - 启动线程消息循环:
Looper.prepare()
,将线程与Looper进行绑定; - 进入消息循环:
Looper.loop()
,等待接收并处理消息。
Handler原理详解
如下图所示,为Handler、Looper以及MessageQueue三者之间的关系。
- Looper不断查询消息队列中的消息,如果发现有新的消息,则将其发送给对应的Handler执行;
- Handler接收到来自Looper的消息后,对其进行处理;
- MessageQueue接收发自从Handler产生的消息,将其放入队列;
明白了三者的作用,接下来就来看一看具体的实现细节。
线程与Looper的绑定
调用prepare()
函数后,新建一个Looper实例与线程进行绑定,并将其保存在线程本地变量中。
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } ... // 新建一个与该线程对应的MessageQueue private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
源码:/android/frameworks/base/core/java/android/os/Looper.java
同时,创建一个消息队列:
public final class MessageQueue { // True if the message queue can be quit. private final boolean mQuitAllowed; @SuppressWarnings("unused") private long mPtr; // used by native code private boolean mQuitting; .... private native static long nativeInit(); private native static void nativeDestroy(long ptr); private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ private native static void nativeWake(long ptr); private native static boolean nativeIsPolling(long ptr); private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; //本地初始化 mPtr = nativeInit(); } .... }
源码: /android/frameworks/base/core/java/android/os/MessageQueue.java
初始化本地消息队列(新建一个本地消息队列):
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); if (!nativeMessageQueue) { jniThrowRuntimeException(env, "Unable to allocate native queue"); return 0; } nativeMessageQueue->incStrong(env); return reinterpret_cast<jlong>(nativeMessageQueue); } // 本地消息队列 NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { //首次为空 mLooper = Looper::getForThread(); if (mLooper == NULL) { mLooper = new Looper(false); Looper::setForThread(mLooper); } }
源码: /android/frameworks/base/core/jni/android_os_MessageQueue.cpp
新建一个本地Looper,监听I/O事件:
Looper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false), mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s", strerror(errno)); AutoMutex _l(mLock); rebuildEpollLocked(); } void Looper::rebuildEpollLocked() { // Close old epoll instance if we have one. if (mEpollFd >= 0) { close(mEpollFd); } // Allocate the new epoll instance and register the wake pipe. mEpollFd = epoll_create(EPOLL_SIZE_HINT); struct epoll_event eventItem; memset(& eventItem, 0, sizeof(epoll_event)); eventItem.events = EPOLLIN; eventItem.data.fd = mWakeEventFd; int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem); for (size_t i = 0; i < mRequests.size(); i++) { const Request& request = mRequests.valueAt(i); struct epoll_event eventItem; request.initEventItem(&eventItem); int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem); if (epollResult < 0) { ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s", request.fd, strerror(errno)); } } }
源码: /android/system/core/libutils/Looper.cpp
有关异步I/O EPOLL相关资料:
- http://davmac.org/davpage/linux/async-io.html
- poll/select/epoll 性能比较
- https://linux.die.net/man/4/epoll
- select-poll-epoll-practical-difference-for-system-architects/
- http://blog.lucode.net/linux/epoll-tutorial.html
线程进入消息循环
线程与Looper绑定后,运行消息队列,准备处理消息:
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; for (;;) { // 查询消息 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } .... try { // 发送消息到目标Handler msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } .... msg.recycleUnchecked(); } }
不断查询消息队列,有消息时直接返回消息:
public final class MessageQueue { // 查询可用的消息 Message next() { // mPtr指向本地的nativeMessageQueue final long ptr = mPtr; int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { // 查询本地消息 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 获得消息,返回 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } .... //消息队列为空,或者第一个消息尚未就绪 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } //第一次才会执行:空闲等待 for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; } } }
目标Handler接收到消息,直接调用dispatchMessage()
:
public class Handler { public void dispatchMessage(Message msg) { // 有回调,直接调用回调函数 if (msg.callback != null) { handleCallback(msg); } else { // handler本身有回调函数,调用回调函数处理消息 if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } // 调用自定义的函数处理消息 handleMessage(msg); } } // 执行回调 private static void handleCallback(Message message) { message.callback.run(); } }
(全文完)
参考文献
- http://gityuan.com/2015/12/26/handler-message-framework/
- Android Handler消息机制从原理到应用详解
- Android消息机制Handler的原理详解
- Android消息机制(Handler原理)
- Android消息机制原理详解(Looper、Handler、MessageQueue)
- Android Handler 消息机制详解
- [Android] 从源码分析 Handler 消息机制
- Android 从源码看Handler消息机制
- Android 从源码分析Handler消息机制
- android 消息机制 Handler Looper 原理分析
- android Handler Looper,MessageQueue消息机制原理
- Android中Handler消息处理机制原理
- Android Handler消息机制原理及总结
- Android消息机制---Handler工作原理
- android消息机制 - Handler、Looper原理解析
- Android消息机制-Handler原理(三)
- Handler消息机制原理
- Handler消息机制原理
- Handler消息机制原理
- Java软件开发工程师笔试题(答案)
- 自定义View有时在使用时设置requestDisallowInterceptTouchEvent(true)无效
- 数据中心在多云世界中还有未来吗?
- Android 自定义属性
- 经典的软件测试用例(深入浅出)
- Android Handler消息机制从原理到应用详解
- 0001-产品分析(网易云音乐)
- String、StringBuffer与StringBuilder之间区别
- android关于去掉log打印
- Java常考面试题2--访问修饰符public,private,protected,以及不写(默认)时的区别?
- hadoop生产集群离线datanode(遇到的问题及解决方法)
- [笔记]JavaScript与Java的关系
- Java 中使用 try-catch-finally处理异常
- 双向队列