Handler学习小结
在android消息机制中Handler扮演着举足轻重的作用,(AsnyTask其实也是对Handler+Thread做了一层封装),ui线程超过5s就会报出ANR,一般耗时操作操作需要放在子线程中处理,这时候Handler就可以大展身手,Handler主要用来处理完耗时操作将访问UI的工作切换到主线程去。
1、原理篇
简单概括一下:
Handler创建时会采用当前线程的Looper来构建内部的消息循环系统如果当前线程没有Looper那么就会报错,因此使用时主要为当前线程创建Looper即可或者在一个有Looper的线程中创建Handler也可以,在我们的UI线程中系统已经在一开始就为我们创建好了,自定义的子线程如何创建后面会说。
首先需要搞定几个概念
【MessageQueue】是一种数据结构,见名知义,就是一个消息队列(注意不是数据结构的队列,其实是个单链表),存放消息的地方。每一个线程最多只可以拥有一个MessageQueue数据结构。创建一个线程的时候,并不会自动创建其MessageQueue。通常使用一个Looper对象对该线程的MessageQueue进行管理。主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个Message
Queue。其他非主线程,不会自动创建Looper,要需要的时候,通过调用prepare函数来实现。
示例:
public void run(){ Looper.prepare(); //创建该线程的Looper对象 //这里Looper.myLooper()获得的就是该线程的Looper对象了 handler = new ThreadHandler(Looper.myLooper()); Message msg = handler.obtainMessage(1,1,1,"我自己"); handler.sendMessage(msg); Looper.loop(); }
俩行代码,该线程就具有自己的Looper啦
【Message】消息对象
MessageQueue里面可以存放Message的消息一个Message Queue中包含多个Message。Message实例对象的取得,通常使用Message类里的静态方法obtain(),该方法有多个重载版本可供选择;它的创建并不一定是直接创建一个新的实例,而是先从Message Pool(消息池)中看有没有可用的Message实例,存在则直接取出返回这个实例。
/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
源码中可以看出如果Message Pool中没有可用的Message实例(poolsize=50),则才用给定的参数创建一个Message对象。调用removeMessages()时,将Message从Message Queue中删除,同时放入到Message Pool中。除了上面这种方式,也可以通过Handler对象的obtainMessage()获取一个Message实例。
通常Message Queue只有俩种单链表操作 插入(对应方法enqueueMessage)和读取(next)
【Looper】
是MessageQueue的管理者。每一个MessageQueue都不能脱离Looper而存在,Looper对象的创建是通过prepare函数来实现的。同时每一个Looper对象和一个线程关联。通过调用Looper.myLooper()可以获得当前线程的Looper对象创建一个Looper对象时,会同时创建一个MessageQueue对象。除了主线程有默认的Looper,其他线程默认是没有MessageQueue对象的,所以,不能接受Message。如需要接受,自己定义一个Looper对象(通过prepare函数),这样该线程就有了自己的Looper对象和MessageQueue数据结构了。Looper从MessageQueue中取出Message然后,交由Handler的handleMessage进行处理。处理完成后,调用Message.recycle()将其放入Message
Pool中。
Looper扮演着消息循环角色,一有消息就处理,否则就阻塞在那里。
小结一下:
当Handler创建完毕后,内部的Looper以及MessageQueue就可以和Handler一起协同工作了,然后通过Handler的post方法将一个Runnable放到Looper中或者通过send方法发送一个消息,同样也是放到Looper中处理(post最终还是通过send方法来完成的),send然后调用enqueueMessage来插入到消息链表中,Looper发现有新消息来到就会处理这个消息,最终Runnable或者Handler的Messager方法就会被调用,要注意looper是运行在handler所在的线程中,这样handler中的业务逻辑就被切换到创建Handler的逻辑中去了
关于更加详细的Handler分析可以参看http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html
2、使用篇
【handler】
对于Handler来说有两种主要的方式:
1. 计划好消息和Runnable将来的某一个时间点来执行它 2. 从一个不同的线程中执行Handler的入队操作。分发消息由下面的几个方法完成:
1) post(Runnable),
2) postAtTime(Runnable, long),
3) postDelayed(Runnable, long),
4) sendEmptyMessage(int),
5) sendMessage(Message),
6) sendMessageAtTime(Message, long),
7) sendMessageDelayed(Message, long)
post方式的方法可以将一个Runable对象排列到消息队列中。sendMessage方式的方法可以通过 Handler的handleMessage(Message) 方法携带有bundle类型的数据的Message对象到队列中(需要你实现Handler的子类)。
【message】
定义一个message包含描述信息和任意的数据对象发送给Handler。这个对象包含两个额外的int类型的属性和一个Object类型的属性,它可以让你不需要去做一些强制类型的转换的操作。
1) arg1 和 arg2 都是Message自带的用来传递一些轻量级存储int类型的数据,比如进度条的数据等。通过这个数据是通过Bundle的方式来转载的,读者可以自己查阅源代码研究。
2) obj 是Message自带的Object类型对象,用来传递一些对象。兼容性最高避免对齐进行类型转换等。
3) replyTo 是作为线程通信的时候使用.
4) what 用户自定义的消息码让接受者识别消息种类,int类型。
获得Message的构造方法最好的方式是调用Message.obtain() 和 Handler.obtainMessage()方法。以便能够更好被回收池所回收,而不是直接用 new Message的方式来获得Message对象
【避免使用不当造成内存泄露】
使用handler不当会造成内存泄露比如下面一段代码
Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { mImageView.setImageBitmap(mBitmap); } }
当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC
检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有 Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler 的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message
-> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。(参考自http://my.oschina.net/rengwuxian/blog/181449)
通常我们使用弱引用来处理,比如如下的方式就非常优雅
private MyHandler mHandler = null; private static class MyHandler extends Handler { private WeakReference<WaterWaveProgress> mWeakRef = null; private int refreshPeriod = 100; public MyHandler(WaterWaveProgress host) { mWeakRef = new WeakReference<WaterWaveProgress>(host); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (mWeakRef.get() != null) { mWeakRef.get().invalidate(); sendEmptyMessageDelayed(0, refreshPeriod); } } }
Handler学习小结的更多相关文章
- pthread多线程编程的学习小结
pthread多线程编程的学习小结 pthread 同步3种方法: 1 mutex 2 条件变量 3 读写锁:支持多个线程同时读,或者一个线程写 程序员必上的开发者服务平台 —— DevSt ...
- ExtJs学习笔记之学习小结LoginDemo
ExtJs学习小结LoginDemo 1.示例:(登录界面) <!DOCTYPE html> <html> <head> <meta charset=&quo ...
- clone的fork与pthread_create创建线程有何不同&pthread多线程编程的学习小结(转)
进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合,这些资源在Linux中被抽 象成各种数据对象:进程控制块.虚存空间.文件系统,文件I/O.信号处理函数.所以创建一个进程的 过程就是这 ...
- flex学习小结
接触到flex一个多月了,今天做一个学习小结.如果有知识错误或者意见不同的地方.欢迎交流指教. 画外音:先说一下,我是怎么接触到flex布局的.对于正在学习的童鞋们,我建议大家没事可以逛逛网站,看看人 ...
- Python 学习小结
python 学习小结 python 简明教程 1.python 文件 #!/etc/bin/python #coding=utf-8 2.main()函数 if __name__ == '__mai ...
- react学习小结(生命周期- 实例化时期 - 存在期- 销毁时期)
react学习小结 本文是我学习react的阶段性小结,如果看官你是react资深玩家,那么还请就此打住移步他处,如果你想给一些建议和指导,那么还请轻拍~ 目前团队内对react的使用非常普遍,之 ...
- objective-c基础教程——学习小结
objective-c基础教程——学习小结 提纲: 简介 与C语言相比要注意的地方 objective-c高级特性 开发工具介绍(cocoa 工具包的功能,框架,源文件组织:XCode使用介绍) ...
- 点滴的积累---J2SE学习小结
点滴的积累---J2SE学习小结 什么是J2SE J2SE就是Java2的标准版,主要用于桌面应用软件的编程:包括那些构成Java语言核心的类.比方:数据库连接.接口定义.输入/输出.网络编程. 学习 ...
- (转) Parameter estimation for text analysis 暨LDA学习小结
Reading Note : Parameter estimation for text analysis 暨LDA学习小结 原文:http://www.xperseverance.net/blogs ...
随机推荐
- 新浪微博Oauth2.0授权认证及SDK、API的使用(Android)
---------------------------------------------------------------------------------------------- [版权申明 ...
- Android应用UI设计流程
Android应用UI设计流程 设计原理 1.在移动设计中,使用环境是最关键的因素.原型设计方法必须考虑尺寸因素 2.用户测试必须涵盖运动.声音和多点触控等方面: 进行移动设计和测试时,请将你知道的有 ...
- 15 Action View 以及监听 的使用
menu 代码 <menu xmlns:android="http://schemas.android.com/apk/res/android" > <!-- a ...
- Servlet之Response对象
下面的方法可用于在 Servlet 程序中设置 HTTP 响应报头.这些方法通过HttpServletResponse 对象可用. 1 String encodeRedirectURL(Stri ...
- 同步图计算:GraphLite的安装和使用
http://blog.csdn.net/pipisorry/article/details/51350908 export HADOOP_HOME=/usr/local/hadoop-2.6.4ex ...
- android开发之broadcast学习笔记
android中的广播用的太多了,今天稍微总结一下. 按注册方式分为两种: 1.静态注册广播: 静态注册广播就是在androidManifest.xml文件中注册广播,假设我们要实现这样一个效果,在一 ...
- Postgre: How to import UUID function into Postgre 9.3
1. Open a command console and go to the directory where you installed Postgre server. e.g. D:\Progra ...
- Aandroid TV 基于Leanback支持最新MD设计的TV开发框架
原文地址:http://blog.csdn.net/sk719887916 作者:skay 基于6.0最新的API 支持TV的框架 Android 6.0已完美支持TV开发,之前的5.0后Recycl ...
- ubuntu make menuconfig error
主机环境:ubuntu -------------------------------------------------------------- 在ubuntu系统中,要编译内核,还需要安装一系列 ...
- Cocos2D将v1.0的tileMap游戏转换到v3.4中一例(一)
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 首先说一下为什么要转换,这是为了后面的A*寻路算法做准备.由于在 ...