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

前言

文章是一篇学习笔记,主要记录了阅读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章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容