【Handler】Looper 原理 详解 示例 总结
核心知识点
- UI线程:就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue
- Handler:作用就是发送与处理信息,如果希望Handler正常工作,在当前线程中必须要有一个Looper对象
- Message:Handler接收与处理的对象。Handler也能接收与处理Runnable对象
- MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue
- Looper:每个线程只能够有一个Looper,Looper负责创建并管理当前线程中的MessageQueue,调用loop方法后就会在一个无限循环体中不断地从MessageQueue中取出Message并分发给对应的Handler,最后回调handleMessage()方法处理此消息。Looper才是整个机制的核心!
- 在主线程中创建Handler并重写handleMessage()方法
- 在任何线程中都可以利用此Handler发送消息,消息会被发送到主线程的MessageQueue中
- 一旦MessageQueue中有新消息,主线程中的 Looper 就会发现此消息,然后就会调用Handler的handleMessage()方法处理此消息
- 调用Looper.prepare()后首先会在本线程中保存唯一的一个Looper实例,然后会在该实例中创建并保存一个MessageQueue对象;Looper.prepare()在一个线程中只能调用一次,MessageQueue在一个线程中也只存在一个。
- 调用Looper.loop()方法后会让当前线程进入一个无限循环中,Looper不断从MessageQueue中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
- Handler的构造方法中,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。
- Handler的sendMessage方法,会给msg.target赋值为handler自身,然后加入MessageQueue中。
- 在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。
Handler面试点
- Looper:一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。
- Handler:你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出发送来的消息。
- Message Queue(消息队列):用来存放线程放入的消息。
- 线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。
message.target = this;//this即这个发送此Message的handler对象
message.target.dispatchMessage(message); //message.target即发送此Message的handler对象
Looper详解
prepare()方法
public static final void prepare() {
if (sThreadLocal.get() != null) throw new RuntimeException("Only one Looper may be created per thread");
sThreadLocal.set(new Looper(true));
}
ThreadLocal可以在一个线程中存储变量,可以看到,我们将一个Looper的实例放入了ThreadLocal,并且判断了sThreadLocal是否存储过Looper对象,存储过(即.get()不为null)则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
loop()方法
public static void loop() {
final Looper me = myLooper();//方法体为 return sThreadLocal.get(); 即此方法直接返回了sThreadLocal存储的Looper实例
if (me == null) throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");//如果me为null则抛出异常,也就是说looper方法必须在prepar()之后运行。
final MessageQueue queue = me.mQueue;//拿到该looper实例中的消息队列
Binder.clearCallingIdentity(); // Make sure the identity of this thread is that of the local process, and keep track of what that identity token actually is.
final long ident = Binder.clearCallingIdentity();
//无限循环
for (;;) {
Message msg = queue.next(); // might block,取出一条消息,如果没有消息则阻塞等待
if (msg == null) return;
Printer logging = me.mLogging; // This must be in a local variable, in case a UI event sets the logger
if (logging != null) logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
msg.target.dispatchMessage(msg);//Msg的target其实就是handler对象
if (logging != null) logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
final long newIdent = Binder.clearCallingIdentity();// Make sure that during the course of dispatching the identity of the thread wasn't corrupted.
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.recycle();//释放消息占据的资源
}
}
- 1、与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
- 2、执行loop()方法,不断从MessageQueue中取消息,交给消息的target属性的dispatchMessage方法去处理。
Handler详解
public Handler() {//我们一般都是使用此无参的构造方法
this(null, false);
}
public Handler(Callback callback, boolean async) {
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());
}
}
mLooper = Looper.myLooper();//获取当前线程保存的Looper实例
if (mLooper == null) throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");//在初始化Handler之前必须先通过Looper.prepare()方法创建Looper的实例
mQueue = mLooper.mQueue;//获取这个Looper实例中保存的MessageQueue(消息队列)
mCallback = callback;
mAsynchronous = async;
}
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) delayMillis = 0;
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) msg.setAsynchronous(true);
return queue.enqueueMessage(msg, uptimeMillis);
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) handleCallback(msg);
else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) return;
}
handleMessage(msg);
}
}
可以看到,最后调用了handleMessage方法,可实际上这是一个空方法,为什么呢?因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。
Handler的全部方法
构造方法其他方法获取消息移除消息和回调发送消息post方法有时候为了方便,我们会直接写如下代码:
mHandler.post(new Runnable() {
@Override
public void run() {
mTxt.setText("yoxi");//在run方法中可以更新UI
}
});其实这个Runnable并没有创建什么线程,而是发送了一条消息,下面看源码:public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();//得到了一个Message对象
m.callback = r;//将我们创建的Runable对象作为callback属性,赋值给了此Message
return m;
}后面的步骤就和handler.sendMessage一样了可以看到,这里msg的callback和target都有值,那么会执行哪个呢?其实上面已经贴过代码,就是dispatchMessage方法:如果msg.callback不为null,则执行callback回调,也就是我们定义的Runnable。
演示代码
public class HandlerTestActivity extends ListActivity {
private TextView tv_info;
private Handler uiHandler;
private StaticThread thread;//一个子线程
public static final int MSG_WHAT_1 = 1;
public static final int MSG_WHAT_2 = 2;
public static final int MSG_WHAT_3 = 3;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"开启子线程,并在子线程中创建一个Handler", //
"在主线程中,通过子线程的Handler[向子线程]发消息", //
"演示Handler的post方法"};
tv_info = new TextView(this);
tv_info.setText("Handler、Looper、Message、MQ、Thread关系");
getListView().addFooterView(tv_info);
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));
uiHandler = new StaticUiHandler(this); //系统启动时已经为主线程初始化了Looper、MQ等,我们可以直接创建Handler
thread = new StaticThread(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (uiHandler != null) uiHandler.removeCallbacksAndMessages(null);
if (thread != null && thread.getAnsyHandler() != null) thread.getAnsyHandler().removeCallbacksAndMessages(null);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0://开启子线程,并在子线程中创建一个Handler
if (thread != null && !thread.isAlive()) thread.start();//A thread is alive if it has been started and has not yet died.
break;
case 1://在主线程中,通过子线程的Handler[向子线程]发消息
Message msg = Message.obtain(null, MSG_WHAT_1, "消息内容"); //第一个参数Handler的作用是指定msg.target
//这里设为null的原因是:后面调用sendMessage方法时重新指定了发送此消息的Handler为msg.target
if (thread != null && thread.getAnsyHandler() != null) thread.getAnsyHandler().sendMessage(msg);
tv_info.append("\n1、在UI线程中用子线程的Handler发消息,what=" + msg.what);
break;
case 2:
//其实这个Runnable并没有创建什么线程,而是发送了一条消息,当Handler收到此消息后回调run()方法
uiHandler.post(() -> tv_info.append("\n演示Handler的post方法"));
break;
}
}
//***********************************************静态内部类,防止内存泄漏*******************************************
/**
* 主线程使用的Handler
*/
private static class StaticUiHandler extends Handler {
private SoftReference<HandlerTestActivity> mSoftReference;
public StaticUiHandler(HandlerTestActivity activity) {
mSoftReference = new SoftReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlerTestActivity activity = mSoftReference.get();
if (activity != null && activity.thread != null && activity.thread.getAnsyHandler() != null) {
activity.tv_info.append("\n4、UI线程的Handler收到消息,what=" + msg.what);
Message msg3 = Message.obtain(null, MSG_WHAT_3, msg.obj);
activity.thread.getAnsyHandler().sendMessageAtTime(msg3, SystemClock.uptimeMillis() + 2000);
activity.tv_info.append("\n5、在UI线程中用子线程的Handler发消息,what=" + msg3.what);
}
}
}
/**
* 异步线程(子线程)使用的Handler
*/
private static class StaticAnsyHandler extends Handler {
private SoftReference<HandlerTestActivity> mSoftReference;
public StaticAnsyHandler(HandlerTestActivity activity) {
mSoftReference = new SoftReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlerTestActivity activity = mSoftReference.get();
if (activity != null) {
final Message tempMsg = Message.obtain(msg);//把收到的消息保存起来
//注意,一定要注意!根据消息池机制,当此消息不再在【此子线程】中使用时,此msg会立即被重置(引用虽在,内容为空)
//所以,如果想把此消息转发到其他线程,或者想在其他线程中引用此消息,一定要手动把消息保存起来!
activity.runOnUiThread(() -> {//在子线程中创建Handler的目的是为了和其他线程通讯,绝对不是(也不能)更新UI
activity.tv_info.append("\n2、子线程的Handler收到消息,what=" + tempMsg.what);
if (activity.uiHandler != null && tempMsg.what == MSG_WHAT_1) {
Message msg2 = Message.obtain(null, MSG_WHAT_2, tempMsg.obj);
activity.uiHandler.sendMessageDelayed(msg2, 2000);
//注意,不能直接把一条还在使用的消息转发出去,否则IllegalStateException: This message is already in use
activity.tv_info.append("\n3、在子线程中用UI线程的Handler发消息,what=" + msg2.what);
}
});
}
}
}
/**
* 一个线程,用于执行耗时的操作
*/
private static class StaticThread extends Thread {
private SoftReference<HandlerTestActivity> mSoftReference;
public StaticThread(HandlerTestActivity activity) {
mSoftReference = new SoftReference<>(activity);
}
private Handler ansyHandler;
public Handler getAnsyHandler() {
return ansyHandler;
}
public void run() {
HandlerTestActivity activity = mSoftReference.get();
if (activity != null) {
Looper.prepare(); //在创建Handler【前】必须调用此方法初始化Looper,否则直接报否则报RuntimeException崩溃
//里面做的事情:①为当前线程创建唯一的Looper对象 ②在它的构造方法中会创建一个的MessageQueue对象
//此方法只能被调用一次,这保证了在一个线程中只有一个Looper实例以及只有一个与其关联的MessageQueue实例
ansyHandler = new StaticAnsyHandler(activity); //任何线程都可通过此Handler发送信息!
Looper.loop(); //若要能够接收到消息,创建Handler后,必须调用loop方法。当然此方法必须是在prepar之后执行
//里面做的事情:启动一个死循环,不断从MQ中取消息,没有则阻塞等待,有则将消息传给指定的Handler去处理
activity.runOnUiThread(() -> Toast.makeText(activity, "会一直阻塞在这里", Toast.LENGTH_SHORT).show());
}
}
}
}
【Handler】Looper 原理 详解 示例 总结的更多相关文章
- Handler Looper 原理 详解
演示代码 public class MainActivity extends ListActivity { private TextView tv_info; private CalT ...
- 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)
[1]前言 本篇幅是对 线程池底层原理详解与源码分析 的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...
- [转]js中几种实用的跨域方法原理详解
转自:js中几种实用的跨域方法原理详解 - 无双 - 博客园 // // 这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同 ...
- WebActivator的实现原理详解
WebActivator的实现原理详解 文章内容 上篇文章,我们分析如何动态注册HttpModule的实现,本篇我们来分析一下通过上篇代码原理实现的WebActivator类库,WebActivato ...
- TOMCAT原理详解及请求过程(转载)
转自https://www.cnblogs.com/hggen/p/6264475.html TOMCAT原理详解及请求过程 Tomcat: Tomcat是一个JSP/Servlet容器.其作为Ser ...
- [No0000126]SSL/TLS原理详解与WCF中的WS-Security
SSL/TLS作为一种互联网安全加密技术 1. SSL/TLS概览 1.1 整体结构 SSL是一个介于HTTP协议与TCP之间的一个可选层,其位置大致如下: SSL:(Secure Socket La ...
- JSPatch实现原理详解<二>
本文转载至 http://blog.cnbang.net/tech/2855/ 距离上次写的<JSPatch实现原理详解>有一个月的时间,在这段时间里 JSPatch 在不断地完善和改进, ...
- CentOS 6.5 iptables原理详解以及功能说明
CentOS 6.5 iptables原理详解以及功能说明 来源 https://blog.51cto.com/tanxw/1389114 前言 iptables其实就是Linux下的一个开源的信息过 ...
- 高性能JavaScript模板引擎实现原理详解
这篇文章主要介绍了JavaScript模板引擎实现原理详解,本文着重讲解artTemplate模板的实现原理,它采用预编译方式让性能有了质的飞跃,是其它知名模板引擎的25.32 倍,需要的朋友可以参考 ...
随机推荐
- Bazel
Using Bazel on Windows Google软件构建工具Bazel FAQ
- Spring boot整合shiro权限管理
Apache Shiro功能框架: Shiro聚焦与应用程序安全领域的四大基石:认证.授权.会话管理和保密. #,认证,也叫作登录,用于验证用户是不是他自己所说的那个人: #,授权,也就是访问控制,比 ...
- 2018用IDEA搭建SSM框架(Spring+SpringMVC+Mybatis)
使用IDEA搭建ssm框架 环境 工具:IDEA 2018.1 jdk版本:jdk1.8.0_171 Maven版本:apache-maven-3.5.3 Tomcat版本:apache-tomcat ...
- .NET Core2.1下采用EFCore比较原生IOC、AspectCore、AutoFac之间的性能
一.前言 ASP.NET Core本身已经集成了一个轻量级的IOC容器,开发者只需要定义好接口后,在Startup.cs的ConfigureServices方法里使用对应生命周期的绑定方法即可,常见方 ...
- session效验
看代码... <?php $servername = "服务器名"; $username = "账户名"; $password = "密码&qu ...
- new Date 兼容性问题
IOS 11.1.2 iphoneX 下面解析 // 下面的代码会报错 var d = new Date('2018-09-20 19:20:32'); alert(d.getTime()); 必 ...
- POJ 3177 Redundant Paths 双联通分量 割边
http://poj.org/problem?id=3177 这个妹妹我大概也曾见过的~~~我似乎还没写过双联通分量的blog,真是智障. 最少需要添多少条边才能使这个图没有割边. 边双缩点后图变成一 ...
- Loj #6560 小奇取石子
题面 分类讨论一波,n小的暴力2^n,n大的背包. #include<bits/stdc++.h> #define ll long long using namespace std; co ...
- 2018-2019-2 20162318《网络对抗技术》Exp3 免杀原理与实践
一.实验内容 1.正确使用msf编码器,msfvenom生成如jar之类的其他文件,veil-evasion),加壳工具),使用shellcode编程 2.通过组合应用各种技术实现恶意代码免杀(如果成 ...
- poj 1509
求一个字符串在旋转置换群下最小字典表示. 用的是后缀数组(后缀自动机还是再听听jason_yu讲讲吧,关于right集合的部分还有问题) 最小表示法的思想很有好(判断两个对象在某一置换群划分下,是否等 ...