一、 在MainActivity中为什么只是类似的写一行如下代码就可以使用handler了呢?

Handler handler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    // handle the nsg message...
  }
}; private void sendMessage() {
   handler.sendEmptyMessage(11);
}

打开handler的源码可以在它的构造函数中,看到如下的几行代码:

mLooper = Looper.myLooper();        //  (1)
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;

如果继续跟进 (1) 号注释中的 Looper.myLooper(); 这条语句,近儿,可以看到下面的代码:

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

其中 sThreadLocal Looper 类中被声明为:

static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>();

可得它是一个静态不可变的键值,因为它是静态成员,由类的加载过程可知在 Looper 被加载后就立即对该对象进行了初始化,而且它被声明为final类型,在接下来的生命周期中将是一个不可改变的引用,这也是称呼它为一个键值一个原因;当然,当你继续跟进时会发现,称呼它为键值原来是有更好的理由的。跟进(2)号注释中的sThreadLocal.get(); 语句,可以得到下面的逻辑:

public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread(); // (3)
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
} return (T) values.getAfterMiss(this); // (4)
}

我们只关注上面的代码中的 注释(3)和 (4)两行代码,其中从注释 (3)处我们得知这里的读取操作是从当前线程的内部获得数据的,而注释(4)则进一步告诉我们,它是一个以 ThreadLocal 类型的对象为键值,也就是Looper中的 static final 访问控制的 sThreadLocal 对象。奇妙的地方就在这里,因为每次调用时 ThreadLocal 虽然都是同一个 sThreadLocal 对象,但在ThreadLocal 内部它是从当前正在活动的线程中取出 Looper 对象的,所以达到了一种不同的线程调用同一个 Looper 中的同一个 sThreadLocal 对象的 get 方法,而得到的 Looper是不一样的目的。并且从注释(1)处可以得知,当我们调用 sThreadLocal.get();如果返回是一个null时,我们的调用将是失败的,也就是在从当前正在活动的线程的内部读取时还没有初始化,抛出的异常提醒我们先执行ooper.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));
}

可得知,它同样是通过sThreadLocal对象来存放数据的,跟进一步:

 public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}

从上面的代码可得知,每个线程至多只能存放一个Looper对象,注意这里强调的是每个线程只能存放一份,并不是sThreadLocal 只能放一个,这也是线程本地化存储的秘密所在。所以我们得到一个前提条件,在调用类似new Handler(){};语句时,必须事先已执行 Looper.prepare(); 语句,而且要确保两条语句都是在同一个线程中被调用的,不然可能得不到我们期望的结果。但是为什么我们在 MainAcitivity 中没有显示调用 Looper.prepare(); 方法,而只是简单调用 new Handler(); 语句呢?原来在 MainActivity运行所在的主线程(也成UI线程,getId()得到的id号是1)被android系统启动时,就主动调用了一次Looper.prepare()方法:

public static final void More ...main(String[] args) {
SamplingProfilerIntegration.start();
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper(); (跟进该方法)
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop();
if (Process.supportsProcesses()) {
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.detach();
String name = (thread.mInitialApplication != null)
? thread.mInitialApplication.getPackageName()
: "<unknown>";
Log.i(TAG, "Main thread of " + name + " is now exiting");
}
}

二 、 Handler中是什么原理,使得发送的消息时只要使用handler对象,而在消息被接受并处理时就可以直接调用到handler中覆写的handleMessge方法?

就从下面的这行代码开始:

private void sendMessage() {
handler.sendEmptyMessage(0x001);
}

在handler类中一直跟进该方法,可以发现下面的几行代码:

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

在上面的注释 (5) 处可以看到,这里对每个msg中的target字段进行了设置,这里就是设置了每个消息将来被处理时用到的handler对象。

可以跟进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); // (6) 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();
}
}

在注释 (6)处可以看到, msg.target.dispatchMessage(msg); 原来是调用的Message的 dispatchMessage()方法,不妨我们跟进去看看:

/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); //(7)
}
}

一系列优先考虑的回调判断之后,终于轮到了自定义的处理的函数处理。

