Android Handler学习笔记
已经习惯了挖坑不填,继续任性一下,周一到周五继续挖坑,每周六周日负责填坑。
1.从Android UI线程谈起
出于性能考虑,Android 中的UI操作并不是线程安全的,所以Android中规定只能在主线程中修改Activity中的UI组件,故主线程又称UI线程。Android程序启动时会同时启动UI线程,该线程负责处理UI相关事件,并且进行事件分发。
- 不是线程安全可以理解为两个人同时独立的在一张纸上画画,最后画出来的就是错乱的。实现线程安全可以通过加锁进行数据保护,以后有机会写C++相关博客再介绍多线程问题。
在某些情况下我们需要让新启动的线程周期性更新UI(比如游戏动画中),所以要找一个方式在子线程中发送消息,在主线程接受消息,配合起来更新UI,这时我们就需要Handler了。
2.Handler是个什么鬼
参考资料:
疯狂Android讲义第三章
Handler的主要作用有两个:1)在新线程发送消息,2)在主线程获取和处理消息
先用了再说:
/*
*主线程中使用Handler
*/
private static final String TAG = "HandlerActivity";
private Handler mHandler;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_test);
//在主线程中创建Hander对象
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//接受消息对象并处理
super.handleMessage(msg);
Log.e(TAG,"------------> msg.what = " + msg.what);
}
};
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
//在子线程中使用Handler对象发送消息
mHandler.sendEmptyMessage(0);
}
}).start();
}
感觉很好用,说起来就是Android不允许子线程访问UI组件,所以我们要在新线程中发送消息,让系统更新UI组件。在new出来的Handler对象中重写了handleMessage()
,当通过新线程发送消息是,该方法自动被回调。
接下来总结一下Handler中的消息发送和处理方法:
算了,懒得总结,直接看官方文档
常用的有这些:
发送消息:
sendMessage()
,sendEmptyMessage()
,sendMessageDelayed()
,sendEmptyMessageDelayed()
接受处理消息:
handleMessage
,obtainMessage()
等
3.可以在子线程创建Handler吗
/*
*子线程中使用Handler
*/
private static final String TAG = "TestHandlerActivity";
//主线程中的handler
private Handler mHandler;
//子线程中的handler
private Handler mHandlerThread = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_test);
//创建handler对象
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//获得刚才发送的Message对象,然后在这里进行UI操作
Log.e(TAG,"------------> msg.what = " + msg.what);
}
};
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
//通过Handler发送一个消息切换回主线程(mHandler所在的线程)
mHandler.sendEmptyMessage(0);
/*
//调用Looper.prepare()方法
Looper.prepare();
*/
//在子线程中创建Handler
mHandlerThread = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//在子线程中调用handleMessage接受和处理message对象
Log.e("sub thread","---------> msg.what = " + msg.what);
}
};
mHandlerThread.sendEmptyMessage(1);
/*
/*
//调用Looper.prepare()方法
Looper.loop();
*/
*/
}
}).start();
}
很不幸,崩溃了!
使用Handler的两个经典异常:
CalledFromWrongThreadException
:这种异常是因为尝试在子线程中去更新UI,进而产生异常。
Can’t create handle inside thread that ha not called Looper.prepared
:是因为我们在子线程中去创建Handler,而产生的异常。
修改方式,调用Looper.prepare()
方法,将上面的注释放开。
4.异步消息处理——Handler
深入源码解析Android中的Handler,Message,MessageQueue,Looper
Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?
异步消息处理线程启动之后处于无限循环体中,每循环一次,则从内部消息队列中取出一条消息,然后回掉相关消息处理函数。
执行完一个消息之后会继续循环,若消息队列为空,则线程阻塞等待。
Looper负责创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler 。
Looper的核心方法
Looper的构造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
//构造方法主要是创建消息队列
mRun = true;
mThread = Thread.currentThread();
}
prepare()方法
public static final void prepare() {
//sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true));
}
第5行行代码中将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例。
loop方法:
public static void loop() {
final Looper me = myLooper();
//返回了sThreadLocal存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行。
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//拿到该looper实例中的mQueue(消息队列)
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);
//把消息交给msg的target的dispatchMessage方法去处理,即实现消息分发。
//Msg的target是什么呢?其实就是handler对象
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.recycle();
}
}
Looper主要作用:
1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是乎:Handler登场了。
5.理解一下
Android系统中专门启动一个线程用于异步消息处理,从都源码可以看到该线程为sLocalThread
,该线程中启动一个Looper对象,Looper对象中的Loop()
函数负责遍历消息队列和分发消息,每一个Looper中有且仅有一个MessageQueue,该对象用于保存所有的msg。
可以理解为 one loop per thread (陈硕在muduo的设计理念中提到)。
10.一个坑爹的问题——为什么在有些时候子线程中是可以直接更新UI的?
Android Handler学习笔记的更多相关文章
- Android:日常学习笔记(7)———探究UI开发(1)
Android:日常学习笔记(7)———探究UI开发(1) 常用控件的使用方法 TextView 说明:TextView是安卓中最为简单的一个控件,常用来在界面上显示一段文本信息. 代码: <T ...
- Android自动化学习笔记:编写MonkeyRunner脚本的几种方式
---------------------------------------------------------------------------------------------------- ...
- Android自动化学习笔记之MonkeyRunner:官方介绍和简单实例
---------------------------------------------------------------------------------------------------- ...
- android开发学习笔记000
使用书籍:<疯狂android讲义>——李刚著,2011年7月出版 虽然现在已2014,可我挑来跳去,还是以这本书开始我的android之旅吧. “疯狂源自梦想,技术成就辉煌.” 让我这个 ...
- Android动画学习笔记-Android Animation
Android动画学习笔记-Android Animation 3.0以前,android支持两种动画模式,tween animation,frame animation,在android3.0中 ...
- Android 数字签名学习笔记
Android 数字签名学习笔记 在Android系统中,所有安装到系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程序之间建立信任关系,如果一个permission的pro ...
- Android:日常学习笔记(9)———探究持久化技术
Android:日常学习笔记(9)———探究持久化技术 引入持久化技术 什么是持久化技术 持久化技术就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失 ...
- Android:日常学习笔记(9)———探究广播机制
Android:日常学习笔记(9)———探究广播机制 引入广播机制 Andorid广播机制 广播是任何应用均可接收的消息.系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播.通过将 In ...
- Android:日常学习笔记(8)———开发微信聊天界面
Android:日常学习笔记(8)———开发微信聊天界面 只做Nine-Patch图片 Nine-Patch是一种被特殊处理过的PNG图片,能够指定哪些区域可以被拉升,哪些区域不可以.
随机推荐
- H5动效的常见制作手法
众所周知,一个元素,动往往比静更吸引眼球: 一套操作界面,合适的动态交互反馈能给用户带来更好的操作体验: 一个H5运营宣传页,炫酷的动画特效定能助力传播和品牌打造. 近两年,小到loading动画,表 ...
- POJ 2486 Apple Tree (树形dp 经典题)
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const ...
- Centos 搭建activemq
Centos 搭建activemq 1,官方下载 http://activemq.apache.org/activemq-5122-release.html apache-activemq-5.15 ...
- PHP中调用Soap/WebService
关于在PHP中如何调用Soap/WebService的描述,网络上有不少帖子.但是主要讲述了如何用PHP开发服务器端.客户端并加以关联,而很少触及在PHP中调用现成的WebService的情况.在本文 ...
- Edit Distance FZU-1434
题目大意: 给你两个字符串A,B,和以下三种操作: 1.删除一个字符 2.插入一个字符 3.把一个字符改变成另一个字符 求使A变成B所需要的最少的操作: 我刚开始的思路是以为求出最长公共子序列,然后对 ...
- 训练1-o
给出2个N * N的矩阵M1和M2,输出2个矩阵相乘后的结果. Input 第1行:1个数N,表示矩阵的大小(2 <= N <= 100)第2 - N + 1行,每行N个数,对应M1的1行 ...
- BZOJ 3028 食物 (生成函数+数学题)
题面:BZOJ传送门 题目让我们求这些物品在合法范围内任意组合,一共组合出$n$个物品的方案数 考虑把每种食物都用生成函数表示出来,然后用多项式乘法把它们乘起来,第$n$项的系数就是方案数 汉堡:$1 ...
- 【 AIM Tech Round 5 (rated, Div. 1 + Div. 2) C】Rectangles
[链接] 我是链接,点我呀:) [题意] 给你n个矩形. 让你找出一个点(x,y) 使得这个点在其中至少(n-1)个矩形中. [题解] 若干个矩形交在一起的话. 它们所有的公共区域也会是一个矩形. 这 ...
- BJFU fudq的等式
/* BJFU fudq的等式 http://101.200.220.237/contest/19/problem/118/ 数论 勒让德定理 二分答案 */ #include <cstdio& ...
- JS中的call()(转)
1.方法定义 call方法: 语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象. 说明: call 方 ...