关闭

Android消息处理机制:源码剖析Handler、Looper,并实现图片异步加载

标签: 异步加载源码android
5482人阅读 评论(4) 收藏 举报
分类:

引言

我们在做 Android 开发时,常常需要实现异步加载图片/网页/其他。事实上,要实现异步加载,就需要实现线程间通信,而在 Android 中结合使用 Handler、Looper、Message 能够让不同的线程通信,完成异步任务。虽然 Android 官方为我们提供了 AsyncTask 类来完成异步任务,但这个类存在许多问题,并不好用,而且,AsyncTask 也是通过 Handler 和 Thread 来实现异步加载的,所以学习这方面的知识是有必要的

本文讲解思路大致如下:绘制 Android 消息处理机制图 -> 源码剖析消息处理机制中的组件 -> 实现一个图片异步加载 Demo。最终 Demo 效果图如下:

Android 消息处理机制剖析

消息处理模型

我们不妨先想想,一个消息处理机制需要什么?当然是:

  • 消息源
  • 消息队列
  • 消息处理器
  • 消息管理器

其中消息管理器又将划分为三个子模块:消息获取、消息分发、消息循环。我们先不管 Android 内部将如何实现消息处理机制(因为处理机制的抽象结构肯定是一样的,只是具体实现不一样),按照我们列出来的4大模块画出一个简单的消息处理模型:

Android 消息处理组件

现在我们已经知道消息处理模型需要哪些组件了,那就去 Android SDK 里面找相应的类吧~然后我们会发现下面四个类:

  • Message 类代表消息
  • MessageQueue 类代表消息队列
  • Handler 代表消息获取、消息处理
  • Looper 代表消息循环、消息分发

可能有人会怀疑我在吹nb,在骗大家,这个时候我只能选择看源码了……为了方便大家理解,我将从 Looper 类开始分析,因为 Looper 类在消息处理机制中是个“承上启下”的功能模块。

Looper

在解析 Looper 之前,不妨先来想想为什么需要 Looper 吧。

我们在进行 Android 开发的时候,为了不阻塞主线程(UI 线程),常常需要另开一个线程完成一些操作,而这些操作有一些执行一次就完了,有一些可能需要执行几次,几十次,甚至只要程序进程存活就要不断执行该操作。而普通线程通过 start() 方法只能执行相关动作一次,为了满足多次执行的需求,于是有了 Looper。

那么我们就进入 Looper 的源码,看看 Looper 中有哪些成员吧:

public final class Looper {
    private static final String TAG = "Looper";

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;

    private Printer mLogging;
}

大家可以看到,Looper 的核心成员是一个消息队列,该Looper 对应的线程,ThreadLocal 对象,和一个主线程 Looper 的引用。我们根据 Looper 的使用流程来分析它们的作用:

要使用 Looper,就必须调用 Looper 的 prepare() 方法:

    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));
    }

我们可以看到,调用 prepare 方法后会通过 ThreadLocal 的 set 方法创建一个 Looper 对象,而且一个线程只能创建一个 Looper,我们不妨看看 ThreadLocal 通过 set 方法对 Looper 对象干了啥:

    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

实际操作 Looper 对象的是 values() 方法返回对象

Values values(Thread current) {
        return current.localValues;
    }

values() 方法返回的对象是一个线程的内部变量,我们再进去看看会发现:在 Thread 类内部是这样定义 localValues 的 - ThreadLocal.Values localValues。也就是说,set 方法实际完成的操作是,将 Looper 对象与线程绑定,并且该 Looper 对象只在该线程内有效,其他线程无法访问该 Looper 对象。

执行完 prepare() 方法之后,我们就要调用 Looper 的 loop() 方法来实现循环了:

    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;

        // 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.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.recycleUnchecked();
        }
    }

方法有点长,但实际逻辑比较简单:首先判断 prepare() 方法是否被调用,以确保 Looper 和某个线程绑定(需要注意的是:默认情况下,新建的 Looper 对象都与主线程绑定),然后获取对应线程的消息队列,之后就不断循环读取队列中的消息,如果队列中没有消息时 Loop() 方法就会结束(一般不会出现这种情况),否则将消息交给 msg.target 对象分发。

以上就是 Looper 的核心代码了,通过分析我们可以了解到 Looper 与线程的关系,以及在消息分发机制中所起的作用,如图:

Message

