Android异步消息处理机制总结

  之前三篇博客,分别对android消息处理机制中涉及到的looper和handler根据源代码进行了分析,那么在这篇本博客中,我们来对android消息处理机制做一个总结。还是老惯例,直接先上示例代码。

   在子线程里实现更新ui的方式大家应该都知道,但是另一种形式的更新UI大家可能比较少见,在activity中:

private Handler mHandler;//全局变量
@Override
protected void onCreate(Bundle savedInstanceState) {
    mHandler = new Handler();
    new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mTestTV.setText("This is post");//更新UI
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
}
   咦,怎么好像在子线程里更新ui了?不是说不可以在子线程里更新Ui吗?上述原理是什么?这个问题我们最后再来回答。

   再来看一段代码,这次不在activity中了,在新建的类中:

public class LooperThread extends Thread {

    public Handler mHandler;

    @Override
    public void run() {
        // TODO Auto-generated method stub
        Looper.prepare();
        synchronized (this) {
            mHandler = new Handler() {

                @Override
                public void handleMessage(Message msg) {
                    // TODO Auto-generated method stub
                    super.handleMessage(msg);
                    Log.w("LooperThread", "handleMessage::Thread id---" + getId());
                }

            };
        }
        Looper.loop();
        notifyAll();
    }

}
   然后我们在activity中给其发消息:
final LooperThread mLooperThread = new LooperThread();
    mLooperThread.start();
    new Thread() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (mLooperThread.mHandler == null) {
                try {
                    wait();//防止在发送消息时Handler还没建立
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            mLooperThread.mHandler.sendEmptyMessage(0);
            Log.w(TAG, "Send Message::Thread id ---" + getId());
        }
    }.start();
    打印结果如下:

   从这两个示例代码中又有了问题,Looper是什么?mLooperThread.mHandler.sendEmptyMessage(0)使用之后又发生了什么?为什么打印了handleMessage里面的信息?handler又是什么?为什么在第一个示例代码中不需要用到Looper?接下来我们会一一解决这些问题。
   首先,这里将handler先简单的理解为处理消息的一种机制(我们稍后再来对其进行具体分析)。
  程序的入口是main,应用程序启动之后,将创建ActivityThread 主线程,就是我们常说的ui线程,即acticity运行的线程,为什么activity中运行的线程就是ActivityThread 主线程?是因为 在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的【主线程】负责执行。
  来看看main的部分源代码你就知道了:

public static void main(String[] args) {  
        Looper.prepareMainLooper();  
       // 创建ActivityThread实例  
        ActivityThread thread = new ActivityThread();  
        thread.attach(false);  
  
        if (sMainThreadHandler == null) {  
            sMainThreadHandler = thread.getHandler();  
        }  
  
         
        if (false) {  
            Looper.myLooper().setMessageLogging(new  
                    LogPrinter(Log.DEBUG, "ActivityThread"));  
        }  
  
        Looper.loop();  
  
        throw new RuntimeException("Main thread loop unexpectedly exited");  
    }  
    这里有三个方法,Looper.prepareMainLooper(),Looper.myLooper(),Looper.loop(); 加上之前的looper.prepare()因此我们就从looper.prepare开始看起,上源码:
public static void prepare() {
        prepare(true);
    }

    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));
}
     sThreadLocal是looper里的一种数据结构  ,其中 sThreadLocal的定义如下:
