Android内存优化11 内存泄漏常见情况2 线程持久化

简介:

线程持久化

Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。所以当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉

内存泄漏1:AsyncTask

void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
        @Override protected Void doInBackground(Void... params) { while(true); } }.execute(); } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View aicButton = findViewById(R.id.at_button); aicButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAsyncTask(); nextActivity(); } });

 

使用LeakCanary检测到的内存泄漏:

这里写图片描述

为什么? 
上面代码在activity中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。

怎么解决? 
自定义静态AsyncTask类,并且让AsyncTask的周期和Activity周期保持一致,也就是在Activity生命周期结束时要将AsyncTask cancel掉。

内存泄漏2:Handler

非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Handler是这样写的:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessageDelayed(msg,1000); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { // 做相应逻辑 } } }; } 

也许有人会说,mHandler并未作为静态变量持有Activity引用,生命周期可能不会比Activity长,应该不一定会导致内存泄露呢,显然不是这样的!

熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandlerActivity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueueLooper都是与线程相关联的,MessageQueueLooper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。

通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式。

public class MainActivity extends AppCompatActivity { private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new MyHandler(this); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private static class MyHandler extends Handler { private WeakReference<MainActivity> activityWeakReference; public MyHandler(MainActivity activity) { activityWeakReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = activityWeakReference.get(); if (activity != null) { if (msg.what == 1) { // 做相应逻辑 } } } } } 

mHandler通过弱引用的方式持有Activity,当GC执行垃圾回收时,遇到Activity就会回收并释放所占据的内存单元。这样就不会发生内存泄露了。

上面的做法确实避免了Activity导致的内存泄露,发送的msg不再已经没有持有Activity的引用了,但是msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息给移除掉。

@Override
protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }

为什么?

创建的Handler对象为匿名类,匿名类默认持有外部类activity, Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。这时activity被handler持有
handler被message持有,message被messagequeue持有,message queue被loop持有,主线程的loop是全局存在的,这时就造成activity被临时性持久化,造成临时性内存泄漏

怎么解决? 
可以由上面的结论看出,产生泄漏的根源在于匿名类持有Activity的引用,因此可以自定义Handler和Runnable类并声明成静态的内部类,来解除和Activity的引用。或者在activity 结束时,将发送的Message移除

内存泄漏3:Thread

代码如下: 
MainActivity.java

void spawnThread() {
    new Thread() {
        @Override public void run() { while(true); } }.start(); } View tButton = findViewById(R.id.t_button); tButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { spawnThread(); nextActivity(); } }); 

 

为什么? 
Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。看到这相信你应该也是心中有答案了吧 : 我在每一个MainActivity中都创建了一个线程,此线程会持有MainActivity的引用,即使退出Activity当前线程因为是直接被GC Root引用所以不会被回收掉,导致MainActivity也无法被GC回收

怎么解决? 
当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉

内存泄漏4:Timer Tasks

TimerTimerTask在Android中通常会被用来做一些计时或循环任务,比如实现无限轮播的ViewPager

public class MainActivity extends AppCompatActivity { private ViewPager mViewPager; private PagerAdapter mAdapter; private Timer mTimer; private TimerTask mTimerTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); mTimer.schedule(mTimerTask, 3000, 3000); } private void init() { mViewPager = (ViewPager) findViewById(R.id.view_pager); mAdapter = new ViewPagerAdapter(); mViewPager.setAdapter(mAdapter); mTimer = new Timer(); mTimerTask = new TimerTask() { @Override public void run() { MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { loopViewpager(); } }); } }; } private void loopViewpager() { if (mAdapter.getCount() > 0) { int curPos = mViewPager.getCurrentItem(); curPos = (++curPos) % mAdapter.getCount(); mViewPager.setCurrentItem(curPos); } } private void stopLoopViewPager() { if (mTimer != null) { mTimer.cancel(); mTimer.purge(); mTimer = null; } if (mTimerTask != null) { mTimerTask.cancel(); mTimerTask = null; } } @Override protected void onDestroy() { super.onDestroy(); stopLoopViewPager(); } } 

