Looper、Handler源码分析

写回调函数的时候突然想到,在执行回调函数之外的时间里进程都在做些什么呢?联想起之前调试源码时总是看到的looper,决定查看相关代码,窥探一下android的消息处理机制。现记录下这两天学习之后的理解。


对于一个自定义线程,典型的looper用法如下:

class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run() {
            Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
  
            Looper.loop();
        }
    }

由于Looper的构造函数为私有函数,因此无法在外部构造looper实例:

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

真正的构造工作是在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.set(new Looper(quitAllowed));
完成了Looper实例的生成,并将生成的实例保存在Thread Local Variable中。

Thread Local Variable,顾名思义,是那些作用在线程范围内的变量。官方SDK API对ThreadLocal的说明如下:

Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values.

查看Thread.java:

/**
     * Normal thread local values.
     */
    ThreadLocal.Values localValues;

    /**
     * Inheritable thread local values.
     */
    ThreadLocal.Values inheritableValues;

可以看到,每个线程都保存了一个ThreadLocal.Values,而在ThreadLocal的set方法中:

public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
首先获取到当前的线程,之后修改线程中localValues的内容,从而实现Thread和Looper的绑定。

因此,如果不调用Looper.prepare()方法,线程是没有Looper对象的。android系统在进程启动时会为主线程创建Looper,因此主线程是有默认的Looper的,详情请参考ActivityThread.java。


在Looper的构造函数中可以看到,MessageQueue也是在此生成,接下来就要让Looper“跑起来”,不停地从MessageQueue中读取消息,并发给Handler。要让Looper跑起来,需要调用Looper.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();
        }
    }

在循环中,Looper调用MessageQueue.next()方法不断获得消息,然后通过msg.target.dispatchMessage(msg)调用Handler的消息处理函数。


Handler的构造函数完成的全部工作就是设置与自身对应的Looper、MessageQueue、 消息处理函数,此外还有异步标识的设置。

Looper的获得是通过Looper.myLooper()方法,这是个静态方法,返回所在线程的Looper对象,因此需要在创建Handler之前调用Looper.prepare()实例化一个Looper。

在主线程中,由于有默认的Looper,因此我们无需调用prepare(),而在自定义线程中,则不能省去这一步。但是,我们也可以指定Handler的Looper,而不是获取其所在线程的Looper,具体的使用场景会在后面讨论。


当Looper.loop()方法被调用,消息循环开启后,一般通过Handler.sendMessage(msg)或Message.sendToTarget()方法进行消息的传送。后者的代码如下:

public void sendToTarget() {
        target.sendMessage(this);
    }

事实上只是对前一种方法的封装,且要求生成的消息包含target信息。

生成消息一般通过new Message(), Message.obtain() 或者Handler.obtainMessage(), 其中只有Handler.obtainMessage()生成的消息带有target信息,我们也可以通过Message.setTarget(Handler target)来额外设置target信息。

Handler.sendMessage(msg)是对一系列函数的封装,最终的功能实现是在enqueueMessage()中,如果用Handler.sendMessage(msg)来发送消息,则无需提前设置消息的target,看一下源码便可知道原因:

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

第一行代码设置了msg的target,而这个方法所做的事情正如其名字表示的那样——向MessageQueue中插入一条消息。


至此,消息处理的大致流程就很清楚了:

首先调用Looper.prepare()为线程设置一个Looper;

之后在线程内创建Handler,默认情况下该Handler会自动获取到刚刚创建的Looper,进而得到该Looper的MessageQueue;

调用Looper.loop()开启消息循环;

生成一个带有target信息的消息,并发送至MessageQueue;

Looper获取MessageQueue中的消息,根据其中的target信息,调用相应Handler的dispatchMessage()方法处理消息。


如果只关注流程的最后两步,也就是Handler发消息、Looper读消息并发送给Handler处理的过程,会发现这里其实“绕”了个弯,起初看到这里感到不解:既然消息已经包含了target信息,为何不直接调用Handler.dispatch()处理消息呢?

事实上,这正是消息处理异步机制的体现。在多线程的场景下,往往是子线程将消息发送到主线程(UI线程)的Handler,子线程只负责消息的发送,而消息的获取与处理则是主线程完成的,两者之间毫无疑问是异步的。如果直接在子线程中调用dispatchMessage(),则意味着消息的生成和处理全在子线程中完成了。并且,更新UI这样的任务是无法完成的。


