Android_Message_Handler_消息处理机制总结笔记

一次性线程和无限循环线程

普通线程是一次性的,执行结束后也就退出了(这种说法可能不严谨,但为了下文描述方便)。
但某些状况下须要无限循环、不退出的线程。好比处理用户交互的线程,它等待并执行用户的点击、滑动等等操做事件,也执行由系统触发的广播等事件,称之为主线程,也叫UI线程。html

关于这种无限循环线程须要说的是:java

  • 每一个事件及其所包含的信息被封装为一个Message(下文中提到的“消息”和“事件”是一回事儿);android

  • 线程不能同时处理全部事件,因此须要有个收件箱MessageQueue(下文中提到的线程,若是没有特别说明是一次性线程,那么都指looper thread);spring

  • 之因此不一样于一次性线程,而能无限循环,是Looper的功劳,因此这种线程也被称为message looper thread;编程

  • 事件由Handler发送和处理。app

建立一个looper thread

Android提供的类HandlerThread,是一个looper thread类。经过源码理解它是怎么办到的:
HandlerThread源码连接异步

23 public class HandlerThread extends Thread {
51     @Override
52     public void run() {
            // 初始化当前线程为looper thread,prepare()方法保证为每一个线程只新建一个Looper object;
            // 在新建Looper object时,新建了一个MessageQueue object;
            // 因此,HandlerThread对象、Looper对象、MessageQueue对象,三者之间创建了彼此唯一的关联。
54         Looper.prepare();
55         synchronized (this) {
                // 该静态方法返回与当前线程关联的Looper object,若是当前线程未关联Looper则返回空;
                // 稍后讲到的Handler也是经过该方法创建和当前线程的唯一关联;
                // 至于为何是当前线程,好像是和ThreadLocal<Looper>这个静态变量有关,不甚明了,存疑 TODO.
56             mLooper = Looper.myLooper();
57             notifyAll();
58         }
            // 你能够覆写这个函数,以在循环开始前作些必要的工做,好比实例化一个Handler,稍后讲它。
60         onLooperPrepared();
            // 调用loop()方法后,Looper对象就开始循环地从MessageQueue对象中取消息、处理消息、没消息时等待消息;
            // 这就使得它成为了一个looper thread;
            // 而如何处理消息,涉及Handler,稍后讲它。
61         Looper.loop();
63     }

因此,建立一个HandlerThread实例,就建立了一个looper thread:async

// 在合适的地方新建并启动线程,好比在Activity.onCreate()方法。
HandlerThread mHandlerThread = new HandlerThread("Gallery Downloading Thread");
mHandlerThread.start();

// 必须调用getLooper(),并且必须在start()以后调用,该方法获取与线程关联的Looper对象,以确保线程就绪;
// 由于该方法会被阻塞直至looper对象初始化好了。
mHandlerThread.getLooper();

// 在合适的地方调用quit()方法,好比Activity.onDestroy()方法,以退出线程的looper循环,再也不处理消息队列中的任何消息;
// 此处存疑 TODO:线程退出了吗?(好像并无,该如何处理?)
mHandlerThread.quit();

如此,一个looper thread就在运转了,但它发现没有消息,因而它等待。问题是,
该如何传递消息给它;它拿到消息后又是怎么处理的呢?答案在Handler。ide

Handler负责消息的发送和处理

Handler源码连接函数

关于Handler须要说的是:

  • Handler使你可以发送并处理MessageRunnable对象;

  • 当你建立了一个Handler实例,它就被绑定给建立它的线程,只能绑定给这1个线程,这也就意味着只关联了1个MessageQueue,此后,就能够发送messages和runnables给所关联的message queue,而当这些消息出队时,就在handler绑定的线程中获得执行;

  • Handler主要有2种用途:(1) 安排messages和runnables在未来的某个时间点执行;(2) 让另外一个线程作些事情。
    以上内容截取自源码文件的注释(翻译后)。

使用默认构造器建立一个Handler

Handler mHandler = new Handler();

经过源码来理解为何handler被绑定到了建立它的线程:

188    public Handler(Callback callback, boolean async) {
            // 经过该静态方法得到与当前线程绑定的Looper,而Looper是和当前线程唯一关联的(参看上文描述);
            // 这样就创建了handler和当前线程及其MessageQueue的唯一关联;
            // 固然也能够经过另外一个方法关联到指定的Looper,此处暂且不表 TODO.
198        mLooper = Looper.myLooper();
            // 若是当前线程没有looper,就没有queue,就无法接收消息,因此抛出异常。
199        if (mLooper == null) {
200            throw new RuntimeException(
201                "Can't create handler inside thread that has not called Looper.prepare()");
202        }
203        mQueue = mLooper.mQueue;
204        mCallback = callback;
206    }

// 默认的构造器就调用上面的方法,绑定handler到当前线程,也就是建立它的线程。
113    public Handler() {
114        this(null, false);
115    }

如今有了handler,又该如何发送消息呢?在发送以前,咱们先新建一个消息。

使用默认构造器建立一个Message

Message msg = new Message();
msg.what // 自定义的消息识别码,整形,以便接收者识别消息;
msg.arg1 // 若是两个int数据就能够知足你的要求,就用arg1和arg2;
msg.arg2
msg.obj // 若是复杂,就传递Object对象,或者Bundle数据;
msg.setData(Bundle data)

使用Handler发送消息

Handler能够发送处理MessageRunnable两种对象,提供了若干方法:

boolean post(Runnable r);
boolean postAtTime(Runnable r, Object token, long uptimeMillis);
boolean postDelayed(Runnable r, long delayMillis);

boolean sendEmptyMessage(int what);
boolean sendMessage(Message msg);
boolean sendMessageAtTime(Message msg, long uptimeMillis);
boolean sendMessageDelayed(Message msg, long delayMillis);

这些方法最终都调用了sendMessageAtTime(),而后把消息放入队列。Runnable对象在内部被转成了Message对象的callback字段,稍后讲它。

Message绑定给了发送它的Handler

须要特别关注message的成员变量Handler target,消息入队时,该字段被设置为发送它的handler,这就确保了message对象和handler对象的唯一关联。这样在message出队时,就知道该交给哪一个handler处理;除此以外它还有别的用处,稍后讲它。

高效发送消息的作法

上文发送消息的作法,每次都须要新建一个message实例。若是频繁的话,Android推荐这样作:

mHandler
    .obtainMessage(int what, int arg1, int arg2, Object obj)
    .sendToTarget();

Handler.obtainMessage(...) 方法从公共循环池里获取消息(若是没有的话,它会建立新的实例),并传入消息的各个字段。这样能够避免建立新的Message实例,提升效率。
Message.sendToTarget()方法只执行了一条语句target.sendMessage(this),这就是上文提到的target字段的别的用处。这样就把消息放入了队列。

使用Handler处理消息

这是消息循环的核心源码,一个死循环:

// Looper.loop()
109    public static void loop() {
110        final Looper me = myLooper();
114        final MessageQueue queue = me.mQueue;
115
121        for (;;) {
122            Message msg = queue.next(); // might block 没有消息则阻塞。
123            if (msg == null) {
                    // 收到空消息就退出。
124                // No message indicates that the message queue is quitting.
125                return;
126            }
                // 这就是前面提到的target字段的做用:知道把出队的消息交给谁。
135            msg.target.dispatchMessage(msg);
153        }
154    }

下面就开始dispatchMessage:

// Handler.dispatchMessage()
93     public void dispatchMessage(Message msg) {
            // 上文提到的,发送消息时,Runnable对象在内部被转成了Message对象的`callback`字段;
            // 若callback不为空,那么消息就是一个runnable对象,那么就执行它;
94         if (msg.callback != null) {
95             handleCallback(msg);
96         } else {
                // 若是不是runnable,那么就是Message对象了:
                // 上文只讲了使用默认构造器建立handler,也能够Handler(Callback callback);
                // 这样就为每一个handler设置了各自的callback,优先执行它;
97             if (mCallback != null) {
98                 if (mCallback.handleMessage(msg)) {
99                     return;
100                }
101            }
                // 若是handler对象没有本身的callback,那么就执行这个方法;
                // 这是一个空方法,当经过默认构造器新建一个handler时须要覆写它。
102            handleMessage(msg);
103        }
104    }

理论知识已经具有了,那么接下来,在1个典型的应用场景中使用它们解决咱们的问题。

一个demo使用后台线程完成下载任务

这段代码截取自《Android权威编程指南》第26, 27章,做者构建了一个这样的App:下载Flicker上最新的100张缩略图,并填充到GridView内。首先经过AsyncTask线程获取包含缩略图url信息的XM文件;而后下载那些url指向的缩略图。这里须要思考的是,什么时候下载这些缩略图,是一次下载完?仍是下载一部分?由谁触发下载?下载后怎样填充到GridView内?
解决了这些问题,就基本掌握了建立后台线程,并和主线程通讯的方法。

public class PhotoGalleryFragment extends Fragment {
    ThumbnailDownloader<ImageView> mThumbnailDownloader;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        mThumbnailDownloader = new ThumbnailDownloader<ImageView>(new Handler());
        mThumbnailDownloader.setOnThumbnailDownloadListener(new OnThumbnailDownloadListener<ImageView>() {
            public void onThumbnailDownloaded(ImageView imageView, Bitmap bitmap) {
                imageView.setImageBitmap(bitmap);
            }
        });
        mThumbnailDownloader.start();
        mThumbnailDownloader.getLooper();
    }

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

    private class GalleryItemAdapter extends ArrayAdapter<GalleryItem> {
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ImageView imageView;
            String url;
            // 对于须要显示在GridView视图中的成百上千张图片,咱们不可能一次下载完而后显示;
            // 只能在须要显示时下载,而GridView的adapter知道何时显示哪些视图;
            // 因此咱们在此处安排后台线程的下载任务。
            mThumbnailDownloader.queueThumbnail(imageView, url);
            return convertView;
        }
    }
}


