美文网首页
Android开发之异步任务消息机制

Android开发之异步任务消息机制

作者: 大荒里种菜 | 来源:发表于2019-08-20 23:30 被阅读0次

    前言

    文章是一篇学习笔记,主要记录了阅读HandlerMessageMessageQueueLooper源码(Android 8.1)的一些心得体会,但并没有涉及到更深层次源码的分析,比如MessageQueuenative层的源码。而AsyncTask只是简单介绍其API

    概述

    Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueueLooper的支撑,常用于更新UI

    MessageQueue用于存储消息(Message),内部的存储结构采用了链表,而不是队列,它提供插入和删除消息的功能,但不会处理消息。Looper,MessageQueue的管家,它的loop方法会一直查看MessageQueue是否存在消息(Message)需要处理,如果有,就交给Handler来处理。Handler是消息(Message)的发送者和处理者。

    ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据,这些数据对于其他线程是不可见的。每个线程中只会有一个Looper,可以通过ThreadLocal获取到。

    另外,线程默认是没有Looper的,如果需要使用Handler就必须为线程创建,比如在子线程。而主线程已经为我们创建好了,可以查阅ActivityThreadmain方法,其中包括了MessageQueue的初始化。

    它们的数量级关系是:Handler(N):Looper(1):MessageQueue(1):Thread(1)

    Handler的主要作用是将一个任务切换到某个指定的线程中执行,这样设计主要是为了解决Android只能再主线程中更新UI

    系统为什么不允许再子线程中访问UI呢?这是因为AndroidUI控件不是线程安全的,如果再多线程中并发访问可能会导致UI控件处于不可预期的状态。

    加锁的缺点有两个:首先加上锁机制会让UI访问的逻辑变得复杂;其次,锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

    创建方式

    在此只列出一种创建Handler的方式,其他的创建方式可以自行百度,但是,一定要注意:如果在没有Looper的线程里创建Handler会报错的

        // 在主线程创建
        public class MyHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                // 处理的代码
            }
        }
    
        MyHandler handler = new MyHandler();
    
        // 子线程中执行
        Message message = handler.obtainMessage();
        message.what = 1;
        message.obj = 123;
        handler.sendMessage(message);
    

    原理图

    Handler原理图(一).png handler原理图(二).png

    源码分析

    先从ActivityThreadmain方法看起,里面有两句需要注意的:

    public static void main(String[] args) {
          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    
          // CloseGuard defaults to true and can be quite spammy.  We
          // disable it here, but selectively enable it later (via
          // StrictMode) on debug builds, but using DropBox, not logs.
          CloseGuard.setEnabled(false);
    
          Environment.initForCurrentUser();
    
          // Set the reporter for event logging in libcore
          EventLogger.setReporter(new EventLoggingReporter());
    
          // Make sure TrustedCertificateStore looks in the right place for CA certificates
          final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
          TrustedCertificateStore.setDefaultUserDirectory(configDir);
    
          Process.setArgV0("<pre-initialized>");
    
          // 注意
          Looper.prepareMainLooper();
    
          ActivityThread thread = new ActivityThread();
          thread.attach(false);
    
          if (sMainThreadHandler == null) {
              sMainThreadHandler = thread.getHandler();
          }
    
          if (false) {
              Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
          }
    
          // End of event ActivityThreadMain.
          Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          // 注意
          Looper.loop();
    
          throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    大致就是说,主线程的Looper已经准备好了。通常,我们创建Handler会选择继承Handler并重写handleMessage,因为父类的handleMessage什么也不做。这里,我们关注其构造方法(无参的):

    public Handler() {
        this(null, false);
     }
    
    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,它会抛出异常:

    Can't create handler inside thread that has not called Looper.prepare()
    

    可见,Handler的创建需要Looper。那这个异常如何解决呢?举例如下:

    new Thread("new Thread") {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
            }
        }.start();
    

    现在,我们回想一下,为什么在主线程可以直接创建Handler?正如前面所讲述的,当我们的App启动后,会调用ActivityThreadmain的方法,而LooperprepareMainLooper方法会被调用,也就是创建了Looper,也就是满足了Handler的创建条件。其源码如下:

    /** 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) {
        if (sThreadLocal.get() != null) {
            // 一个线程只能创建一个Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
     }
    
    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
      * to call this function yourself.  See also: {@link #prepare()}
      */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    

    不管是prepareMainLooper()方法还是prepare()方法,最后都是通过prepare(boolean quitAllowed)来创建Looper。我们先来看sThreadLocal

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    

    详细的介绍可以参考这篇博客:Android的消息机制之ThreadLocal的工作原理。它的作用是保存当前线程的Looper,且线程间互不干扰。

    再看Looper的构造方法,它完成了MessageQueue的创建:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    

    总的来说,Android的主线程就是ActivityThread,主线程的入口方法为main,再main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。

    Message:消息载体

    • public int what:标识
    • public int arg1:保存int数据
    • public int arg2:保存int数据
    • public Object obj:保存任意数据
    • long when:记录应该被处理的时间值,换句话说就是延时时间
    • Handler target:保存在主线程创建的Handler对象引用
    • Runnable callback:用来处理消息的回调器(一般不用,见原理图二)
    • Meaage next:指向下一个Message用来形成一个链表
    • private static Message sPool:用来缓存处理过的Message,以便复用
    • Message obtain():它利用了Message中消息池(sPool

    先从Message的创建入手,官方更推荐使用obtain来创建,而不是其构造方法。它的构造方法什么也不做,只是完成了Message的创建,而obtain可以复用Message,且有很多多种重载。从内存、效率来看,obtain更优。

    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
     */
    public Message() {
    }
    
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() { // 最常见
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    

    可见,消息池采用了链表结构,确实是复用了Message

    private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    
    private static final int MAX_POOL_SIZE = 50;
    

    下面是Message的回收处理:

    /**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
    
    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;
    
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
    

    前面也提到,Message可以自行处理,处理的逻辑交给一个Runnable,也就是callback,读者可以自行查阅obtain的其他重载,看看callback赋值的情况,这里就不帖代码了。

    Handler:发送、处理、移除消息

    前面已经分析过Message的相关代码,该小节将从发送Message开始分析。其实,也可以通过Handler来创建Message,其内部也是调用Message.obtain来创建Message对象。

    public final Message obtainMessage() {
        return Message.obtain(this);
    }
    

    Handler发送消息的方式有多种方式,这里选了一种最常见的,它的调用流程:

    发送过程.png
    源码如下:
    public final boolean sendMessage(Message msg) {
        return sendMessageDelayed(msg, 0); // 延时发送时间为0
    }
    
    // 延时发送
    public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        // 容错
        if (delayMillis < 0) {
            delayMillis = 0;
        }
         // 当前时间加上延时时间就是真正发送Message的时间
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        // Looper创建时,MessageQueue也创建了
        // mQueue在Handler创建是就初始化了,代码在前面已经贴过了
        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);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // Message的成员变量,用来存储Handler的对象引用
        // 也就是记录了由哪个Handler来处理这个Message
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 将Message插入到MessageQueue中,完成发送
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    另外,Handlerpost方法也可以发送Message,且该Message将交给它自身来处理,当读者看过dispatchMessage方法后就明白了。

    public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r; // 可以回忆下讲述Message的那一节
        return m;
     }
    

    下面是dispatchMessage的源码,它在Looper.loop方法中被调用:

    public void dispatchMessage(Message msg) {
        // 如果Message.callback不为null,就交给它自身处理
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 如果mCallback不为null,交给该Handler处理
            // mCallback是Handler内部的一个接口
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 最后是我们最常见的,重写handleMessage
            handleMessage(msg);
        }
    }
    

    接着来看看Callback,这个接口长啥样:

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }
    

    它也是在Handler创建的时候初始化的,但调用的构造方法不一样,导致最后的初始化不一样。无参的构造方法会导致mCallbacknull

    public Handler(Callback callback) {
        this(callback, false);
    }
    

    最后来看看Handler移除Message的实现,但真正的实现交给了MessageQueue

    public final void removeMessages(int what) {
        mQueue.removeMessages(this, what, null);
    }
    
    public final void removeMessages(int what, Object object) {
        mQueue.removeMessages(this, what, object);
    }
    

    MessageQueue:存储消息的,以message的when排序优先级

    MessageHandler发送后到达MessageQueue,它采用链表的数据结构来存储Message。下面是其构造方法:

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        // 如果需要理解为什么loop方法不会卡住主线程,可以从这个本地方法开始入手
        mPtr = nativeInit();
    }
    

    MessageQueue中最主要的两个方法是nextenqueueMessage

    next方法也会阻塞(当消息队列为空时或当前时间还没到达Message要处理的时间点时)但不会卡住主线程,它的工作就是根据需求从消息队列中取一个Message。注意,next方法是一个无限循环的方法。

    enqueueMessage方法的工作就是将一个Message插入到消息队列且位置是合适的,因为它会根据Message要处理的时间点进行排序,从它的插入操作也可以了解到MessageQueue采用了链表。

    由于next方法和enqueueMessage方法的源码过长,下面只贴出enqueueMessage排序Message的源码:

    if (p == null || when == 0 || when < p.when) {
       // New head, wake up the event queue if blocked.
       msg.next = p;
       mMessages = msg;
       needWake = mBlocked;
    } else {
       // Inserted within the middle of the queue.  Usually we don't have to wake
       // up the event queue unless there is a barrier at the head of the queue
       // and the message is the earliest asynchronous message in the queue.
       needWake = mBlocked && p.target == null && msg.isAsynchronous();
       Message prev;
       // 查找合适的插入位置
       for (;;) {
           prev = p;
           p = p.next;
           if (p == null || when < p.when) {
              break;
           }
           if (needWake && p.isAsynchronous()) {
              needWake = false;
            }
       }
       msg.next = p; // invariant: p == prev.next
       prev.next = msg;
    }
    

    关于阻塞但不会卡住主线程的问题在下一小节会提到。

    Looper:从MessageQueue中获取当前需要处理的消息,并提交给Handler处理

    关于Looper在前面也讲述了一部分,现在只剩下loop方法了,很重要。在它的内部也开启了一个无限循环。

    for无限循环中,有一句表明了会阻塞:

    Message msg = queue.next(); // might block
    

    导致它阻塞的原因在MessageQueue.next方法。当消息队列退出或正在退出时,loop方法会结束,也就是跳出无限循环。

    if (msg == null) {
       // No message indicates that the message queue is quitting.
       return;
    }
    

    在这个循环里还有一句需要注意的:

    msg.target.dispatchMessage(msg);
    

    意思时交给Handler处理这个Message。至此,整个流程就分析完了。

    面试的时候可能会遇到:为什么loop方法会阻塞主线程但不会卡住呢?以下的文章也许可以解答你心中的疑问:

    异步任务

    • 逻辑上:以多线程的方式完成的功能需求
    • API上:指AsyncTask

    AsyncTask

    • 在没有AsyncTask之前,我们用Handler+Thread就可以实现异步任务的功能需求
    • AsyncTask是针对HandlerThread的封装,使用它编码更加简洁,更加高效
    • AsyncTask封装了ThreadPool,比直接使用Thread效率要高

    相关API

    • AsyncTack<Params,Progress,Ressult>
      • Params启动任务执行的输入参数,比如HTTP请求的URL
      • Progress后台任务执行的百分比
      • Result后台执行任务最终返回的结果,比如String
    • execute(Params... params):启动任务,开始任务的执行流程
    • onPreExcute():在分线程工作开始之前在UIThread中执行,一般用来显示提示视图5
    • doInBackground(Params... params):在workerThread中执行,完成任务的主要工作,通常需要较长的时间
    • onPostExecute(Result result):在doInBackground执行完后再UIThread中执行,一般用来更新界面
    • publishProgress(Progress... values):在分线程中发布当前进度
    • onProgressUpdate(Progress... values):在主线程中更新进度

    AsyncTask在具体的使用过程中也是有一些条件限制的,主要有以下几种(摘自《Android开发艺术探索》):

    • 1、AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程,当然这个过程在Android 4.1及以上版本中已经被系统自动完成。在Android 5.0的源码中,可以查看ActivityThreadmain方法,它会调用AsyncTaskinit方法,这就满足了AsyncTask的类必须在主线程中进行加载这个条件了。
    • 2、AsyncTask的对象必须在主线程中创建。
    • 3、execute方法必须在UI线程调用。
    • 4、不要再程序中直接调用onPreExecuteonPostExecutedoInBackgroundonProgressUpdate方法
    • 5、一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常
    • 6、在Android 1.6之前,AsyncTask是串行执行任务的,Android 1.6的时候AsyncTask开始采用线程池里处理并行任务,但是从Android 3.0开始,为了避免AsyncTask所带来的并发错误,AsyncTask又采用一个线程来串行执行任务。尽管如此,在Android 3.0以及后续的版本中,我们仍然可以通过AsyncTaskexecuteOnExecutor方法来并行地执行任务。

    总结

    文章以Handler的创建过程为参照,简单介绍了Handler的原理和源码。文章末尾对AsyncTask进行了简单的介绍。读者有空可以读一读《Android开发艺术探索》的第10章和第11章。

    相关文章

      网友评论

          本文标题:Android开发之异步任务消息机制

          本文链接:https://www.haomeiwen.com/subject/gxqakctx.html