为了更好地理解Handler的工作原理,说下与Handler一起工作的几个组件。
1. MessageQueue:Handler接收和处理的消息对象。 2.Looper:每个线程只能拥有一个Looper。它的loop方法负责读取MessageQueue中的消息,读到信息 之后就把消息交给发送该消息的Handler进行处理。 3.MessageQueue消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时,会在它 的构造器中创建MesssQueue对象。 //贴份Looper的构造器源代码: private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }该构造方法使用了private修饰,不是public表明是不可以通过构造器去创建Looper对象。 从构造方法中不难看出,程序在初始化Looper时会创建一个与之关联的MessageQueue,而这个MessageQueue就负责管理消息。
Handler有两个要点,前篇有说过——发送消息和处理消息,程序使用Handler发送消息,由Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果希望Handler正常工作,必须在当前线程有一个MessageQueue;否则消息就没有MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前线程中有一个Looper对象,为了保证当前线程中有Looper对象,可分为以下两种情况处理:
1.在主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就 可通过Handler来发送消息、处理消息。 2.你自己启动的子线程,必须自己创建一个Looper对象,并启动它,创建Looper对象并调用它的prepare()方法保证每个线程最多只有一个Looper对象。
//贴份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)); }接下来调用Looper的静态loop()方法来启动它。loop()方法使用一个死循环不断取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler进行处理。
//贴份Looper类中的loop()方法,部分关键的源代码 for (;;) { // might block //获取消息队列中的下一个消息,如果没有消息,就会阻塞 Message msg = queue.next(); if (msg == null) { // No message indicates that the message queue is quitting. //如果消息为null,表明消息队列正在退出. 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修饰该标识符,保证在分发消息的过程中线程标识符不会被修改 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.recycleUnchecked(); }组织一下,Looper、MessageQueue、Handler各自的作用
Looper:每个线程只有一个Looper,它负责管理MessageQueue,会不断地从MessageQueue中取出消息, 并将消息分给对应的Handler处理。 MessageQueue:由Looper负责管理。它采用先进先出的方式来管理Message。 Handler:它能把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。在线程中使用Handler的步骤
1.调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器 会创建与之配套的MessageQueue。 2.有了Looper之后,创建Handler子类的实例,重写handlerMessage()方法,该方法负责处理来自于其他线程的消息。 3.调用Looper的loop()方法启动Looper。简述了它们之间的作用,不上个实例感觉缺少点啥
该实例允许用户输入一个数值上限,当用户点击按钮时,该应用会将该上限数值发送到新启动的线程中,让该线程来计算该范围内的所有质数。
为了将用户在UI界面输入的数值上限动态地传给新启动的线程,此实例将会在线程中创建一个Handler对象,然后UI线程的事件处理方法就可以通过该Handler向新线程发送消息。
上面隔开的关键代码,在新线程内创建了一个Handler,由于在新线程中创建Handler时必须先创建Looper,因此程序先调用Looper的prepare()方法为当前线程创建了一个Looper实例,并创建了配套的MessageQueue。新线程有了Looper对象之后,接下创建了一个Handler对象,该Handler可以处理其他线程发送过来的消息。程序最后还调用了Looper的loop()方法。
运行此Demo,无论用户输入多大的数值,计算该范围内的质数都将会交给新线程来完成,而前台UI线程不会受到影响。
尽量避免在UI线程中执行耗时操作,这样可能就会导致出一个 著名的ANR( Application Not Responding).
只要在UI线程中执行需要消耗大量时间的操作,都会引发ANR,引发的问题就会导致Anroid应用程序无法响应输入事件和Broadcast。