安卓Handler当做内部类,导致内存泄露的问题

2024-02-28 07:38

本文主要是介绍安卓Handler当做内部类,导致内存泄露的问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

this handler should be static or leaks might occur

 

How to Leak a Context: Handlers & Inner Classes

Context是怎么泄露的:Handlers & Inner Classes

Consider the following code:

考虑下面的代码

public class SampleActivity extends Activity {private final Handler mLeakyHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// ... }}
}

While not readily obvious, this code can cause cause a massive memory leak. Android Lint will give the following warning:
尽管不是那么明显,但这段代码会导致大量内存泄露。Android的Lint工具会给出如下警告:
In Android, Handler classes should be static or leaks might occur.
在Android中,Handler类应该被静态修饰,否则可能会出现内存泄露。
But where exactly is the leak and how might it happen? Let's determine the source of the problem by first documenting what we know:
但是究竟是在哪里泄露了内存,如何泄露的,下面让我们根据已有的知识来分析下问题的原因:
When an Android application first starts, the framework creates a Looper object for the application's main thread. A Looper implements a simple message queue, processing Message objects in a loop one after another. All major application framework events (such as Activity lifecycle method calls, button clicks, etc.) are contained inside Message objects, which are added to the Looper's message queue and are processed one-by-one. The main thread's Looper exists throughout the application's lifecycle.
当Android应用首次启动时,framework会在应用的UI线程创建一个Looper对象。Looper实现了一个简单的消息队列并且一个接一个的处理队列中的消息。应用的所有事件(比如Activity生命周期回调方法,按钮点击等等)都会被当做一个消息对象放入到Looper的消息队列中,然后再被逐一执行。UI线程的Looper存在于整个应用的生命周期内。
When a Handler is instantiated on the main thread, it is associated with the Looper's message queue. Messages posted to the message queue will hold a reference to the Handler so that the framework can call Handler#handleMessage(Message) when the Looper eventually processes the message.
当在UI线程中创建Handler对象时,它就会和UI线程中Looper的消息队列进行关联。发送到这个消息队列中的消息会持有这个Handler的引用,这样当Looper最终处理这个消息的时候framework就会调用Handler#handleMessage(Message)方法来处理具体的逻辑。
In Java, non-static inner and anonymous classes hold an implicit reference to their outer class. Static inner classes, on the other hand, do not.
在Java中,非静态的内部类或者匿名类会隐式的持有其外部类的引用,而静态的内部类则不会。
So where exactly is the memory leak? It's very subtle, but consider the following code as an example:
那么,内存到底是在哪里泄露的呢?其实泄漏发生的还是比较隐晦的,但是再看看下面这段代码:

