Android之异步线程原理

基础介绍

异步消息处理线程是指,线程在启动后会进入一个无线循环体中,没循环一次,从内部的消息队列中取出一个一个消息,并回调相应的消息处理函数,执行完一个消息后则继续循环。如果消息队列为空,线程会暂停,知道消息队列中有新的消息。

异步消息处理线程本质上仍然是一个线程,只不过这种线程的执行代码设置成如上所述的逻辑而已。在android中实现异步线程主要涉及到如下几个类:ThreadLocal,Looper,MessageQueue,Handler,Message,接下来我们一一介绍这几个类,解析Android的异步线程机制。

先上一张框架图:

ThreadLocal

ThreadLocal并不是Android的sdk中的类,而是java.lang中的一个类,该类的作用是为线程创建一个基于线程的变量存储,我们可以称之为线程局部存储。ThreadLocal可以使对象达到线程隔离的目的,它为每一个线程维护自己的变量拷贝,通过其中的set方法将变量绑定到线程上。ThreadLocal提供了一种解决多线程同步的问题解决方案,通过为每一份变量进行拷贝,这样的话,每个线程操作的都是属于自己的变量,而不是共同的一个变量,因此也就不需要同步锁了。举个栗子,我们创建出一个变量,而这个变量会被两个线程操作。一般情况下,我们会给这个变量加锁,通过这种方式来解决同步的问题。但是当我们使用ThreaLoca时,我们就可以通过ThreadLoca来为每一个线程做一个拷贝,而且这个拷贝是跟线程绑定在一起的,也就是说每个线程可以更改自己的变量而不影响另外一个线程。这样也就不需要锁了。ThreadLocal在set时会自动绑定到当前的线程,而不需要自己去绑定。代码这里就不写了,知道中心思想就行。有人可能会问,这跟Android有什么关系呢。这就要说到我们的Looper了,因为在Android的异步线程中,ThreadLocal绑定的这个线程变量就是Looper的一个对象。

Looper

Looper有什么用呢,要实现异步线程,必须要Looper,因为Looper是用来产生一个MessageQueue。我们可以通过查看源代码知道,在Looper类中有一个成员变量mQueue(MessageQueue类的实例),该变量用于保存Looper中MessageQueue。

Looper通过静态方法Looper.prepare()方法来创建出一个MessageQueue对象。注意,Looper.prepare()方法在一个线程中只能执行一次。

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

以上为android的源代码,执行了两次prepare时,就会抛出一个异常。这个其实很自然就可以想到,因为前面提到,Looper是用来创建MessageQueue的。一个线程只能有一个MessageQueue,因此自然只能有一个Looper对象。看完prepare()源代码好像并没有发现它创建出了MessageQueue的一个实例啊,对的,这里是看不到,但是。我们来看看在创建一个新的Looper对象时所做的工作:

我们找到Looper的构造函数:

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

我们可以发现,Looper的这个构造函数是私有的,而且Looper只有这一个构造函数。所以我们可以看出我们不能在其他的类中new出一个Looper对象。但是,这不是重点,重点是Looper在这里创建出了一个MessageQueue对象。

loop()方法

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

函数myLooper用来返回当前线程的Looper对象,通过一个for(;;)来执行循环。在for循环内部,通过MessageQueue的next方法来去取出其中的Message,这里有一点需要注意,就是取出来的message,最后会调用msg.target.dispatchMessage(msg);来处理消息,该方法在Handler中,因为msg.target是一个Handler对象。这个放到Handler中说

MessageQueue

MessageQueue是用来处理消息队列的。该类中有几个方法,一个是next()方法,用于取出队列中下一个元素的。另外一个是enqueueMessage方法,用于向消息队里中添加一个元素。该方法会在Handler中的sendMessage中使用到,这里稍微提一下,在Handler的sendMessage中,会调用这个方法,将Message添加到接收线程的MessageQueue中。

Handler

Handler是用于发送信息的,我们熟知的方法就是其中的sendMessage方法了,用于向消息接收线程发送消息。上面的框架图中有说到,Handler一定要在接收消息的线程中创建,只有这样的话才可以给该线程发送消息。因为创建出的Handler对象handler必须要有该线程的MessageQueue消息队列才可以给该线程发送消息。当Handler在创建时,会用到如下的构造函数:

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时, 会通过mLooper = Looper.myLooper();来获取当前线程的Looper对象,而Looper对象又是获取MessageQueue对象前提。Handler在调用handler.sendMessage(Message)发送message时,最终会调用到这个方法:

handler的sendMessage方法(最终会调用的方法)

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        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);
    }

在这里可以看出,handler会获得当前线程的MessageQueue对象。之后调用enqueueMessage(queue, msg, uptimeMillis);这个方法是Handler中的方法,而不是MessgaeQueue中的enqueueMessage方法,我们查看Handler中的enqueueMessage方法:

handler中的enqueueMessage

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到个方法体中的第一行代码:msg.target = this;将当前发送的message.target对象设置成当前的handler。这个在Looper.loop()方法中,调用了 msg.target.dispatchMessage(msg);方法。这个很快就会介绍。先不说这个。可以看到,handler中的enqueueMessage方法会设置message的target为当前对象,之后会调用MessageQueue对象的enqueueMessage(该方法在MessageQueue中介绍过,是用于向消息队列添加消息的方法)方法,将message添加到MessageQueue对象中。