当我们Activity销毁的时,有可能Timer还在继续等待执行TimerTask,它持有Activity的引用不能被回收,因此当我们Activity销毁的时候要立即cancelTimerTimerTask,以避免发生内存泄漏。

为什么? 
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。

怎么解决? 
在适当的时机进行Cancel。

内存泄漏5:属性动画造成内存泄露



动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

@Override
protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); } 





    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/8473376.html ,如需转载请自行联系原作者

相关文章
|
29天前
|
编解码 算法 Java
构建高效的Android应用:内存优化策略详解
随着智能手机在日常生活和工作中的普及,用户对移动应用的性能要求越来越高。特别是对于Android开发者来说,理解并实践内存优化是提升应用程序性能的关键步骤。本文将深入探讨针对Android平台的内存管理机制,并提供一系列实用的内存优化技巧,以帮助开发者减少内存消耗,避免常见的内存泄漏问题,并确保应用的流畅运行。
|
5天前
|
移动开发 Android开发 开发者
构建高效Android应用:采用Kotlin进行内存优化的策略
【4月更文挑战第18天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,由于设备和版本的多样性,确保应用流畅运行且占用资源少是一大挑战。本文将探讨使用Kotlin语言开发Android应用时,如何通过内存优化来提升应用性能。我们将从减少不必要的对象创建、合理使用数据结构、避免内存泄漏等方面入手,提供实用的代码示例和最佳实践,帮助开发者构建更加高效的Android应用。
5 0
|
7天前
|
缓存 移动开发 Java
构建高效的Android应用:内存优化策略
【4月更文挑战第16天】 在移动开发领域,尤其是针对资源有限的Android设备,内存优化是提升应用性能和用户体验的关键因素。本文将深入探讨Android应用的内存管理机制,分析常见的内存泄漏问题,并提出一系列实用的内存优化技巧。通过这些策略的实施,开发者可以显著减少应用的内存占用,避免不必要的后台服务,以及提高垃圾回收效率,从而延长设备的电池寿命并确保应用的流畅运行。
|
7天前
|
存储 缓存 监控
Java内存管理:垃圾回收与内存泄漏
【4月更文挑战第16天】本文探讨了Java的内存管理机制,重点在于垃圾回收和内存泄漏。垃圾回收通过标记-清除过程回收无用对象,Java提供了多种GC类型,如Serial、Parallel、CMS和G1。内存泄漏导致内存无法释放,常见原因包括静态集合、监听器、内部类、未关闭资源和缓存。内存泄漏影响性能,可能导致应用崩溃。避免内存泄漏的策略包括代码审查、使用分析工具、合理设计和及时释放资源。理解这些原理对开发高性能Java应用至关重要。
|
10天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
21天前
|
安全 Linux API
Android进程与线程
Android进程与线程
18 0
|
22天前
|
缓存 算法 Java
Java内存管理:优化性能和避免内存泄漏的关键技巧
综上所述,通过合适的数据结构选择、资源释放、对象复用、引用管理等技巧,可以优化Java程序的性能并避免内存泄漏问题。
25 5
|
29天前
|
缓存 Linux iOS开发
【C/C++ 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind 】Linux 下 Valgrind 工具的全面使用指南
【C/C++ 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind 】Linux 下 Valgrind 工具的全面使用指南
64 1
|
30天前
|
缓存 移动开发 Java
构建高效Android应用:内存优化实战指南
在移动开发领域,性能优化是提升用户体验的关键因素之一。特别是对于Android应用而言,由于设备和版本的多样性,内存管理成为开发者面临的一大挑战。本文将深入探讨Android内存优化的策略和技术,包括内存泄漏的诊断与解决、合理的数据结构选择、以及有效的资源释放机制。通过实际案例分析,我们旨在为开发者提供一套实用的内存优化工具和方法,以构建更加流畅和高效的Android应用。
|
1月前
|
存储 JSON 监控
Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
【2月更文挑战第30天】Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
14 1

相关实验场景

更多