安卓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

相关文章

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

解决Nginx启动报错Job for nginx.service failed because the control process exited with error code问题

《解决Nginx启动报错Jobfornginx.servicefailedbecausethecontrolprocessexitedwitherrorcode问题》Nginx启... 目录一、报错如下二、解决原因三、解决方式总结一、报错如下Job for nginx.service failed bec

SysMain服务可以关吗? 解决SysMain服务导致的高CPU使用率问题

《SysMain服务可以关吗?解决SysMain服务导致的高CPU使用率问题》SysMain服务是超级预读取,该服务会记录您打开应用程序的模式,并预先将它们加载到内存中以节省时间,但它可能占用大量... 在使用电脑的过程中,CPU使用率居高不下是许多用户都遇到过的问题,其中名为SysMain的服务往往是罪魁

MySQ中出现幻读问题的解决过程

《MySQ中出现幻读问题的解决过程》文章解析MySQLInnoDB通过MVCC与间隙锁机制在可重复读隔离级别下解决幻读,确保事务一致性,同时指出性能影响及乐观锁等替代方案,帮助开发者优化数据库应用... 目录一、幻读的准确定义与核心特征幻读 vs 不可重复读二、mysql隔离级别深度解析各隔离级别的实现差异