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命 ... 字 word 字节 byte 位 bit 字长是指字的长度 1字=2字节(1 word = 2 byte) 1字节=8位(1 byte = 8bit) 一个字的字长为2个字节=2*8=16 一个字节 ... yzm10的进阶之路,记录一点一滴的进步,希望与之共勉. 时间 比赛 战绩 2015.11 NOIP2015全国青少年信息学奥林匹克联赛 提高组二等奖 2017.6 青岛大学“六一八杯”校赛个人赛 冠 ... 1.sqlplus 执行后提示输入用户名及密码 2.sqlplus /nolog; 执行后非登录状态进入 3.conn name/password; 以指定用户名及密码登录 4.sqlplus nam ... 比较好用的压缩软件,支持合并 JsCompressor-v3.0 比较好用的混淆站点:http://dean.edwards.name/packer/ ylbtech-AI-Info-Micron-Insight:工业 5.0,伟大的思想将殊途同归 1.返回顶部 1. 工业 5.0,伟大的思想将殊途同归 两个头脑比一个好吗?似乎如此,尤其是当其中一个 ... Node.js v8.11.1 Node.js v8.11.1 文档 今天大致浏览了一下Node.js的官方文档,走马观花的了解了大部分模块的api,对他们的使用场景做一个简单的笔记 assert 断 ... 前言 HTML的DOM是JavaScript有能力对HTML作出反应,有时候,我们需要一些网页效果,为了更好地适应用户的效果,比如,我们平时接触,点击某个按钮,按下去的瞬间会变色,再比如,有时候鼠标经 ... 题目背景 在那遥远的西南有一所学校 /*被和谐部分*/ 然后去参加该省省选虐场 然后某蒟蒻不会做,所以也出了一个字符串题: 题目描述 给你一个字符串a,每次询问一段区间的贡献 贡献定义: 每次从这个区 ... \(\color{#0066ff}{题目描述}\) 给定一个由 \(n\) 行数字组成的数字梯形如下图所示. 梯形的第一行有 \(m\) 个数字.从梯形的顶部的 \(m\) 个数字开始,在每个数字处可 ... 和某篇随笔重了?!!?!?!?!?!?不管了留着吧 题目: 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之 ...
然后通过 for(;
Android多线程源码学习笔记一:handler、looper、message、messageQueue的更多相关文章
随机推荐