• Android消息机制


    每一个Android应用在启动的时候都会创建一个线程,这个线程被称为主线程或者UI线程,Android应用的所有操作默认都会运行在这个线程中。
    但是当我们想要进行数据请求,图片下载,或者其他耗时操作时,是不可能在这个UI线程做的,因为Android在3.0以后的版本已经禁止了这件事情,直接抛出一个异常。所以我们需要一个子线程来处理那些除UI操作的事情。
    但是这个又会有一个问题,我们只能在UI线程进程UI操作,只能在子线程进行耗时操作,如果我们需要在耗时操作结束后在Android界面上显示一个View,我们应该怎么做?我们是不可能在子线程直接刷新UI的。这时我们需要用到Android的消息机制,来实现主线程和子线程的通信。简单来说,就是子线程获取到数据之后,不直接进行UI更新,而是把数据装到消息中发送到主线程,主线程中有一个循环轮询会立即收到子线程发过来的信息,然后拿到消息数据后在主线程更新UI 。说起来比较简单,我们来仔细的看一下具体是怎么说的。

    处理消息的手段——Handler, Looper, MessageQueue

    Handler

    我们先讲解一下Handler,Handler顾名思义就是处理者,通常对他的用法是在UI线程中新建一个Handler,并覆写他的handleMessage, 然后再耗时的线程中将消息postUI线程,例子如下:

    class MyHandler extends Handler{
    	@Override
    	public void handleMessage(Message msg){
    	//更新UI
        }
    }
    
    MyHandler mHandler = new MyHandler();
    new Thread(){
    	public void run(){
    		mHandler.sendEmptyMessage(123);
        };
    }.start();
    

    这里规定了Handler必须在主线程创建,因为只有在UI线程创建才会让Handler关联到已有的MessageQueue。而MessageQueue被封装到Looper中,而Looper又通过ThreadLocal封装到一个线程中,最后相当于MessageQueue关联了一个线程。所以简单来说就是Handler将消息投递到一个关联了线程的MessageQueue中,然后Handler在从MessageQueue中取出消息,并且处理它。
    我们看一下Handler的2个常用的方法

    void handleMessage(Message msg) : 处理消息的方法
    final boolean sendMessage(Message msg) : 立即发送消息

    第一个方法 我们通常在UI线程中执行,一般用来刷新UI,至于如果创建了一个非静态内部类产生对内存泄漏,建议参考这篇博客Handler引发的内存泄漏.第二个方法我们通常在子线程中执行,需要一个Handler的实例化对象,通常是由主线程去去传递给子线程。并且需要一个Message对象,指定他的msg.what作为消息的标示,但是如果我们只是用Handler去处理一个消息的时候,选择post方法是个更好的选择,例子如下:

    private Handler mHandler = new Handler();
    
    new Thread(new Runnable() {
        @Override
        public void run() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    //UI操作
                    ...
                }
            });
        }
    }).start();
    

    下面我们接着讨论下消息的循环队列MessageQueue与包装他的Looper循环

    Looper和MessageQueue

    上面提到了在UI线程中创建并实例化Handler对象不需要LooperMessageQueue,因为我们的应用在启动的时候先执行了ActivityThreadMain,在这个方法就是Java语言运行的入口public static void main(String [] args) 在这里面创建了一个MainLooper,创建的过程如下:

    public static void main(string[] args){
        //初始化
    	Looper.prepareMainLooper();
    	ActivityThread thread = new ActivityThread();
    	thread.attach(false);
        if(sMainThreadHandler == null){
    		sMainThreadHandler = thread.getHandler();
        }
        AsyncTask.init();
        //动起来
        Looper.loop();
    }
    

    这里面并没有MessageQueue的出现,我们可以看一看Looper类的源码,来了解在初始化的时候发生了什么有趣的事情。

    public class Looper {
        private static final ThreadLocal sThreadLocal = new ThreadLocal();
        // Looper内的消息队列
        final MessageQueue mQueue;
        // 当前线程
        Thread mThread;
        // 。。。其他属性
        // 每个Looper对象中有它的消息队列,和它所属的线程
        private Looper() {
            mQueue = new MessageQueue();
            mRun = true;
            mThread = Thread.currentThread();
        }
        // 我们调用该方法会在调用线程的TLS中创建Looper对象
        public static final void prepare() {
            if (sThreadLocal.get() != null) {
                // 试图在有Looper的线程中再次创建Looper将抛出异常
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper());
        }
        // 其他方法
    }
    

    我们一行行的看这段代码,首先是实例化一个ThreadLocal对象,这个用来实现Looper循环的本地化存储,关于ThreadLocal可以看这篇文章为什么用ThreadLocal,简而言之就是当多个线程同时访问Looper对象的时候,我们不用synchronized同步机制来处理他,而是为每个线程创建一个自己的Looper副本,A线程改变了A的looper副本,不影响B线程的Looper,从而比较高效的实现线程安全。后面几句依次定义了MessageQueue,并对Looper进行了私有化构造,在prepare方法中将Looper对象设置给了sThreadLocal 这样MessageQueue包装在了Looper对象中,同时通过ThreadLocal使得线程和Looper关联上,从而消息队列与线程关联上,并且不同的线程就不能访问对方的消息队列。
    如下图所示:
    Looper,MessageQueue,Thread三者示意图

    接着就是Looper.loop 循环执行起来,我们看一下,在loop方法里面执行了发生了什么事情

    public static final void loop() {
            Looper me = myLooper();  //得到当前线程Looper
            MessageQueue queue = me.mQueue;  //得到当前looper的MQ
            
            while (true) {
                Message msg = queue.next(); // 取出message
                if (msg != null) {
                    if (msg.target == null) {
                         return;
                    }
                  
                    msg.target.dispatchMessage(msg);
                   
                    msg.recycle();
                }
            }
        }
    

    这是省略版的代码,我们从这里看出无限循环执行,首先从消息队列中不断取出消息,然后不断msg是否为空,msg.target是否为空,不空的话,执行dispatchMessage方法,这个方法是handler的一个方法,由此我们可以看出msg.targethandler的类型,至此,通过Looper.prepareLoop.loop实现了MessageQueue,Looper,Handler三者之间的关联。而HandlerLooper,和MessageQueue关联则是在Handler的默认构造器中,通过Looper.getLooper获取loop对象,从而获取MessageQueue,其源码如下:

    public Handler(){
        //直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
    	mLooper = Looper.myLooper();
    	mQueue = mLooper.mQueue;
    	mCallback = null;
    }
    

    然后我们的流程图可以多些内容,如下所示:
    Handler工作流程

    我们接下来看一下dispatchMessage() 方法,在该方法中实际上只是一个分发方法,如果Runable类型的callback为空,则执行handlerMessage来处理消息,该方法为空,需要覆写。如果不为空,则执行handleCallback。实际上,如果我们用handlepost方法,则就执行了callback,如果用sendMessage,则就执行了handleMessage
    这里无论是post(Runnable callback)还是handlerMessage实际上都是在调用一个方法sendMessageDelayed(Message msg) 只不过handlerMessage是直接接受一个参数,而Runable callback实际上是将这个Runable对象赋给了Message对象的callback成员变量,最后将Message对象插入消息队列里面。最后Looper不断从MessageQueue中读取消息,并且调用Handler的dispatchMessage消息,在根据callback是否为空,来采用不同的方法执行。Android消息机制分析到此结束。

    回到最开始

    我们这次知道了为什么要在主线程中实例化Handler对象才能更新UI刷新,因为只有发送到UI线程的消息,才能被UI线程的handler处理,如果我们要在非UI线程中,实例化Handler,则必须先将线程变成LooperThread,在实例化。也就是说执行如下的代码:

    Loop.prepare();
    hander = new Handler;
    Loop.loop
    

    至于原因相信读完上面的讲解,应该知道。
    现在我们看一下我们最开始的代码,最后脑补一下Handler的工作流程。

    class MyHandler extends Handler{
    	@Override
    	public void handleMessage(Message msg){
    	//更新UI
        }
    }
    
    MyHandler mHandler = new MyHandler();
    new Thread(){
    	public void run(){
    		mHandler.sendEmptyMessage(123);
        };
    }.start();
    

    Handler实例化成mHandler的时候,系统通过Handler默认的构造函数完成了HandlerLooper的关联,并通过Looper关联到了MessageQueue。而主线程的Looper则早在系统启动的时候通过Loop.prepare就已经构造完成了,并与UI线程通过ThreadLocal关联起来,然后在新的线程中执行mHandler.sendEmptyMessage,将Message发送给了MessageQueue,Looper.loop在循环的时候,不断取出message,交给Handler处理,在我们覆写的HandleMessage中,识别出我们发送的消息,将消息处理。当然这里只是一个Empty消息,所以在handleMessage中没有去执行msg.what的判断。

  • 相关阅读:
    [学]寻找mysql高并发解决方案(事务,行锁,事务隔离级别)
    [学]MYSQL中replace into的用法
    Dynamics CRM OData方式进行增删改查时报错的问题
    Get Form type using javascript in CRM 2011
    Dynamics CRM 同一实体多个Form显示不同的Ribbon按钮
    java中throw与throws
    Dynamics CRM 通过OData查询数据URI中包含中文的情况
    我是怎样阅读技术论文的
    Dynamics CRM2013/2015 禁止欢迎界面(Disable the Welcome Screen)
    社会化网络分析
  • 原文地址:https://www.cnblogs.com/qifengshi/p/5351454.html
Copyright © 2020-2023  润新知