zoukankan      html  css  js  c++  java
  • 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 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的异步线程说到这里就拆不多了。


  • 相关阅读:
    golang包管理工具glide安装
    kafka单机安装和启动
    python爬虫得到unicode编码处理方式
    束带结发洛杉矶到付款啦就是的开发
    是的发送到
    【业务】
    下载
    Peach+Fuzzer
    【Selenium】IE浏览器启动问题
    TestNG
  • 原文地址:https://www.cnblogs.com/wei1228565493/p/4571704.html
Copyright © 2011-2022 走看看