既然我们在分析 Looper 源码的最后提到了 Message 类,那么我们就先来看看 Message 类~那么 Message 类作为消息的载体到底存储了什么,做了什么呢?

public final class Message implements Parcelable {
    public int what;

    public int arg1; 

    public int arg2;

    public Object obj;

    public Messenger replyTo;

    public int sendingUid = -1;

    /*package*/ static final int FLAG_IN_USE = 1 << 0;

    /** If set message is asynchronous */
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

    /** Flags to clear in the copyFrom method */
    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;

    /*package*/ int flags;

    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    /*package*/ Message next;

现在我们可以知道,原来刚刚处理消息的 target 就是 Handler 类的对象啦。在 Message 类里,what 用于辨识 Message 的用途,arg1 和 arg2 用于传递一些简单的数据,obj 用于传递对象,data 用于传递复杂数据。

Message 类的方法我觉得是没啥好说的,基本上都是 get/set 方法,当然还有回收方法,例如在 Looper 的 loop() 方法中,每一次循环结束都会执行 Message 的 recycleUnchecked() 方法,将被分发 Message 对象回收。

可能有人会奇怪,消息如果没有被处理就被回收了不会发生消息丢失的情况吗?莫慌,等会我会在分析 Handler 处理消息的时候给大家解释。

Handler

我们在前面的分析中看到,真正处理消息的是 Handler 的 dispatchMessage() 方法,那么我们就从这个方法入手分析 Handler 吧:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在 dispatchMessage() 方法中,如果 msg 的 callback 不为 null 会调用 Handler 的 handleMessage() 方法处理消息。也就是说,只要消息的 callback 不为 null,就会调用 handleCallback() 方法,那么未处理的消息会不会回到消息队列呢?

    private static void handleCallback(Message message) {
        message.callback.run();
    }

看到这里有没有恍然大悟的感觉呢?刚刚我们在分析 Message 源码的时候已经知道,callback 就是 Runnable 接口的实例,也就是说,如果消息没有被处理,就会回到消息队列中啦。那么 Handler 又是怎样处理消息的呢?

    public void handleMessage(Message msg) {
    }

竟然是个空方法……不过也很正常,因为 Handler 类只需要提供抽象,具体的处理逻辑应该由开发者决定嘛。那我们分析就到此为止了吗?才没有!我们还没有剖析 Handler 能够实现异步事件处理的原因呢,回到 Handler 的源码,我们会看到下面这个代码段:

    final MessageQueue mQueue;
    final Looper mLooper;
    final Callback mCallback;
    final boolean mAsynchronous;
    IMessenger mMessenger;

我靠……Handler 里面居然拥有消息队列、Looper、异步标志位,我们回想一下刚刚分析得到过什么结论:一个 Looper 只能属于一个线程,Looper 有对应线程的消息队列。我们再来看看 Handler 的构造方法,随便挑一个吧:

    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;
    }

我们可以看到,Handler 内的消息队列就是 Looper 里的消息队列,也就是说 Handler 能够与任何一个线程的消息队列进行通信,并处理其中的消息,或者发送信息到其他线程的消息队列中!

异步处理 Demo

完成上面的分析以后,我们就知道在 Android 中的消息处理机制了,那么现在就来实现一个异步处理 Demo吧。Demo 非常简单,就是异步下载一张图片,并把它显示到一个 ImageView 中,代码比较多,就只给出核心代码,下面有下载地址:

public class DownloadTask implements Runnable{
…………    
    private void updateMsg(Message msg){
        Bundle data = new Bundle();
        Bitmap img = downImage(url);

        data.putString("url", url);
        data.putParcelable("img", img);
        msg.setData(data);
    }

    public Bitmap downImage(String url) {
        Bitmap img = null;

        try {
            URL mUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) mUrl.openConnection();
            conn.setDoInput(true);
            conn.connect();

            InputStream is = conn.getInputStream();
            img = BitmapFactory.decodeStream(is);
        } catch (IOException e) {
            Log.d(TAG, "downloadImg-Exception");
        }

        return img;
    }
}

DownloadTask 类负责处理下载任务,下载开始下载任务,下载任务完成后将图片地址和图片存到 msg 里边,发送给 DownloadHandler :

public class DownloadHandler extends Handler{
……

    @Override
    public void handleMessage(Message msg) {
        String url = msg.getData().getString("url");
        Bitmap img = msg.getData().getParcelable("img");

        Log.d(TAG, url);

        loader.iLoader.update(img);
    }
}