public class ThumbnailDownloader<Token> extends HandlerThread {
    private static final String TAG = "ThumbnailDownloader";
    private static final int MESSAGE_DOWNLOAD = 0;

    Handler mHandler;
    Map<Token, String> requestMap = Collections.synchronizedMap(new HashMap<Token, String>());

    Handler mResponseHandler;
    OnThumbnailDownloadListener<Token> mOnThumbnailDownloadListener;

    public interface OnThumbnailDownloadListener<Token> {
        void onThumbnailDownloaded(Token token, Bitmap thumbnail);
    }

    public void setOnThumbnailDownloadListener(OnThumbnailDownloadListener<Token> l) {
        mOnThumbnailDownloadListener = l;
    }

    public ThumbnailDownloader(Handler responseHandler) {
        super(TAG);
        // 后台线程能在主线程上完成任务的一种方式是,让主线程将其自身的Handler传给后台线程;
        // mResponseHandler始终和主线程保持关联,由它发送的消息都将在主线程中获得处理。
        mResponseHandler = responseHandler;
        // 咱们也能够传递主线程的context,经过下述方式获取主线程的handler:
        // mResponseHandler = new Handler(mContext.getMainLooper());
    }

    public void queueThumbnail(Token token, String url) {
        // requestMap是一个同步HashMap。 使用Token做为key,可存储或获取与特定Token关联的URL.
        requestMap.put(token, url);
        // mHandler是和后台线程关联的,咱们开放这个方法给主线程,主线程调用这个方法来安排后台线程的任务。
        // 咱们把下载信息封装成message后放入后台线程的收件箱。
        mHandler.obtainMessage(MESSAGE_DOWNLOAD, token).sendToTarget();
    }