public class SampleActivity extends Activity {private final Handler mLeakyHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {// ...}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Post a message and delay its execution for 10 minutes.mLeakyHandler.postDelayed(new Runnable() {@Overridepublic void run() { /* ... */ }}, 1000 * 60 * 10);// Go back to the previous Activity.finish();}
}

When the activity is finished, the delayed message will continue to live in the main thread's message queue for 10 minutes before it is processed. The message holds a reference to the activity's Handler, and the Handler holds an implicit reference to its outer class (the SampleActivity, in this case). This reference will persist until the message is processed, thus preventing the activity context from being garbage collected and leaking all of the application's resources. Note that the same is true with the anonymous Runnable class on line 15. Non-static instances of anonymous classes hold an implicit reference to their outer class, so the context will be leaked.
当activity被finish的时候,延迟发送的消息仍然会存活在UI线程的消息队列中,直到10分钟后它被处理掉。这个消息持有activity的Handler的引用,Handler又隐式的持有它的外部类(这里就是SampleActivity)的引用。这个引用会一直存在直到这个消息被处理,所以垃圾回收机制就没法回收这个activity,内存泄露就发生了。需要注意的是:15行的匿名Runnable子类也会导致内存泄露。非静态的匿名类会隐式的持有外部类的引用,所以context会被泄露掉
To fix the problem, subclass the Handler in a new file or use a static inner class instead. Static inner classes do not hold an implicit reference to their outer class, so the activity will not be leaked. If you need to invoke the outer activity's methods from within the Handler, have the Handler hold a WeakReference to the activity so you don't accidentally leak a context. To fix the memory leak that occurs when we instantiate the anonymous Runnable class, we make the variable a static field of the class (since static instances of anonymous classes do not hold an implicit reference to their outer class):

解决这个问题也很简单:在新的类文件中实现Handler的子类或者使用static修饰内部类。静态的内部类不会持有外部类的引用,所以activity不会被泄露。如果你要在Handler内调用外部activity类的方法的话,可以让Handler持有外部activity类的弱引用,这样也不会有泄露activity的风险。关于匿名类造成的泄露问题,我们可以用static修饰这个匿名类对象解决这个问题,因为静态的匿名类也不会持有它外部类的引用。

public class SampleActivity extends Activity {/*** Instances of static inner classes do not hold an implicit* reference to their outer class.*/private static class MyHandler extends Handler {private final WeakReference<SampleActivity> mActivity;public MyHandler(SampleActivity activity) {mActivity = new WeakReference<SampleActivity>(activity);}@Overridepublic void handleMessage(Message msg) {SampleActivity activity = mActivity.get();if (activity != null) {// ...}}}private final MyHandler mHandler = new MyHandler(this);/*** Instances of anonymous classes do not hold an implicit* reference to their outer class when they are "static".*/private static final Runnable sRunnable = new Runnable() {@Overridepublic void run() { /* ... */ }};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Post a message and delay its execution for 10 minutes.mHandler.postDelayed(sRunnable, 1000 * 60 * 10);// Go back to the previous Activity.finish();}
}

The difference between static and non-static inner classes is subtle, but is something every Android developer should understand. What's the bottom line? Avoid using non-static inner classes in an activity if instances of the inner class could outlive the activity's lifecycle. Instead, prefer static inner classes and hold a weak reference to the activity inside.
静态和非静态内部类的区别是非常微妙的,但这个区别是每个Android开发者应该清楚的。那么底线是什么?如果要实例化一个超出activity生命周期的内部类对象,避免使用非静态的内部类。建议使用静态内部类并且在内部类中持有外部类的弱引用。

 

这篇关于安卓Handler当做内部类,导致内存泄露的问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/754814

相关文章

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作

如何解决mysql出现Incorrect string value for column ‘表项‘ at row 1错误问题

《如何解决mysql出现Incorrectstringvalueforcolumn‘表项‘atrow1错误问题》:本文主要介绍如何解决mysql出现Incorrectstringv... 目录mysql出现Incorrect string value for column ‘表项‘ at row 1错误报错

如何解决Spring MVC中响应乱码问题

《如何解决SpringMVC中响应乱码问题》:本文主要介绍如何解决SpringMVC中响应乱码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC最新响应中乱码解决方式以前的解决办法这是比较通用的一种方法总结Spring MVC最新响应中乱码解

pip无法安装osgeo失败的问题解决

《pip无法安装osgeo失败的问题解决》本文主要介绍了pip无法安装osgeo失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 进入官方提供的扩展包下载网站寻找版本适配的whl文件注意:要选择cp(python版本)和你py

解决Java中基于GeoTools的Shapefile读取乱码的问题

《解决Java中基于GeoTools的Shapefile读取乱码的问题》本文主要讨论了在使用Java编程语言进行地理信息数据解析时遇到的Shapefile属性信息乱码问题,以及根据不同的编码设置进行属... 目录前言1、Shapefile属性字段编码的情况:一、Shp文件常见的字符集编码1、System编码

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

Redis解决缓存击穿问题的两种方法

《Redis解决缓存击穿问题的两种方法》缓存击穿问题也叫热点Key问题,就是⼀个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,本文给大家介绍了Re... 目录引言解决办法互斥锁(强一致,性能差)逻辑过期(高可用,性能优)设计逻辑过期时间引言缓存击穿:给