Android多线程源码学习笔记一:handler、looper、message、messageQueue
最近在学习Android多线程相关知识的源码,现在把自己的笔记整理一下,写出来加深印象。
Android多线程通讯的核心是handler、looper、message、messageQueue,这篇文章就先记录下这套系统的源码要点,具体的实现方法下一篇文章再写。
内容为自己看源码的理解,如有问题,欢迎留言探讨,共同进步。
Thread
用法一:
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
mThread.setText(msg.obj.toString());
}
}
};
...
new Thread(new Runnable() {
@Override
public void run() {
Log.d("coco", "thread:" + Thread.currentThread().getName());
Message message = handler.obtainMessage();
message.obj = "thread_msg";
message.what = 1;
handler.sendMessage(message);
}
}).start();
主线程中初始化handler,实现handleMessage,子线程中sendMessage,实现通讯。(ps:handler内存泄漏后面写)
方法二:
handler.post(new Runnable() {
@Override
public void run() {
Message message = Message.obtain(handler);
message.obj = "thread_msg1";
message.what = 1;
handler.sendMessage(message);
}
});
这种方法跟第一种实现原理是一样的,直接返回sendMessageDelayed(getPostMessage(r), 0),通过getPostMessage从Runnable中获取message,然后放到messageQueue中。
handler
handler是多线程通讯的控制器,负责消息的发送与处理,handler的初始化代码如下:
//FIND_POTENTIAL_LEAKS为常量,值为false,即第一个if语句不会执行。内部的代码逻辑是判断handler的创建方式,决定是否需要打
//印内存泄漏的log,如果是该handler对象是通过匿名类、成员类、内部类、非静态类的话,有可能造成内存泄漏,需要打印log
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> 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());
}
}
//首先对looper进行判空,如果为空就抛出异常,所以如果在子线程中初始化handler,一定要先初始化looper,主线程在系统创建时就初
//始化了looper,所以可以直接创建handler。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
mQueue是获取的mLooper的mQueue,所以mQueue也是当前线程相关的,具体原因在looper的源码分析中会讲。mAsynchronous是判断是否有异步消息,Android会优先处理异步消息,具体的实现在messageQueue中会讲到。
public final Message obtainMessage()
{
return Message.obtain(this);
}
obtainMessage方法是从message的公共池中取出一个message,相对于直接new出来,效率更高。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
dispatchMessage方法是handler在接收到message后进行分发时调用的,msg.callback是一个Runnable对象,在message创建时传入,或者通过setCallback方法设置,默认为空;mCallback是Callback对象,在handler初始化的时候传入,默认也为空。所以没有特定设置的情况下,会直接走到handlerMessage中,即我们创建handler时复写的回调方法。
looper
looper的主要成员变量如下:
MessageQueue mQueue跟looper绑定的消息队列。
Thread mThreadlooper所在线程对象。
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));
}
sThreadLocal的数据结构为ThreadLocal,在prepare中首先判断sThreadLocal是否为空,表明一个线程只能有一个looper对象,符合单例模式的设计思想。
sThreadLocal.set(new Looper(quitAllowed))该方法是new一个Looper,并将该Looper与当前线程的threadLocalMap关联起来,所以该looper属于调用prepare方法的线程。
接下来是最重要的loop方法,loop与prepare方法都是静态方法,通过Looper.prepare跟Looper.loop调用即可,所以在loop开始的时候要先获取当前thread的looper与messageQueue。
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;
Binder.clearCallingIdentity();
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
...
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
Binder.clearCallingIdentity() 方法是为了清除当前线程传入的IPC标识,如果在其他线程传入IPC请求,当前线程又要调用当前线程的本地接口,先清除传入的IPC标识,那么调用本地接口时就不需要进行权限验证。 常见的使用Handler线程间通讯: 主线程: Handler handler = new Handler() { @Override public void handleMessage(Messag ... JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ... Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ... 本文主要讲述AQS的共享模式,共享和独占具有类似的套路,所以如果你不清楚AQS的独占的话,可以看我的<JUC源码学习笔记1> 主要参考内容有<Java并发编程的艺术>,< ... JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ... 上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ... 版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}()); 这样的东西,我们应该知道这是一个 IIFE(立即执行 ... AXI_LITE源码学习笔记 1. axi_awready信号的产生 准备接收写地址信号 // Implement axi_awready generation // axi_awready is a ... Hadoop源码学习笔记(6) ——从ls命令一路解剖 Hadoop几个模块的程序我们大致有了点了解,现在我们得细看一下这个程序是如何处理命令的. 我们就从原头开始,然后一步步追查. 我们先选中ls命 ... 本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ... 1.properties文件在classpath根路径下读取方式 Properties properties = new Properties(); properties.load(BlogIndex ... Django提供了很多内置的模板标签比如{% if %}或者{% block %}Django也允许你创建自己的模板标签(template tags)来执行自定义的动作.当你需要在你的模板中添加功能而 ... Shell命令的开头有三种: hadoop fs hadoop dfs hdfs dfs 关于具体命令之前写过 可查看:https://www.cnblogs.com/floakss/p/970344 ... “在我短暂的电梯作业中我发现,架构的优化能力是有限的.越是工于优化算法…越是会被自己的架构所制约….想要更好的优化,唯有超越架构………" 零.基础 优化建立在架构之上,这句话莫得问题,也莫得 ... [bzoj2751][HAOI2012]容易题(easy) 先考虑k=0的情况 那么第一个元素可能为[1,n] 如果序列长度为m-1时的答案是ans[m-1] 那么合并得 然后同理答案就是 k很小 而 ... 性能视图V$开头 V$SYSTEM_EVENT 正在等待的资源的系统信息 V$SESSION_EVENT 会话累计发生的等待事件 V$SESSION_WAIT 会话正在等待或者曾经等待的详细时间信息 ... \(\color{#0066ff}{题目描述}\) W 公司有 m 个仓库和 n 个零售商店.第 i 个仓库有 \(a_i\) 个单位的货物:第 j 个零售商店需要 \(b_j\) 个单位的货物. 货 ... P2416 泡芙 题目描述 火星猫经过一番努力终于到达了冥王星.他发现冥王星有 N 座城市,M 条无向边.火星猫准备出发去找冥王兔,他听说有若干泡芙掉落在一些边上,他准备采集一些去送给冥王兔.但是火星 ... link ms是莫比乌斯反演里最水的题... 题意:对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d. 多组询问, T<=50000, ...
然后通过 for(;
Android多线程源码学习笔记一:handler、looper、message、messageQueue的更多相关文章
随机推荐