热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android源码解析Handler处理机制(一)

我们都知道Android的主线程是不安全的,如果在非UI线程中更新UI,那么很有可能报错!那么比较常用的方法是,在子线程中使

     我们都知道Android的主线程是不安全的,如果在非UI线程中更新UI,那么很有可能报错!那么比较常用的方法是,在子线程中使用Handler发送消息,然后在主线程中接受到该消息,接着再更新UI。 在分析Handler处理机制之前,首先认识一些在消息处理中经常出现的名词。

一、Message。

    Message:消息。定义一个message包含描述信息和任意的数据对象发送给Handler。这个对象包含两个额外的int类型的属性和一个Object类型的属性,它可以让你不需要去做一些强制类型的转换的操作。

(1).arg1 和 arg2 都是Message自带的用来传递一些轻量级存储int类型的数据。如果只需要存储一些整型值,arg1 和 arg2是用setData()的低成本替代品;
(2).obj 是Message自带的Object类型对象,发送给接收者的任意对象。当使用Message对象在线程间传递消息时,如果它包含一个Parcelable的结构类(不是由应用程序实现的类),此字段必须为非空(non-null)。其他的数据传输则使用setData(Bundle)方法;
(3).replyTo 指明此message发送到何处的可选Messenger对象。具体的使用方法由发送者和接受者决定;
(4).what 用户自定义的消息代码,这样接受者可以确定这个消息的信息。每个handler各自包含自己的消息代码,所以不用担心自定义的消息跟其他handlers有冲突;
PS: 尽管Message的构造器是公开的,但是获取Message对象的最好方法是调用Message.obtain()或者Handler.obtainMessage(), 这样是从一个可回收对象池中获取Message对象;

(5).getCallback(),获取回调对象,此对象会在message处理时执行。此对象必须实现Runnable接口。回调由接收此消息并分发的目标handler调用。如果没有设置回调,此消息会分发到接收handler的;

(6).getData(),获取附加在此事件上的任意数据的Bundle对象,需要时延迟创建。通过调用setData(Bundle)来设置Bundle的值。需要注意的是,如果通过Messenger对象在进程间传递数据时,需要调用Bundle类的Bundle.setClassLoader()方法来设置ClassLoader,这样当接收到消息时可以实例化Bundle里的对象;

(7).getTarget(),获取将接收此消息的Handler对象。此对象必须要实现Handler.handleMessage()方法。每个handler各自包含自己的消息代码,所以不用担心自定义的消息跟其他handlers相冲突;
(8).getWhen(),返回此消息的传输时间,以毫秒为单位;
(9).obtain(),从全局池中返回的一个新的Message对象。避免在许多情况下,分配新的对象;
(10).recycle(), 向全局池中返回一个Message实例。一定不能在调用此函数后再使用Message---它会立即被释放;
(11).setData(Bundle data), 设置一个任意数据值的Bundle对象。如果可以,使用arg1和arg2域发送一些整型值以减少消耗。

二、MessageQueue。

    MessageQueue:消息队列,它的内部存储了一组消息,以队列的形势对外提供插入和删除的工作。虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。每一个线程最多只有一个消息队列。

    MessageQueue是比较低层的类,是持有Message(在Looper中派发)的队列,但Message不是直接添加到MessageQueue中的,而是通过与Looper相关联的Handler来进行的。大多数情况下,你不需要显式的new它,当你setup一个Looper时,MessageQueue会被自动创建

(1).mQuitAllowed表示MessageQueue是否允许退出,系统创建的UI线程的MessageQueue是不允许的,其他客户端代码创建的都是允许的;
(2).mPtr是native代码相关的,指向C/C++代码中的某些对象(指针);
(3).mMessages表示消息队列的头Head;
(4).mIdleHandlers是IdldHandler接口的ArrayList, mPendingIdleHandlers是数组版本,在后面的代码中会将ArrayList的内容拷贝到它里面;
(5).mQuitting表示当前队列是否处于正在退出状态;

三、Looper。

   Looper, 消息循环,创建消息队列,无限循环从该队列中读取消息。

   Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

