Android 中消息传递那点事(上)
前言
2019年8月29日,以前都是在笔记上记录学习的知识,总感觉是一家之言,心血来潮,还是想写一点东西让大家看看,也是对自己能力的提高。
今天不是很忙,抽出时间来复习一下学习过的知识,又加深了印象,同时也学习了一些新的东西,在这里整理出来,有不足的地方,希望大家指出,文章仅供学习,记录,交流,转载请注明出处。
这节说一下基本原理,下讲源码。
psvm
说Android消息传递之前,我们说一些相关的事情。
在刚刚接触android时,我们都会有一个问题,明明是java写的,为什么没有找到熟悉的psvm?
public static void main(String[] args) {
}
其实Android是有的,位于ActivityThread类中,我们先不看内容代码。
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 libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
到了这里,可能也就明白,那我为什么会提出psvm这个问题呢,是因为我们在后面确实会用到。
明确前提
android中的线程
Android中的线程主要分为两种:
- UIThread,即主线程,又称UI线程;
- 另一类是WorkerThread,即自定义线程,或者称为工作线程。
一个Android进程运行起来后,JVM自动启动一个线程,即主线程(也就是UI线程),在这个线程里原则上可以进行任何合法操作。但在sdk13以后,规定不能再主线程中进行访问网络的操作,因此就需要我们新开一个线程来完成网络操作。
关于子线程能否更新UI
不可以,子线程访问UI并发访问,导致UI线程不安全,所以只能在主线程中更新UI
系统为什么不对UI控件加上访问锁?缺点有俩:
- 锁的机制会让UI访问的逻辑变得复杂
- 降低UI访问的效率,锁的机制会阻塞某些线程的执行
鉴于以上缺点,Android采用单线程模型来处理UI操作,然后通过Handler切换UI访问的执行线程
如果在其它线程访问UI线程,Android提供了以下的方式:
1.Activity.runOnUiThread(Runnable)
2.View.post(Runnable) / postDelayed(Runnable, long)
3.Handler
子线程更新UI错误提示
如果非要在子线程进行UI的更新,会出现
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6581)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)
如图也可以看出来,检测是由ViewRootImpl的checkThread方法
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
通过这个错误我们发现,自线程是可以更新UI的,因为他是说,只有创建该控件的线程可以更新他,而我们绝大部分创建控件都是在主线程,所以更新也就需要在主线程,也就有了只有主线程才能更新UI的说法了
涉及到的对象
Handler
- 定义:Message的处理者,发送者
- 作用:将一个任务切换到某个特定的线程中去执行(负责发送Message到消息队列 处理Looper分派过来的Message)
Message(消息)
- 定义:Handler接收和处理的消息对象,数据的包装结构
- 作用:通信时相关信息的存放和传递
MessageQueue(消息队列)
- 定义:存储结构并不是真正的队列,而是采用单链表的数据结构存储消息列表
- 作用:消息的存储单元,并不处理消息
- 队列:先进先出,前段删除,后端进入
- 单链表:单向链式存储的线性表
Looper(消息循环器)
- 定义:扮演Message Queue和Handler之间桥梁的角色
- 作用:
- 消息循环:循环取出Message Queue的Message 有消息就处理消息,没有就一直等待
- 消息派发:将取出的Message交付给相应的Handler
- 存储于:ThreadLocal
- 定义:线程内部的数据存储类
- 作用:并不是线程,作用域于线程,可以在每个线程中互不干扰的存储、提供数据,可以轻松的获得每个线程的Looper
handler和Looper的纠缠
- 使用handler必须有Looper,否则会出错
- 主线程默认创建了Looper
- 子线程默认没有Looper
- 没有Looper会出现错误
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
Looper类中给出了示例,正确的使用Handler需要进行如下操作。
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
有人说了,那为啥我不创建Looper也一样能使用,那是因为你在主线程中,我们之前在psvm时提到的代码这时就用到了,我剔除了一些其他代码,保留了相关代码。
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在精简一下,你会发现和上面的示例已经不能在像了
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
我的理解
在我们最开始关心线程的问题,肯定是需要根据数据去更新界面,但是又不是在主线程中,然后才意识到handler的存在,我们刚开始一般都会这样使用
public class XXXActivity {
// 步骤1:匿名内部类方式创建handler实例
private Handler mhandler = new mHandler(){
// 步骤2:通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需执行的UI操作
}
};
// 步骤3:创建所需的消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
// 步骤4:在工作线程中 通过Handler发送消息到消息队列中
mHandler.sendMessage(msg);
}
看完涉及的对象,以及handler和Looper之间的关系,结合我们以前简单使用的handler的方式(先不考虑这样使用造成警告,导致内存泄漏的问题),我简单说一下我的理解
首先我们在工作线程获得了新的数据,但是不允许更新ui,我们只能去使用系统提供的handler,我们需要创建handler对象,但是使用handler需要先获得looper,我们目的是更新主线程,所以在主线程调用无参数的构造方法创建handler,这个构造函数默认调用当前线程的Looper。
我们做个比喻,handler只进行消息传递和处理,可以看成主审官员,数据就是一个个待处理的囚犯,Message是包装数据的,毕竟数据身份比较复杂,无论是大员还是乞丐,都要一致对待,也就是都要穿囚服,MessageQueue是一个存放消息的队列,就是审讯囚犯的通道,来的消息需要按顺序执行,大家不能插队是吧,Looper就是侍卫,毕竟handler有身份,需要有人来把囚犯带过来,有人说了,不是handler发送的消息么,主审官员需要提犯人,说我要审谁谁谁,毕竟这个数据是给当前handler处理。然后handler就可以坐在椅子上等待审讯了,Looper侍卫一遍一遍从MessageQueue中循环着拿出Message交给Handler进行处理。
消息传递流程
这里采用了网上一张图