最后通过 ILoader 接口更新外部 UI:

    public interface ILoader {
        public void update(Bitmap img);
    }
}

下载地址

4
0
查看评论

Android中Looper的实现原理

Android中Looper的实现原理,为什么调用Looper.prepare()就在当前线程关联了一个Looper对象,它是如何实现的。 1、线程间通信机制 首先,looper、handler、messagequeue三者共同实现了android系统里线程间通信机制。如在A、B两个子线程之间...
  • syc434432458
  • syc434432458
  • 2015-09-08 11:30
  • 1307

『ANDROID』android的消息处理机制—Looper/Handler/Message

原文地址:http://www.jb51.net/article/33514.htm android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道。 L...
  • fallenink
  • fallenink
  • 2014-11-27 20:49
  • 802

Android多线程及异步任务消息处理机制(一)--Handler的使用

转载请注明出处: 前言         我们知道,不管在任何的语言或操作系统平台(Android系统也不例外),多线程、多进程和异步任务的相关技术地讨论都是永恒的话题,很多的开发需求都需要使用多线程及异步任务以便实现多任务的同时执行和灵活的...
  • yegongheng
  • yegongheng
  • 2014-08-26 12:39
  • 3150

Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38377229 ,本文出自【张鸿洋的博客】很多人面试肯定都被问到过,请问Android中的Looper , Handler , Message有什么关系?本篇博客目的首先为...
  • lmj623565791
  • lmj623565791
  • 2014-08-07 09:17
  • 198256

Android进阶——Android消息机制之Looper、Handler、MessageQueen

前言 Android消息机制可以说是我们Android工程师面试题中的必考题,弄懂它的原理是我们避不开的任务,所以长痛不如短痛,花点时间干掉他,废话不多说,开车啦 Android消息机制的简介 在安卓开发中,常常会遇到获取数据后更新UI的问题,比如:在获取网络信息后,需要弹出一个Toast...
  • qq_30379689
  • qq_30379689
  • 2016-11-30 11:44
  • 5323

Android中的Handler,Looper,Message机制

Android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类。下面一一介绍: 线程的魔法师 Looper Looper的字面意思是“循环者...
  • jiangwei0910410003
  • jiangwei0910410003
  • 2013-11-29 12:03
  • 4349

android中handler和looper的工作原理

在android中,UI线程和其他子线程之间传递数据一般要用到handler和looper,那么其工作原理是 什么呢? 我们通用的方法是:在UI线程中创建handler--将handler传递到子线程中--在子线程中sendmessage---消息被加载进looper的message队列中--在U...
  • yxb_yingu
  • yxb_yingu
  • 2016-06-25 18:12
  • 1114

深入Android的消息机制源码详解~Handler,MessageQueue与Looper关系

一说到Android的消息机制,自然就会联想到Handler,我们知道Handler是Android消息机制的上层接口,因此我们在开发过程中也只需要和Handler交互即可,很多人认为Handler的作用就是更新UI,这也确实没错,但除了更新UI,Handler其实还有很多其他用途,比如我们需要在子...
  • javazejian
  • javazejian
  • 2016-03-04 23:25
  • 2031

Android源码分析--Handler和Looper机制详解

在Android系统中的应用程序,与Java的应用程序相同,都是靠消息驱动,简单的说就是:有一个消息队列,我们可以不断的向这个消息队列中添加消息,并从中取出消息,处理消息。Android中与此工作相关的主要是由Handler,Looper以及Message来完成。 Looper类:为一个线程运行着一...
  • smbroe
  • smbroe
  • 2015-03-13 16:21
  • 4445

Android Handle消息机制:秒懂Looper、Handler、Message三者关系

一般情况下,在主线程中我们绑定了Handler,并在事件触发上面创建新的线程用于完成某些耗时的操作,当子线程中的工作完成之后,会对Handler发送一个完成的信号,而Handler接收到信号后,就进行主UI界面的更新操作。 Handler机制主要为了解决以下2个问题 1.  不要阻塞U...
  • WHB20081815
  • WHB20081815
  • 2017-03-28 17:09
  • 393
    个人资料
    • 访问:210126次
    • 积分:4458
    • 等级:
    • 排名:第7964名
    • 原创:234篇
    • 转载:8篇
    • 译文:0篇
    • 评论:46条
    博客专栏
    最新评论