    @Override
    protected void onLooperPrepared() {
        // onLooperPrepared()方法发生在Looper.loop()以前,此时消息尚未开始循环,
        // 因此是咱们实现mHandler的好地方,在此处下载。
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MESSAGE_DOWNLOAD) {
                    Token token = (Token)msg.obj;
                    handleRequest(token);
                }
            }
        };
    }

    private void handleRequest(final Token token) {
        // 下载,并根据获取的数据构建Bitmap对象;
        final String url = requestMap.get(token);
        final Bitmap bitmap;
        // 下载完成后,咱们在后台线程使用与主线程关联的handler,安排要在主线程上完成的任务。
        // 除了post,咱们也能够sendMessage给主线程,那么主线程的handler须要覆写本身的handleMessage()方法。
        mResponseHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mOnThumbnailDownloadListener != null) {
                    mOnThumbnailDownloadListener.onThumbnailDownloaded(token, bitmap);
                }
            }
        });
    }
}

后记和参考

强烈建议阅读《Android权威编程指南》第26, 27这两章代码。
本书的官方主页连接 能够从这里获取本书的源码
但我在学习这两个章节的时候,有些概念和类的描述理解很吃力,好比“消息循环由一个线程和一个looper组成。 Looper对象管理着线程的消息队列”,又好比“一个Handler仅与一个Looper相关联,一个Message 也仅与一个目标Handler”。
由于做者着重讲解app的实现思路,对涉及到的类只给出告终论,没有说明为何。因此初学时很费解,实际编程时也是只知其然不知其因此然。后来读了些博文(下面给出了连接),又阅读了源码,总算厘清了这些类,而这篇文章就是我理解后的一个产物。

下面是3个做者的4篇博文,总结的都很棒,侧重于源码分析。尤为是第2篇,做者最后画了一张图,经过传送带来解释涉及到的类和概念:“在现实生活的生产生活中,存在着各类各样的传送带,传送带上面洒满了各类货物,传送带在发动机滚轮的带动下一直在向前滚动,不断有新的货物放置在传送带的一端,货物在传送带的带动下送到另外一端进行收集处理。”

下面就是源码连接了,对于理解android的消息处理机制很是有帮助。


版权声明:《Android Message Handler 消息处理机制总结笔记》由 WeiYi.Li 在 2015年10月15日写做。著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。
文章连接:http://li2.me/2015/10/communicate-with-a...

相关文章
相关标签/搜索