public class LooperThread extends Thread {@Overridepublic void run() {// 将当前线程初始化为Looper线程Looper.prepare();// ...其他处理,如实例化handler// 开始循环处理消息队列Looper.loop();}
}

(1).Looper.prepare(),

public class Looper {// 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象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());}// ...
}

Looper对象,它的内部维护了一个消息队列,prepare()方法比较简单,创建了一个“Looper”对象,并且将该Looper对象设置给ThreadLocal(线程本地变量)对象。

(2).Looper.loop(),

public static void loop() {final Looper me &#61; myLooper();//得到当前线程Looperif (me &#61;&#61; null) {throw new RuntimeException("No Looper; Looper.prepare() wasn&#39;t called on this thread.");}final MessageQueue queue &#61; me.mQueue;//得到当前looper的消息队列// 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 &#61; Binder.clearCallingIdentity();for (;;) {Message msg &#61; queue.next(); //取出messageif (msg &#61;&#61; null) {//message等于null&#xff0c;则退出循环return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging &#61; me.mLogging;if (logging !&#61; null) {logging.println(">>>>> Dispatching to " &#43; msg.target &#43; " " &#43;msg.callback &#43; ": " &#43; msg.what);}final long traceTag &#61; me.mTraceTag;if (traceTag !&#61; 0) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}try {msg.target.dispatchMessage(msg);//真正的处理消息的地方} finally {if (traceTag !&#61; 0) {Trace.traceEnd(traceTag);}}if (logging !&#61; null) {logging.println("<<<<loop()方法&#xff0c;是Looper对象不断的从消息队列中取出消息。

(3).Looper.myLooper()&#xff0c;

public static final Looper myLooper() {// 返回当前线程的looperreturn (Looper)sThreadLocal.get();}

四、ThreadLocal。&#xff08;摘录自-Android开发艺术探索&#xff09;

    ThreadLocal并不是线程&#xff0c;它的作用是可以在每个线程中存储数据。大家知道&#xff0c;Handler创建的时候会采用当前线程的Looper来构造消息循环系统&#xff0c;那么Handler内部如何获取到当前线程的Looper呢&#xff1f;这就要使用ThreadLocal了&#xff0c;ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据&#xff0c;通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是&#xff0c;线程是默认没有Looper的&#xff0c;如果需要使用Handler就必须为线程创建Looper。大家经常提到的主线程&#xff0c;也叫UI线程&#xff0c;它就是ActivityThread&#xff0c;ActivityThread被创建时就会初始化Looper&#xff0c;这也是在主线程中默认可以使用Handler的原因。

  ThreadLocal是一个线程内部的数据存储类&#xff0c;通过它可以在指定的线程中存储数据&#xff0c;数据存储以后&#xff0c;只有在指定线程中可以获取到存储的数据&#xff0c;对于其它线程来说无法获取到数据。在日常开发中用到ThreadLocal的地方较少&#xff0c;但是在某些特殊的场景下&#xff0c;通过ThreadLocal可以轻松地实现一些看起来很复杂的功能&#xff0c;这一点在Android的源码中也有所体现&#xff0c;比如Looper、ActivityThread以及AMS中都用到了ThreadLocal。具体到ThreadLocal的使用场景&#xff0c;这个不好统一地来描述&#xff0c;一般来说&#xff0c;当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候&#xff0c;就可以考虑采用ThreadLocal。比如对于Handler来说&#xff0c;它需要获取当前线程的Looper&#xff0c;很显然Looper的作用域就是线程并且不同线程具有不同的Looper&#xff0c;这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取&#xff0c;如果不采用ThreadLocal&#xff0c;那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper&#xff0c;这样一来就必须提供一个类似于LooperManager的类了&#xff0c;但是系统并没有这么做而是选择了ThreadLocal&#xff0c;这就是ThreadLocal的好处。

五、ActivityThread。

      当应用程序启动时&#xff0c;系统会为其创建一个进程&#xff0c;也会创建一个线程名字叫做main&#xff0c;所有其所属组件的创建&#xff0c;系统事件的处理&#xff0c;系统的回调等一切应用相关的事情都运行在此名叫main的线程中。此线程即为常说的主线程&#xff08;main thread)。俗称的UI线程&#xff08;UI thread&#xff09;也是它&#xff0c;因为只有主线程可以操作UI相关的事情&#xff0c;所以有人把主线程也称作UI线程&#xff0c;但这并不是正确的说法&#xff0c;因为Service所属的线程也可以操作Toast&#xff0c;但是Service并没有UI。为什么非主线程不能操作UI呢&#xff1f;因为对UI操作常常会引发系统的回调&#xff0c;所以如果允许第三线程来操作可能会引发系统回调的紊乱&#xff0c;进而会打乱整个框架的时序&#xff01;而ActivityThread&#xff0c; Android应用程序的核心类。

      ActivityThread是运行在主线程中的核心类&#xff0c;记住ActivityThread不是线程&#xff0c;它仅仅是运行在主线程的一个类。当运行该类时&#xff0c;首先会调用它的main(String[] args)方法&#xff0c;main()方法是应用程序的入口函数&#xff0c;

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");SamplingProfilerIntegration.start();// CloseGuard defaults to true and can be quite spammy. We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);// 初始化应用中需要使用的系统路径Environment.initForCurrentUser();// Set the reporter for event logging in libcoreEventLogger.setReporter(new EventLoggingReporter());// Make sure TrustedCertificateStore looks in the right place for CA certificates//为应用设置当前用户的CA证书保存的位置final File configDir &#61; Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);//设置进程的名称Process.setArgV0("");Looper.prepareMainLooper();//创建消息循环//创建ActivityThread 对象ActivityThread thread &#61; new ActivityThread();thread.attach(false);if (sMainThreadHandler &#61;&#61; null) {sMainThreadHandler &#61; thread.getHandler();//主线程的Handler}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();//执行消息循环throw new RuntimeException("Main thread loop unexpectedly exited");}

ActivityThread类有6000多行&#xff0c;此处就不详说了&#xff01;(我也没看太懂)偷笑如果大家感兴趣&#xff0c;请自行去查看该类的其他源码。

六、Handler。

       Handler&#xff0c;消息的处理者&#xff0c;将Message放入MessageQueue中&#xff0c;以及处理接受到的消息。

public class Handler {private static final boolean FIND_POTENTIAL_LEAKS &#61; false;private static final String TAG &#61; "Handler";public interface Callback {public boolean handleMessage(Message msg);}public void handleMessage(Message msg) {}public void dispatchMessage(Message msg) {if (msg.callback !&#61; null) {handleCallback(msg);} else {if (mCallback !&#61; null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}public Handler() {this(null, false);}public Handler(Callback callback) {this(callback, false);}public Handler(Looper looper) {this(looper, null, false);}public Handler(Looper looper, Callback callback) {this(looper, callback, false);}public Handler(boolean async) {this(null, async);}public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class klass &#61; getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) &#61;&#61; 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " &#43;klass.getCanonicalName());}}mLooper &#61; Looper.myLooper();//获取当前的looper对象if (mLooper &#61;&#61; null) {throw new RuntimeException("Can&#39;t create handler inside thread that has not called Looper.prepare()");}mQueue &#61; mLooper.mQueue;//获取当前的消息队列mCallback &#61; callback;//设置回调方法mAsynchronous &#61; async;}...final Looper mLooper;//Looper对象final MessageQueue mQueue;//消息队列final Callback mCallback;// 回调接口...}handler创建时会关联一个looper&#xff0c;如果looper等于null&#xff0c;则会抛异常。

Handler中分发消息的一些方法&#xff1a;
post(Runnable)
postAtTime(Runnable&#xff0c;long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message&#xff0c;long)
sendMessageDelayed(Message&#xff0c;long)

Handler拥有下面两个重要的特点&#xff1a;
1.handler可以在任意线程发送消息&#xff0c;这些消息会被添加到关联的消息队列上。


2.handler是在它关联的looper线程中处理消息的。


PS:

1 .Android 为什么要设计只能通过 Handler 机制更新 UI 呢?
最根本的目的就是解决多线程并发的问题&#xff0c;假设在一个 Activity 当中&#xff0c;有多个线程去更新 UI&#xff0c;并且对更新的 UI 的操作进行枷锁处理的话又会产生什么样的问题呢? 那就是性能下降&#xff0c;Handler 通过消息队列&#xff0c;保证了消息处理的先后有序。鉴于以上问题的考虑&#xff0c;Android 给我们提供了一套更新 UI 的机制&#xff0c;我们只要使用一套机制就好&#xff0c;所有的更新 UI 的操作都是在主线程中轮询处理。

2.Handler 与 Looper、MessageQueue 的关系。
handler 负责发送消息&#xff0c;Looper 负责接收 Handler 发送消息&#xff0c;并直接把消息回传给 handler 自己&#xff0c;MessageQueue 就是一个存储消息的容器。

3.如果想在子线程中创建一个位于主线程的Handler&#xff0c;该怎么做呢&#xff1f;

其实也很简单&#xff0c;在创建Handler时传入主线程的looper对象即可&#xff0c; Handler mHandler&#61;new Handler( Looper.getMainLooper());

本篇文章只罗列了消息机制中常用的几个概念&#xff0c;相信大家阅读完后&#xff0c;对这几个概念的理解会更深一步。下篇文章&#xff0c;我们会从源码的角度去深入分析Handler处理机制。详情请看&#xff0c; Android 源码解析Handler处理机制&#xff08;二&#xff09;。

总结&#xff1a;

      1. 一个Thread只能有一个Looper对象&#xff1b;

      2.每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构&#xff1b;

      3.一个线程可以有多个Handler&#xff0c;但是只能有一个Looper&#xff1b;

      4.主线程即UI线程是有消息循环的&#xff0c;子线程默认是没有消息循环的&#xff1b;

      5.消息循环的本质是&#xff0c;一个线程开启循环模式持续监听并依次处理其他线程给它发的消息&#xff1b;






推荐阅读
author-avatar
rachel_wxh_614
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有