从源码角度理解Android的通信机制——Handler消息机制

一、概述

在安卓开发里面,当子线程在执行耗时操作的时候,不是说你的主线程就阻塞在那里等待子线程的完成,也不是调用Thread.wait()或是Thread.sleep()。安卓采取的方法是,主线程为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的ANR对话框。
一个程序的运行,就是一个进程的在执行,一个进程里面可以拥有很多个线程。Android里面,线程分两种:

主线程:也叫UI线程,或称ActivityThread,或者MainThread,用于运行四大组件和处理他们用户的交互。 ActivityThread类管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的主线程负责执行。

ActivityThread既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。

子线程: 用于执行耗时操作,比如 I/O操作和网络请求等。更新UI的工作必须交给主线程,在安卓里子线程是不允许更新UI的。

二、几个基本概念:

1、消息机制:
就是不同线程之间的通信机制或规则。
2、安卓的消息机制:

简单的讲就是就是 Handler的运行机制。

3、Handler 运行机制的作用:

有效避免ANR的发生,一旦发生ANR,程序就Crash了。

4、触发ANR的条件:

a、在activity中超过5秒的时间未能响应下一个事件;b、BroadcastReceive超过10s未响应。以上a和b两个条件任一个出现都会引发ANR。而造成a、b两点的原因有很多,比如网络请求, 大文件的操作, 复杂的耗时计算等。
如何避免ANR:A、主线程不能执行耗时操作;B、子线程不直接更新UI界面(UI界面更新方式可参阅下问《Android开发中更新UI的几种常用方式》http://blog.csdn.net/haoyuegongzi/article/details/78406342)。

5、Handler的四角生死恋:
1)、Message:

用于存放消息的对象,消息发送的载体。

2)、MessageQueue:

消息队列(单表链的方式实现),用来存放通过Handler发布的消息,管理Message,遵循先进先出的原则。

3)、Handler:

负责处理消息,将Message添加到消息队列以及对消息队列中的Message进行处理。

4)、Looper:

一个死循环,扮演MessageQueue和Handler之间纽带的角色,循环取出MessageQueue里面的Message,并交付给相应的Handler进行处理。
简而言之,Handler消息机制主要就是就是Handler、Looper、MessageQueue、Message四人帮一台戏。

三、Handler的工作机制概要:

1、Handler通过其对象调用sendxxx方法插入一条信息到MessageQueue;
2、Looper不断轮询调用MeaasgaQueue的next方法;
3、如果发现message就调用handler的dispatchMessage,ldispatchMessage被成功调用,接着调用handlerMessage()。大致过程见下图。

这里写图片描述

四、Handler的工作机制详解:

1、Looper对象的创建

在应用App启动的时候,会在执行程序的入口ActivityThread.class类中主函数public static void main(String[] args)里面会创建一个Looper对象:Looper.prepareMainLooper(),然后Looper.loop();完成Looper对象的创建(下面红色部分代码)。实际上.prepareMainLooper()方法还是调用了Looper的prepare()方法完成Looper对象的创建。因此在主线程中通过关键字new创建的Handler对象之前,Looper对象已经存在并始终存在。

public static void main(String[] args) {
	SamplingProfilerIntegration.start();    
	CloseGuard.setEnabled(false);
	Environment.initForCurrentUser();    
	EventLogger.setReporter(new EventLoggingReporter());
	Security.addProvider(new AndroidKeyStoreProvider());    
	Process.setArgV0("<pre-initialized>");
	Looper.prepareMainLooper();          
	ActivityThread thread = new ActivityThread();
	thread.attach(false);
	if (sMainThreadHandler == null) {	
		sMainThreadHandler = thread.getHandler();       
	}
	AsyncTask.init();
	if (false) {  
		Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread")); 
	}
	Looper.loop();      
	throw new RuntimeException("Main thread loop unexpectedly exited");
}

这里要注意一点就是:在子线程中通过关键字new创建的Handler对象时,Looper对象是没有被创建的,直接使用的话,会报异常。如果要在子线程中创建Handler对象,那么就需要在创建前添加代码:Looper.prepare();来解决问题。

new Thread(new Runnable() {  
	@Override  
	public void run() {  
		Looper.prepare();      
	}
}).start();

原因:
在我们new一个Handler的时候,调用了Handler的构造方法,其构造方法主要如下:

public Handler() {
	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;
}

可以看到在if语句判断mLooper是否为空前,调用了mLooper = Looper.myLooper();来获取mLooper对象,如果Looper对象为空,则会抛出一个运行时异常。
那什么时候Looper对象才可能为空呢?我们来看看判断mLooper是否为空的if语句的前一段代码myLooper的源码:

