安卓handler发送消息过多会造成handler内存泄露问题吗

> 博客详情
摘要: 对Handler通信机制及其切换线程的思路进行解析记录
ThreadLocal作用
在当前线程中存放属于该线程的数据
ThreadLocal存储算法记录
将当前线程的ThreadLocal作为key,将存放的值作为value,使用当前线程内部Value的一个对象数组table存放,key的index为ThreadLocal的引用的hash和当前线程内部Value对象的mask(mask:用于将hash转化为指数(indices))相与的结果,存放的值的index为key的index+1
为什么不用一个ThreadManager去管理比如维护一个ConcrrentHashMap?主要有两个问题
1、防止线程竞争所带来的效率问题 2、防止线程引用被强引用而在线程完成工作后不能立即被回收
其实主要是第一个问题,第二个问题可以用WeakReference解决,但第一个问题会随着时间越来越大
ThreadLocal+Value解决方案
ThreadLocal的内部存储结构是给每一个Thread创建一个Value,用来保存自己的数据,这个Value内部维护一个数组用来存放当前线程的数据
线程的Value如果为空则通过任意ThreadLocal创建,因为是每一个线程都有自己的ThreadLocal所以不用担心线程同步的问题,Threadlocal的set、remove、get方法都是间接调用到了Value内部的方法,由于ThreadLocal仅存放Value的临时变量所以不会泄露,Value利用当前的ThreadLocal作为key计算出其在table中的index,key就存放在这个Index上,而其值则存放在index+1的位置,这样就使得每一个线程都具有其自己的数据,而且避免了多线程的竞争和内存泄露的问题
Handler与Looper
在子线程中new一个Handler时,如果不在构造参数中传入一个Looper,则必须在new之前调用Looper#prepare()方法为该子线程创建一个Looper,该方法内部使用ThreadLocal存储Looper,这样可以让每个线程拥有自己的Looper,Handler在构造器初始化时会调用Looper#myLooper()方法,该方法会从ThreadLocal中获取一个Looper对象,你也可以在构造器中传入一个looper对象,如果是通过Looper#getMainLooper()得到的looper对象,不需要再调用looper#loop(),因为系统已经调用过了,系统的looper已经开始不断遍历MessageQueue了,只要有新的Message到来,looper就会调用该Message持有的(Handler发送的Message,Message会持有Handler引用)Handler#handleMessage(Message msg)方法
系统创建Looper部分代码ActivityThread#main()
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(); ............ & 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"); }
从Looper.loop()被调用起,UI线程就进入了消息的不断轮询中,一旦有新的消息过来,系统就会执行,android应用程序就这样跑起来了,这也是为什么UI线程中创建Handler不需要调用Looper#prepare()的原因
切换线程实际上就是调用位置的切换而已,Handler切换线程的机制最终原因是handleMessage方法的调用位置的切换,比如主线程。
handler将线程切换到主线程
handler将自己的引用间接被Looper持有,当Looper在主线程调用loop()方法时,该方法会取出handler并调用其handleMessage()方法,相当于切换到主线程
handler--Looper--MessageQueue的关系
这三者构成Handler通信机制
MessageQueue内部使用单链表来存储信息Message,Handler发送的Message全部都添加到了MessageQueue中,Looper#loop()方法通过调用MessageQueue#next()方法不断遍历链表中的Message,当取得符合的Message后,通过Message持有的Handler对象引用调用Handler#handleMessage方法,如此,Looper#loop()在哪个线程调用的,handleMessage方法就切换到哪个线程了。
简单代码模拟实现handler切换线程到主线程
public class MHandler { & &MLooper mLooper; & &public MHandler(MLooper mLooper) { & & & &this.mLooper = mL & &} & & public void sendMessage(MMessage msg) { & & & &//让msg保持MHandler的引用,同时让mLooper保持msg的引用,并在mLooper获取msg中的handler回调handleMessage, & & & &// 这样就能在mLooper的线程中调用handleMessage,相当于切换了线程 & & & &msg.mHandler = this; & & & &mLooper.msg = & & & &//主线程中的looper调用loop()后会处于阻塞状态,获取到msg同时间接获取到handler,此时msg不为null, & & & &// looper#loop()程序继续进行调用handlerMessage & &} & &public void handleMessage(MMessage msg) { & &} }
Message(这里只模拟一个消息,所以就用Message代替MessageQueue)
/** * Created by zz on 16/3/6. * 如果只是切换线程的话,只需要让Looper等待Handler的引用到来然后在ui线程调用就可以 * ,这里是为了模拟Handler发送信息,所以添加了一个Message */ public class MMessage { & &MHandler mHandler; & &String msg; }
public class MLooper { & &//相当于把MessageQueue简化成一个msg了 & &MMessage msg; & &public void loop() { & & & &while(msg == null) { & & & & & &//如果looper没有获取到msg,就让它一直等待 & & & &} & & & &msg.mHandler.handleMessage(msg); & &}
@Override protected void onCreate(Bundle savedInstanceState) { & &super.onCreate(savedInstanceState); & &setContentView(R.layout.activity_2); & &tv = (TextView) findViewById(R.id.activity2_text); & &final MLooper looper = new MLooper(); & &tv.setClickable(true); & &tv.setOnClickListener(new View.OnClickListener() { & & & &@Override & & & &public void onClick(View v) { & & & & & &new Thread() { & & & & & & & &@Override & & & & & & & &public void run() { & & & & & & & & & &MHandler handler = new MHandler(looper) { & & & & & & & & & & & &@Override & & & & & & & & & & & &public void handleMessage(MMessage msg) { & & & & & & & & & & & & & &super.handleMessage(msg); & & & & & & & & & & & & & &//主线程回调 & & & & & & & & & & & & & &tv.setTextSize(64); & & & & & & & & & & & &} & & & & & & & & & &}; & & & & & & & & & &MMessage msg = new MMessage(); & & & & & & & & & &msg.msg = "msg"; & & & & & & & & & &//子线程发送 & & & & & & & & & &handler.sendMessage(msg); & & & & & & & &} & & & & & &}.start(); & & & & & &//在主线程调用,切换了线程 & & & & & &looper.loop(); & & & &} & &});
效果就是将一个TextView的字体大小由32sp变大到64sp,就不演示了
持续更新中 &
代码地址&/franos/androidLearn2
人打赏支持
码字总数 11754
支付宝支付
微信扫码支付
打赏金额: ¥
已支付成功
打赏金额: ¥Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。
中使用Handler造成内存泄露的原因
Handler mHandler = new Handler() {
& & @Override
& & public void handleMessage(Message msg) {
& & & & mImageView.setImageBitmap(mBitmap);
上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建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被持有引用而无法被回收。
内存泄露的危害
只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。
使用Handler导致内存泄露的解决方法
方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
方法二:将Handler声明为静态类。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:
static class MyHandler extends Handler {
& & @Override
& & public void handleMessage(Message msg) {
& & & & mImageView.setImageBitmap(mBitmap);
但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):
static class MyHandler extends Handler {
& & WeakReference&Activity & mActivityR
& & MyHandler(Activity activity) {
& & & & mActivityReference= new WeakReference&Activity&(activity);
& & @Override
& & public void handleMessage(Message msg) {
& & & & final Activity activity = mActivityReference.get();
& & & & if (activity != null) {
& & & & & & mImageView.setImageBitmap(mBitmap);
将代码改为以上形式之后,就算完成了。
延伸:什么是WeakReference?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:5214次
排名:千里之外
(1)(1)(3)(4)您所在的位置: &
Handler 引起的内存泄露
Handler 引起的内存泄露
eoe Android开发者社区
在编写程序时,一般调用API获取服务器数据时,我们都是采取线程来操作的,这里面就需要用到Handler了,但是需要我们注意的是Handler很可能引起内存的泄露。
先看一组简单的代码
public&class&SampleActivity&extends&Activity&{&&&&private&final&Handler&mHandler&=&new&Handler()&{&&&&&@Override&&&&&public&void&handleMessage(Message&msg)&{&&&&&&&&&&&&}&&&}&}&&&
当我们这样写在一个Activity中时,Android Lint会提示我们这样一个
warning: In Android, Handler classes should be static or
leaks might occur.。
意思说:在Android中,Handler
类应该是静态的否则可能发生泄漏。
为什么会是这样呢?
了解一下Handler
当Android程序第一次创建的时候,在主线程同时会创建一个Looper对象。Looper实现了一个简单的消息队列,一个接着一个处理Message对象。程序框架所有主要的事件(例如:屏幕上的点击时间,Activity生命周期的方法等等)都包含在Message对象中,然后添加到Looper的消息队列中,一个一个处理。主线程的Looper存在整个应用程序的生命周期内。
当一个Handler对象在主线程中创建的时候,它会关联到Looper的
message queue
。Message添加到消息队列中的时候Message会持有当前Handler引用,当Looper处理到当前消息的时候,会调用Handler#handleMessage(Message).
在java中,no-static的内部类会 隐式的
持有当前类的一个引用。static的类则没有。
在什么地方引起了内存的泄露呢?再看看下面一段代码
public&class&SampleActivity&extends&Activity&{&&&&private&final&Handler&mHandler&=&new&Handler()&{&&&&&@Override&&&&&public&void&handleMessage(Message&msg)&{&&&&&&&&&&&&}&&&}&&&&@Override&&&protected&void&onCreate(Bundle&savedInstanceState)&{&&&&&super.onCreate(savedInstanceState);&&&&&&&&&&&mHandler.postDelayed(new&Runnable()&{&&&&&&&@Override&&&&&&&public&void&run()&{&}&&&&&},&600000);&&&&&&&&&&&finish();&&&}&}&&&
当Activity结束后,在 Message queue
处理这个Message之前,它会持续存活着。这个Message持有Handler的引用,而Handler有持有Activity(SampleActivity)的引用,这个Activity所有的资源,在这个消息处理之前都不能也不会被回收,所以发生了内存泄露。
解决办法,看下面一段代码
public&class&SampleActivity&extends&Activity&{&&&&&&&&&private&static&class&MyHandler&extends&Handler&{&&&&&private&final&WeakReference&SampleActivity&&mA&&&&&&public&MyHandler(SampleActivity&activity)&{&&&&&&&mActivity&=&new&WeakReference&SampleActivity&(activity);&&&&&}&&&&&&@Override&&&&&public&void&handleMessage(Message&msg)&{&&&&&&&SampleActivity&activity&=&mActivity.get();&&&&&&&if&(activity&!=&null)&{&&&&&&&&&&&&&&&&}&&&&&}&&&}&&&&private&final&MyHandler&mHandler&=&new&MyHandler(this);&&&&&&&&&private&static&final&Runnable&sRunnable&=&new&Runnable()&{&&&&&&&@Override&&&&&&&public&void&run()&{&}&&&};&&&&@Override&&&protected&void&onCreate(Bundle&savedInstanceState)&{&&&&&super.onCreate(savedInstanceState);&&&&&&&&&&&mHandler.postDelayed(sRunnable,&600000);&&&&&&&&&&&finish();&&&}&}&&&
好多人有担心弱引用的Activity回收的情况,这个完全不用担心的,因为我们在这个界面的时候,这个Activity
是不会被回收的,想想 如果我们的这个Activity被回收了,我们的这个界面是怎么存在的呢?
NOTE2: 各位,我是参照AsyncTask
,查看文档和自己的理解,存在我解释不了的地方,还望见谅,如有错误还望见谅。
NOTE3:具体怎么防止泄漏我也说不清楚,但是Handler 下面有一段源码
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());&&&&&&&&&&&&&}&&&【编辑推荐】【责任编辑: TEL:(010)】
关于&&&&的更多文章
Linux内存管理是Linux操作系统非常重要的一个部分,如何做好Linu
大家都知道iOS的发展之迅速,这对于开发者来说,无疑
越来越多的web设计师提出了移动优先的口号,而随着硬
北京时间日,苹果在加利福尼亚召开新品发
JBuilder 2006是一款强大的Java企业级开发平台,其集成了几乎所有的Java技术,涵盖了软件开发生命周期的各个过程。本书深入浅出
Windows Phone专家
Android开发专家
51CTO旗下网站posts - 164,&
comments - 9,&
trackbacks - 0
Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。
中使用Handler造成内存泄露的原因
Handler mHandler = new Handler() {& & @Override& & public void handleMessage(Message msg) {& & & & mImageView.setImageBitmap(mBitmap);& & }}
上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建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被持有引用而无法被回收。
内存泄露的危害
只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。
使用Handler导致内存泄露的解决方法
方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
方法二:将Handler声明为静态类。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:
static class MyHandler extends Handler {& & @Override& & public void handleMessage(Message msg) {& & & & mImageView.setImageBitmap(mBitmap);& & }}
但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):
static class MyHandler extends Handler {& & WeakReference&Activity & mActivityR
& & MyHandler(Activity activity) {& & & & mActivityReference= new WeakReference&Activity&(activity);& & }
& & @Override& & public void handleMessage(Message msg) {& & & & final Activity activity = mActivityReference.get();& & & & if (activity != null) {& & & & & & mImageView.setImageBitmap(mBitmap);& & & & }& & }}
将代码改为以上形式之后,就算完成了。
延伸:什么是WeakReference?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。
阅读(...) 评论()

我要回帖

更多关于 handler内存泄露 的文章

 

随机推荐