下面讨论一下多线程情况下,Handler和Looper消息分发-处理的两种模型:

一种模型是,在主线程中创建Handler,并把该Handler传递给子线程,由于主线程的Handler默认保存了主线程的Looper,因此可以在不给子线程分配Looper的前提下直接进行消息的发送:

protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		final Handler handler = new Handler(){

			@Override
			public void handleMessage(Message msg) {
				// TODO Auto-generated method stub
				super.handleMessage(msg);
			}
		};
		
		new Thread(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				handler.obtainMessage().sendToTarget();
			}	
		}.start();
	}
这样写的好处是简单,并且由于一切消息处理都是在主线程中完成的,修改UI元素的工作也得以进行。而弊端也是显而易见的,随着应用场景的复杂、进程类型的增加,同一个Handler的处理函数会越来越臃肿,对消息类型的判断将十分复杂,可读性、可维护性比较差。

为了提高可读性,可以把Handler的创建从主线程转移到子线程中,但仍然使用主线程的Looper:

protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		new Thread(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				super.run();
				
				Handler handler = new Handler(Looper.getMainLooper()){
					@Override
					public void handleMessage(Message msg) {
						// TODO Auto-generated method stub
						super.handleMessage(msg);
					}		
				};
				
				handler.obtainMessage().sendToTarget();
			}	
		}.start();
	}
这种做法的好处是可以在线程内部依据线程功能的不同,编写不同的Handler,便于后期代码的维护。需要注意的是,当涉及对主线程变量的修改时,必须确保这些变量对于子线程是可见的。

上述两种写法的本质都是使用了主线程的Looper,Handler对消息的处理是由主线程调用的,因此都可以进行界面更新。


另一种模型是,子线程创建自己的Looper,主线程为每个子线程都创建一个Handler并与其Looper关联,

public class ExampleThread extends Thread {
	Looper mLooper;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		Looper.prepare();
		mLooper = Looper.myLooper();
		Looper.loop();
	}
	
	public Looper getLooper() {
		return mLooper;
	}
}

protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		ExampleThread t = new ExampleThread();
		t.start();
		
		Handler handler = new Handler(t.getLooper()){
			@Override
			public void handleMessage(Message msg) {
				// TODO Auto-generated method stub
				super.handleMessage(msg);
			}		
		};
	}

上面的代码存在一个严重的问题,那就是线程的同步。主线程创建Handler时,子线程可能还没有完成Looper的创建,从而导致运行时错误,因此需要对子线程的代码稍作修改:

public class ExampleThread extends Thread {
	private Looper mLooper;
	private final Object mLock = new Object();
	
	public ExampleThread() {
		Thread t = new Thread(null, this, "ExampleThread");
		t.start();
		synchronized(mLock){
			try {
				mLock.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized(mLock){
			Looper.prepare();
			mLooper = Looper.myLooper();
			mLock.notifyAll();
		}
		Looper.loop();
	}
	
	public Looper getLooper() {
		return mLooper;
	}
}
在这种模型下,新建的线程在常规时间里只是在运行Looper的循环,发送消息的工作需要在主线程的其他地方完成。

当Looper获取到消息时,则由子线程分发给Handler处理,由于不是运行在主线程中,Handler不能完成更新界面的工作。这种模型胜任那些耗时的、可能阻塞主线程的任务,比如后台下载、音乐播放。


总结一下:

1.Looper是通过Thread Local Variables和线程绑定的,主线程由系统分配了Looper,我们自定义的线程需要调用Looper.prepare()创建Looper;

2.Handler创建时默认获取所在线程的Looper,所谓的发送消息只是把消息插入Looper的消息队列中,等待Looper获取到消息后再回传给Handler的处理函数;

3.Handler的消息处理函数到底运行在主线程还是子线程中,不是由创建它的线程决定的,而是由把消息交给Handler处理的Looper所在的线程决定的;

4.在子线程中创建Looper,并在主线程中创建对应的Handler,可以提高线程管理的灵活性,但无法直接修改界面;

5.在子线程中创建Looper更适合用来处理耗时的、阻塞主线程的任务;对于需要更新界面的任务,可以考虑在Handler中使用Activity.runOnUiThread()方法。















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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值