好了,我 们现在来说一下handler的dispatchMessage(msg)方法。我们看一下这个方法的源代码,该方法会在looper的loop方法中使用的,当获取每个消息时,会调用方法 msg.target.dispatchMessage(msg);来处理每个消息。

dispatchMessage

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

该方法中先判断消息本身是否有回调函数,有的话则调用消息的回调函数,如果没有,再判断创建这个Handler时有没有传递Callback接口,注意,这个 接口的名字就是Callback,Handler可以通过构造函数来给这个mCallback赋值,如果这个mCallback传递了值的话 就会调用这个方法,否则调用Handler本身的handleMessage方法。这个方法在Handler中是空的,需要在继承Handler的类中,重写该方法。也就是说重写的该方法实际上是优先级最低的。

后面两种比较常见,但是前面一种,也就是消息本身携带了接口的一般通过Message.obtain(Handler h, Runnable callback)方法来设置。

好了 Android的异步线程说到这里就拆不多了。

时间: 2024-12-21 11:05:51

Android之异步线程原理的相关文章

Android Handler 异步调用修改界面与主线程

在Android编程的过程中,如果在Activity中某个操作会运行比较长的时间,比如:下载文件.这个时候如果在主线程中直接下载文件,会造成Activity卡死的现象:而且如果时间超过5秒,会有ANR报错. 在这种情况下, 可以使用Handler来处理. 涉及到的类主要有:Handler.Thread.Message.MessageQueue.Looper.HandlerThread 如果是针对上面的情况,可以只使用Handler.Message和Thread就可以解决.在Thread中处理下载

android高级----&gt;Handler的原理

andriod提供了Handler来满足线程间的通信,上次在更新UI的时候也提到过Handler的使用,关于Handler的基本使用,参见博客(android基础---->子线程更新UI).今天我们深入Handler的源码,了解一个Handler的内部执行原理. 目录导航 Handler简单说明 ActivityThread的说明 Handler的预备分析 Handler的原理分析 友情链接 Handler简单说明 一. 在Handler的原理说明之前,我们列出相关的重要的类: Message:

Android应用程序线程消息循环模型分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6905587 我们知道,Android应用程序是通过消息来驱动的,即在应用程序的主线程(UI线程)中有一个消息循环,负责处理消息队列中的消息.我们也知 道,Android应用程序是支持多线程的,即可以创建子线程来执行一些计算型的任务,那么,这些子线程能不能像应用程序的主线程一样具有消息循环呢?这 些子线程又能不能往应用程序的主线程中发送消息呢?本

Android:异步处理之AsyncTask的应用(二)

前言 在上一篇文章中<Android:异步处理之Handler+Thread的应用(一)>,我们知道Android的UI主线程主要负责处理用户的按键事件.用户的触屏事件以及屏幕绘图事件等:既然UI老人家都这么忙了,我们这些开发者肯定不能不识趣的去添乱阻塞UI线程什么的,否则UI界面万一停止响应了呢——这不是招骂的节奏么?!所以我们知道用Handler+Thread的方法,在子线程中处理耗时的任务,任务完成后通过Handler通知UI主线程更新UI界面,皆大欢喜有木有. 可是这样,还是有某些人觉

Android图片异步加载之Android-Universal-Image-Loader

将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就顺便整理记录下来,作为这一个多月来博客的重新开火做饭吧.从今天起我会陆续恢复博客的更新,也希望大家继续支持. 今天要介绍的是Github上一个使用非常广泛的图片异步加载库Android-Universal-Image-Loader,该项目的功能十分强大,可以说是我见过的目前功能最全.性能最优的图片异

android listview 异步加载图片并防止错位

网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertView 但没有异步操作也不会有问题. 我简单分析一下: 当重用 convertView 时,最初一屏显示 7 条记录, getView 被调用 7 次,创建了 7 个 convertView. 当 Item1 划出屏幕, Item8 进入屏幕时,这时没有为 Item8 创建新的 view 实例, Ite

Android图片异步加载之Android-Universal-Image-Loader类库的使用

Android开发中我们会经常遇到图片过多或操作不当造成Out of Memory异常,有时虽然是解决了这个问题但却会影响程序的运行效率,例如:当用户在快速滑动滚动条的过程中,我们程序在仍在艰难的加载服务器端的图片,这样给用户造成了极不好的体验.其实网络上关于图片的异步加载和缓存的讲解很多,但是其实,写一个这方面的程序还是比较麻烦的,要考虑多线程,缓存,内存溢出等很多方面,针对这一广大开发者都会遇到的问题,一些牛人们已经帮我们解决了这一问题,今天我为大家介绍一款很流行的开源类库,可以很很好的解决

AsyncTask 异步线程 用法

AsyncTask介绍Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理.首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的.Android为了降低这个开发难度,提供了AsyncTask.AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务.AsyncTask直接继承于Object类,位置为android.os.AsyncTask.要使用Asy

Android开发之线程池使用总结

线程池算是Android开发中非常常用的一个东西了,只要涉及到线程的地方,大多数情况下都会涉及到线程池.Android开发中线程池的使用和Java中线程池的使用基本一致.那么今天我想来总结一下Android开发中线程池的使用. OK,假如说我想做一个新闻应用,ListView上有一个item,每个item上都有一张图片需要从网络上加载,如果不使用线程池,你可能通过下面的方式来开启一个新线程: new Thread(new Runnable() { @Override public void ru