三、遇到This Handler class should be static or leaks might occur 警告 为什么?

大致意思是说:Handler 类应该为static类型,否则有可能造成泄露。在程序消息队列中排队的消息保持了对目标Handler类的引用。如果Handler是个内部类,那 么它也会保持它所在的外部类的引用。为了避免泄露这个外部类,应该将Handler声明为static嵌套类,并且使用对外部类的弱应用。说的很明白,因为内部类对外部类有个引用,所以当我们在内部类中保存对外部类的弱引用时,这里的内部类对外引用还是存在的,这是jdk实现的,而只有我们声明为静态内部类,此时是对外部类没有强引用的(这时它和普通的外部类没有什么区别,只是在访问控制上有很大的方便性,它可以直接访问外部类的私有成员),这样当线程中还有 Looper  和 Looper 的 MessageQueue 还有 Message 时( message 中的 target 保持了对 Handler 的引用),而此时 Activity已经死亡时,就可以对Activity 进行回收了。

四、注意事项

1.  在自己定义的子线程中如果想实现handler?

Looper.prepare();
handler=new Handler();
...
Looper.loop();

这几行代码一定要在run方法中调用,而不是在自定义的 Thread 子类的构造函数中调用。因为子线程的构造方法被调用时,其实子线程还没有执行,也就是说当前的线程并不是子线程,还仍然是父线程,所以调用 Looper.prepare(); 也只是对父线程进行了线程本地化存储了一个 Looper 对象。

2.  简而言之

两个线程之间用 handler 进行通信,一个线程先在线程本地存放一个Looper对象,该对象中有消息队列 MessageQueue 成员,只是这个Looper对象,是在线程运行Looper.prepare() 时初始化的,且保证只初始化一次,进而该线程进入Loop.loop()消息循环状态,等待其它线程调用自己的 hanldler 对象的 sendMessage() 方法往自己的消息队列中存放消息,并在有消息时读取并处理,没有则阻塞。

2.  多方协作

如果同时有多个调用方使用一个同一工作线程(调用Looper.loop()的那个线程),则可以先创建一个HandlerThread线程,然后调用它的getLooper方法得到一个Looper对象,

这时每个调用方都可以拿着这个Looper对象去构造满足各自处理逻辑的的Handler对象了,然后需要出的时候将消息丢到自己的Handler对象中即可。

最新文章

  1. Class.forName()用法及与new区别
  2. 前端MV*框架的意义
  3. JConsole监控远程Tomcat服务器
  4. Theano1.1-安装
  5. 移动web前端之meta标签
  6. malloc、calloc、realloc的区别
  7. PHP数组在HTML之中的应用
  8. java第一课:环境、变量、数据类型
  9. 转:使用xhprof进行线上PHP性能追踪及分析
  10. em px 简单换算
  11. Ubuntu 12.04设置豆沙绿
  12. C#操作Xml:XSLT语法 在.net中使用XSLT转换xml文档示例
  13. Python + request + unittest实现接口测试框架
  14. 20145203盖泽双《网络对抗技术》拓展:注入:shellcode及return-into-libc攻击
  15. ERROR 1045 (28000): Access denied for user &#39;xxx&#39;@&#39;localhost&#39; (using password: YES)【奇葩的bug】
  16. swagger支持中英文
  17. Django眼中的MVC
  18. Linux 下执行Shell 脚本的方式
  19. jQuery自定义alert,confirm方法及样式
  20. 使用bootstrap3.0搭建一个具有自定义风格的侧边导航栏

热门文章

  1. POJ 2187 Beauty Contest (求最远点对,凸包+旋转卡壳)
  2. Delphi 类引用 Class Reference 元类 MetaClass 用法
  3. How do I find what queries were executing in a SQL memory dump?-----stack
  4. Nand Flash与Nor
  5. tez是什么?
  6. 【文件监控】之一:理解 ReadDirectoryChangesW part1
  7. python的FTP模块
  8. C#接收xmlrpc接口返回哈希表格式
  9. Android跟踪NDK崩溃信息
  10. [Android Pro] Notification的使用