public static final Looper myLooper() {
	return (Looper)sThreadLocal.get();    
}

这里通过sThreadLocal的get方法获取了一个不为空的mLooper对象。提到get方法,自然就会想到set方法,并给sThreadLocal设置Looper对象,通过前面在子线程中创建Handler对象可知,Looper.prepare()方法是给sThreadLocal设置Looper对象的根本所在。Prepare()如下:

public static final void prepare() {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper());
}

可以看到,首先判断sThreadLocal中Looper对象是否为空,如果为空new一个新的Looper并设置进去。这样也就完全解释了为什么在子线程中我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。
Looper的作用:通过一个while(true)de 死循环,不断轮询MessageQueue,有新的消息就交给Handler处理。而MessageQueue对象是在其构造方法中创建的:

private Looper(boolean quitAllowed) {
	mQueue = new MessageQueue(quitAllowed);    
	mThread = Thread.currentThread();
}
2、Handler发送消息
使用Handler发送消息的流程想必不多说了。直接上代码:

new Thread(new Runnable() {
	@Override
	public void run() {
		Message message = new Message();		
		message.arg1 = 1;
		bundle.putString("data", "data");
		message.setData(bundle); 		    
		handler.sendMessage(message);
	}
}).start();

Handler调用sendMessage方法后把Message发送到哪里去了呢?又如何在Handler的handleMessage()方法中重新得到这条Message呢?
Handler的send系列方法:

sendEmptyMessage(int what);
sendEmptyMessageDelayed(int what, long delayMillis);
sendEmptyMessageAtTime(int what, long uptimeMillis);
sendMessage(Message msg);
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
sendMessageAtFrontOfQueue(Message msg);

除了sendMessageAtFrontOfQueue()方法之外,其它发送消息的方法最终都会辗转调用到sendMessageAtTime()方法中。源码如下:

public boolean sendMessageAtTime(Message msg, long uptimeMillis){
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        msg.target = this;        
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }else {
        RuntimeException e = new RuntimeException(" sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }    
    return sent;	
}

sendMessageAtTime()方法接收两个参数, msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。

这里MessageQueue,前面提到过就是消息队列,通过表单链的形式用于保存和管理Meassage对象,并提供入队和出队的方法,遵循先进先出的原则。它的对象是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。

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

enqueueMessage()方法字面翻译过来就是入(en: entrance)队的方法了:

final boolean enqueueMessage(Message msg, long when) {
    if (msg.when != 0) {
        throw new AndroidRuntimeException(msg + " This message is already in use.");
    }
    if (msg.target == null && !mQuitAllowed) {
        throw new RuntimeException("Main thread not allowed to quit");
    }
    synchronized (this) {
        if (mQuiting) {
			String exception = " sending message to a Handler on a dead thread";
            RuntimeException e = new RuntimeException(msg.target + exception);
            Log.w("MessageQueue", e.getMessage(), e);
            return false;
        } else if (msg.target == null) {            
	        mQuiting = true;        
		}
        msg.when = when;        
        Message p = mMessages;
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;            
            mMessages = msg;            
            this.notify();
        } else {
	        Message prev = null;
            while (p != null && p.when <= when) { 
	            prev = p;
	            p = p.next;
	        }
            msg.next = prev.next;            
            prev.next = msg;
            this.notify();
        }
    }    	
    return true;		
}

从源码可以知道,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间就是我们前面介绍的uptimeMillis参数。操作上就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。如果你是通sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。

明白另外入队操作,再看看出队操作。提到出队操作,就要考虑到我们前面说到的四角生死恋中的Looper对象的loop()方法,源码如下:

public static final void loop() {  
	Looper me = myLooper();  
	MessageQueue queue = me.mQueue;  
	while (true) {  
		Message msg = queue.next();
		if (msg != null) {        
			if (msg.target == null) {     
				return;
			}
			msg.target.dispatchMessage(msg);
			msg.recycle();  
		}  
	}  
}  

从源码可以看到,整个while 循环都是一个死循环,不断地调用的MessageQueue的next()方法,也就是前面讲到的消息队列的出队方法。每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中(第7行)。这里的msg.target,其实就是Handler。不信,我们来看看Handler中dispatchMessage()方法的源码:

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

如果msg.callback 不为空,则调用mCallback的handleCallback()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样就可以解释为什么在Handler中复写它的handleMessage()方法就可以获取到之前发送的消息!

为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler通常是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值