Handle的原理代码实现

  本文从源码的角度来分析Handle如何实现的。

  首先我们得知道Handler,Looper,Message Queue三者之间的关系

  - Handler封装了消息的发送,也负责接收消。内部会跟Looper关联。

  - Looper 消息封装的载,内部包含了MessageQueue,负责从MessageQueue取出消息,然后交给Handler处理

  - MessageQueue 就是一个消息队列,负责存储消息,有消息过来就存储起来,Looper会循环的从MessageQueue读取消息。(西安尚学堂)软件开发

  源码分析

  当我们new一个Handler对象的时候,看看他的构造方法里面做了什么.

  

public Handler(Callback callback, boolean async) {

  if (FIND_POTENTIAL_LEAKS) {

  final Class 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; }

  从源码中我们看到他会调用Looper.myLooper方法获取一个Looper对象,然后从Looper对象获取到MessageQueue对象。

  Looper myLooper()

  跟进去看看Looper.myLooper()方法做了什么。这是一个静态方法,可以类名.方法名直接调用。

  

public static @Nullable Looper myLooper() {

  return sThreadLocal.get();

  }

  这个方法里面就一行代码,从sThreadLocal中获取一个Looper对象,sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。底层是ThreadLocalMap,既然是Map类型那肯定得先set一个Looper对象,然后我们才能从sThreadLocal对象里面get一个Looper对象。

  ActivityThread main()

  ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。那么这个main函数干了什么事呢。

  

public static final void main(String[] args) {

  ------

  Looper.prepareMainLooper();

  if (sMainThreadHandler == null) {

  sMainThreadHandler = new Handler();

  }

  ActivityThread thread = new ActivityThread();

  thread.attach(false);

  if (false) {

  Looper.myLooper().setMessageLogging(new

  LogPrinter(Log.DEBUG, "ActivityThread"));

  }

  Looper.loop();

  -----

  }

  在第二行代码调用Looper.prepareMainLooper()方法,第13行调用了Looper.loop()方法。

  Looper prepareMainLooper()

  继续跟进Looper.prepareMainLooper()方法,在这个方法中第一行代码调用了内部的prepare方法。prepareMainLooper有点像单例模式中的getInstance方法,只不过getInstance会当时返回一个对象,而prepareMainLooper会新建一个Looper对象,存储在sThreadLocal中。

  

public static void prepareMainLooper() {

  prepare(false);

  synchronized (Looper.class) {

  if (sMainLooper != null) {

  throw new IllegalStateException("The main Looper has already been prepared.");

  }

  sMainLooper = myLooper();

  }

  }

  Looper prepare()

  继续跟进prepare方法,看第5行代码,新建了一个Looper对象,调用sThreadLocal.set方法把Looper对象保存起来。

  

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 构造方法

  Looper内部包含了MessageQueue,其实就是在new Looper对象的时候就new了一个MessageQueue对象。

 

 private Looper(boolean quitAllowed) {

  mQueue = new MessageQueue(quitAllowed);

  mThread = Thread.currentThread();

  }

  Looper loop()

  ActivityThread类main方法中调用了Looper的两个方法,前面我们解释了prepareMainLooper(),现在来看第二个方法loop()。

  

public static void loop() {

  final Looper me = myLooper();//获取Looper对象

  if (me == null) {

  throw new RuntimeException("No Looper; Looper.prepare() wasn‘t called on this thread.");

  }

  final MessageQueue queue = me.mQueue;//从Looper对象获取MessageQueue对象

  // 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 (;;) {//死循环 一直从MessageQueue中遍历消息

  Message msg = queue.next(); // might block

  if (msg == null) {

  return;

  }

  // This must be in a local variable, in case a UI event sets the logger

  final Printer logging = me.mLogging;

  if (logging != null) {

  logging.println(">>>>> Dispatching to " + msg.target + " " +

  msg.callback + ": " + msg.what);

  }

  final long traceTag = me.mTraceTag;

  if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {

  Trace.traceBegin(traceTag, msg.target.getTraceName(msg));

  }

  try {

  //调用handler的dispatchMessage方法,把消息交给handler处理

  msg.target.dispatchMessage(msg);

  } finally {

  if (traceTag != 0) {

  Trace.traceEnd(traceTag);

  }

  }

  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.recycleUnchecked();

  }

  }

  这个方法的代码比较多。其实就是一个死循环,一直会从MessageQueue中取消息,如果取到了消息就会执行msg.target.dispatchMessage(msg)这行代码,msg.target就是handler,其实就是调用handler的dispatchMessage方法,然后把从MessageQueue中取到的message传入进去。

  Handler dispatchMessage()

  

public void dispatchMessage(Message msg) {

  //如果callback不为空,说明发送消息的时候是post一个Runnable对象

  if (msg.callback != null) {

  handleCallback(msg);

  } else {

  if (mCallback != null) {//这个是用来拦截消息的

  if (mCallback.handleMessage(msg)) {

  return;

  }

  }

  handleMessage(msg);//最终调用我们重写的handleMessage方法

  }

  }

  这个方法对消息做最后处理,如果是post类型调用handleCallback方法处理,如果是sendMessage发送的消息。看我们有没有拦截消息,如果没有最终调用handleMessage方法处理。

  Handler handleCallback()

  看到这里我们知道为什么post一个Runnable对象,run方法执行的代码在主线程了吧,因为底层根本就没有开启线程,就只是调用了run方法而已。

  

private static void handleCallback(Message message) {

  message.callback.run();

  }

  前面我们从创建handler对象开始,以及创建Looper,创建MessageQueue的整个流程,现在来分析下,当我们调用post以及sendMessage方法时,怎么把Message添加到MessageQueue?

  Handler post()

  调用了getPostMessage方法,把Runnable传递进去。

  