static final ThreadLocal<Looper> sThreadLocal =  new  ThreadLocal<Looper>();
    可以看到, 关键是Looper的构造函数new Looper(quitAllowed),直接上源代码:
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    从上面源码可以看到looper里的局部变量很简单,其实就是线程和消息队列,因此可以简单的默认为Looper是和线程以及消息队列绑定的,即looper里以后所有的操作都是通过mQueue,mThread。
   再来看myLooper(),该方法是获得Looper对象的引用,源代码为:
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
   那我们通过looper获得了线程,消息队列,接下来呢?当然是将消息队列中的消息发送出去给handler去处理了,是通过looper.loop()实现的,来看部分源代码:
 public static void loop() {
        //myLooper()方法就是通过sThreadLocal.get()返回我们刚刚设置的Looper
        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;

       .....

       for (;;) {
            Message msg = queue.next(); // 可能会阻塞
            if (msg == null) {
                // 没有消息则表明这个消息队列退出了.
                return;
            }
            //分发该消息,traget是handelrde 的意思,这里理解成指明谁来处理分发后的消息
            msg.target.dispatchMessage(msg);
            ......
    }
    可以看到,loop方法主要是从消息队列中不断的取出消息,并将该消息分发出去。
    最后一个方法是getMainLooper()方法,该方法的作用是可以返回主线程的looper,即可以将handler指定为主线程,简单的应用示例为:Handler handler =     new    Handler(Looper.getMainLooper());这样就可以在任意位置将其指定为主线程了,那么getMainLooper()方法是如何实现的呢?直接上源代码:
 public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
   只是简单的返回了一个sMainLooper,再来看sMainLooper是在哪里赋值的,直接上源代码:
 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()有这样一段注释:The main looper for your application is created by the Android environment, so you should never need to call this function yourself,意思就是说应用程序的主Looper是由android环境创建的,因此你永远都不要去调用这个函数。
  至此就明白了,在android环境初始化的时候,入口函数是ActivitThread.java 的main() 函数,在main() 函数中调用了Looper的静态方法prepareMainLooper(),将sMainLooper赋值,我们只需要调用getMainLooper()就能返回它了,也就是获得主线程的环境
总结一下,Looper的工作:
   。封装了一个消息队列,
   。利用prepare将Looper和调用prepare方法的线程联系起来
   。利用loop函数分发消息
    接下来继续填坑,handler到底是什么鬼?
    首先来看看它的构造方法,
public Handler(Callback callback, boolean async) {
        ......
        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;
    }
        无参数的构造方法调用了this(null, flase)构造方法,可以看到,在在Handler的成员变量中有一个Looper,首先获取了当前创建Handler的线程的Looper,另外可以看到在Handler中也保存了一个消息队列最终指向了Looper的消息队列。当我们调用了sendMessage方法之后就向Looper发送了一条消息,让我们看看这个方法,消息是如何被传递的。sendMessage方法最终会调用到sendMessageAtTime方法来:
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);
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    可以看到,sendMessage方法最终调用了queue.enqueueMessage方法将消息加入到了Looper中的消息队列。如何加入到消息队列的呢?再来看queue.enqueueMessage方法的源代码:
  boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
   可以看到实际上就是单链表的插入操作,而插入的顺序是根据时间when来排序的,将时间小的排在前面,时间大的排在后面,而Message中出现的target变量又是什么呢?查源代码可知其实target就是Handler,通过 msg.target = this;就将消息队列又与当前handler绑定起来了。这里已经看到了调用sendMessage是如何实现在消息队列中插入消息的,接下来再来看看如何分发消息的,上dispatchMessage源代码(msg.target.dispatchMessage(msg)):
 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
   可以看到,dispatchMessage设置了一套消息处理的优先机制:
   如果Message自带了Callback,则交给Message的Callback处理;
   如果Handler了设置了Callback,则交给Handler的Callback处理;
   如果两者都没有,则调用handleMessage方法处理。
   因此回过头来看我们在示例代码中new handler的时候是默认调用了其无参数的构造函数的,this(null, flase),这个对应的构造函数是:
public Handler(Callback callback, boolean async) {
        ......
        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中的Callback为null,类似的,Message的也为空,因此最终的消息处理函数为我们重写的handleMessage。(async表示对异步消息进行标记)
   到此,来总结一下Looper与handler运作的整个过程,
  1.首先是调用Looper.prepare()方法来创建一个新的队列和获取到当前的线程,
  2.然后我们创建handler并重写其handleMessage方法,在我们构建handler的时候会获取到当前的looper对象(通过该looper对象来获取消息队列),消息队列和Callback对象(当然,在上述代码中Callback对象为null),紧接着我们在主线程里开的新线程中调用sendMessage方法来向handler中的消息队列来传递消息(当然,查看源代码可以知道其最终调用的是sendMessageAtTime方法,并通过返回enqueueMessage方法来讲消息加入到消息队列中),我们重写的handleMessage方法就是用来拦截我们感兴趣的消息并对消息进行处理的方法
  3.最后是调用Looper.loop()来实现消息的循环分发,其通过queue.next()来不断获取消息队列中的最新消息并且调用 msg.target.dispatchMessage(msg)方法来实现消息的分发
  4.最后是dispatchMessage方法实现消息的分发,在该方法中进行了一系列判断:
  如果Message自带了Callback,则交给Message的Callback处理;
  如果Handler了设置了Callback,则交给Handler的Callback处理;
  如果两者都没有,则调用handleMessage方法处理(,父类中该方法为空,默认是不处理的)。
  那么最后,来解决我们最开始的两个问题,
  第一,如果说Looper是使用handler必须的话,那么在activity中,或者更通俗的说,在主线程中,为何不人为设置Looper也不会报错?
  实际上这个问题已经解决了,回去文章最前面看看main函数的源代码,大家应该就懂了
  第二,那个Post方法又是怎么回事?子线程没法更新ui啊?
  直接来看源代码:
public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);//getPostMessage方法是两种发送消息的不同之处
    }
    方法只有一句,内部实现和普通的sendMessage是一样的,但是只有一点不同,那就是 getPostMessage(r) 这个方法:
private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    看到这里,我们只是知道了post和sendMessage原理都是封装成Message,   而且看到那两个方法到最终都调用了sendMessageAtTime(其实其他方法也是如此),sendMessageAtTime方法已经讨论过了,因此不再讨论了。 再往下走,在sendMessageAtTime中所调用的enqueueMessage中有一句关键代码:
msg.target = this;
   target就是message中的handler变量,而this不就是指向了当前调用它的handler了嘛!
   通过 msg.target = this;就将消息队列又与当前handler绑定起来了。 我们是怎么调用的?再来看最开始的示例代码:
 private Handler mHandler;//全局变量
@Override
protected void onCreate(Bundle savedInstanceState) {
    mHandler = new Handler();
    new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);//在子线程有一段耗时操作,比如请求网络
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mTestTV.setText("This is post");//更新UI
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
}
    msg的callback应该已经想到是什么了,就是我们通过Handler.post(Runnable r)传入的Runnable的run方法,对应的是dispatchMessage中的:
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
   这里就要提提Java基础了,直接调用线程的run方法相当于是在一个普通的类调用方法,还是在当前线程执行,并不会开启新的线程。这就是整个消息处理机制,来张图镇楼












   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值