public final boolean post(Runnable r)

  {

  return sendMessageDelayed(getPostMessage(r), 0);

  }

  Handler getPostMessage()

  首先调用Message.obtain()方法,取出一个Message对象,这个方法之前有讲过,然后把Runnable对象赋值了Message对象的callback属性。看到这里我们也明白了dispatchMessage方法为什么要先判断callback是否为空了吧。

  

private static Message getPostMessage(Runnable r) {

  Message m = Message.obtain();

  m.callback = r;

  return m;

  }

  Handler enqueueMessage()

  在post方法里面调用了sendMessageDelayed方法,其实最终调用的是enqueueMessage方法,所以我这里就直接看enqueueMessage方法源码。第一行代码就把handler自己赋值给messgae对象的target属性。然后调用MessageQueue的enqueueMessage方法把当前的Messgae添加进去。

  

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

  msg.target = this;

  if (mAsynchronous) {

  msg.setAsynchronous(true);

  }

  return queue.enqueueMessage(msg, uptimeMillis);

  }

  小结

  总结:handler负责发送消息,Looper负责接收Handler发送的消息,并直接把消息回传给Handler自己。MessageQueue就是一个存储消息的容器。

时间: 2024-10-18 09:32:01

Handle的原理代码实现的相关文章

Handle的原理(Looper、Handler、Message三者关系)

"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> Handle的原理(Looper.Handler.Message三者关系) - Ansen - 博客频道 - CSDN.NET Ansen 当我们失去了青春的时候,起码我们还留下了代码...... 目录视图 摘要视图 订阅 [活动]2017 CSDN博客专栏评选 &n

PHP 弹出文件下载 原理 代码

/** * @author default7<[email protected]> * @description 演示PHP弹出下载的原理 * * @param $file_name */ function downFile($file_name) { $file_path = "/tmp/" . $file_name; $buffer = 102400; //一次返回102400个字节 if (!file_exists($file_path)) { echo "

Python盗号原理-代码实现截屏键盘记录远程发送(不要做坏事)

这年头盗号的从出不穷,不是脱裤就是社工钓鱼.今天呢我们就以前面的几篇技术文章做铺垫,来讲一下python盗号的原理(学习用途,请勿用于违法犯罪),知己知彼方能百战不殆嘛. 脱裤我们就不多提了,无非就是过滤不严谨导致的sql注入,或者数据库存储不安全,这个本篇文章暂且不说,当然有感兴趣的,我后面的文章或许也会讲讲. 最简单的,也是技术手段相对较低的盗号方式当属钓鱼了(当然,社工更考验心理),除了钓鱼网站,就是发布某些带有诱惑性的工具,诱导消费者下载,运行后开启后门,或者启用钩子进行键盘记录.本篇文

【转】 VC中TCP实现 异步套接字编程的原理+代码

所谓的异步套接字编程就是  调用了 如下函数   WSAAsyncSelect   设置了 套接字的状态为异步,有关函数我会在下面详细介绍... 异步套接字解决了 套接字编程过程中的堵塞问题 ...... 什么是堵塞?请看下面  你可能有过这样的体会  在  VC编写基于界面的网路程序时候  ,调用 recv recvfrom   accept  等函数的时候 整个程序的主线程进入堵塞状态直到 有连接或者信息的到来.  我们可以利用多线程的方法解决主线程堵塞的问题,但是我们如果一个程序需要多个套

android adb 流程原理代码分析(一)

由于要用到adb的知识,但是对adb啥也不了解,看了下android的代码,adb的源码在system/core/adb下面,然后网上搜下了资料,发现很多大神的源码分析,瞬间信心爆棚,把大神写的博客都浏览了一遍,然后手动运行了下adb命令,顺便跟踪了下过程,发现原来还是很好的理解,源码的各种线程创建,函数回调,对于我这种基础不咋好的,,还是看的晕晕呼呼,现在把我自己的理解给大家分享,有理解错误的还请多多指正. 一般直接上代码,可能看官都走了一大半,我们逆向的看,先看结果,再看过程,最后再看代码.

promise实现原理代码

//Promise实现原理 // 实例方法 // then // catch // new P( 函数 ).then(functon(){}).then(function(){}) // let p = new MyPromise(); // p.resolve(); //不允许的 // 静态方法 // all // race // 属性 // status : pending / resolved / rejected // resolve / reject // 要求, 每个promise对

EasyUI闪屏,EasyUI页面加载提示:原理+代码+效果图

使用EasyUI时,有个经常遇到的问题,页面还没有渲染完成的时候,就展现了. 刚刚开始很混乱,等加载完成后,就好了. 参考这篇文章http://blog.csdn.net/zheng0518/article/details/12287801 搞定了. $.parser.onComplete,这个是在所有组件解析完成后执行的事件.其实这个事件很有用的.很多在布局用到easyui的时候总会出现一个问题.就是在一进入主界面的时候,页面的并不是马上就展现,而是会有一个混乱的过程,之后一闪就又好了. 其实

sock以及select原理代码测试

#!/usr/bin/python # -*- coding: UTF-8 -*- # IO多路复用 可以监听多个 文件描述符(socket对象)文件句柄 一旦变化即可感知 import socket sk1 = socket.socket() sk1.bind(("127.0.0.1",8090))# 元组 sk1.listen() # sk1 = socket.socket() # sk1.bind(("127.0.0.1",8083))# 元组 # sk1.l

无缝向上滚动原理代码

<script> $(document).ready(function () { var ll = 0;//向上移动的距离 var rolling = setInterval(function () { star() }, 1000); var leng = $('.rolling').children().length;//获取滚动的个数 var hei = leng * 30 + 30;//30是滚动的距离 $(".rolling-li:last-child").aft