Android 内存优化——常见内存泄露及优化方案

看到了一篇关于内存泄漏的文章后,就想着分享给大家,最后一起学习,一起进步:

如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,这种情况下就是内存泄漏。

在Android开发中,一些不好的编程习惯会导致我们开发的app存在内存泄漏的情况,下面介绍一些在Android开发中常见的内存泄漏场景及优化方案。

单例导致内存泄漏

单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄漏。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄漏。

public class AppSettings {
	private static AppSettings sInstance;
	private Context mContext;
	private AppSettings(Context context) {
		this.mContext = context;
	}
	public static AppSettings getInstance(Context context) {
		if (sInstance == null) {
		sInstance = new AppSettings(context);
		}
		return sInstance;
	}
}

向上面代码中这样的单例,如果我们再调用getInstance(Context context)方法的时候传入的context参数是Activity,Service等上下文,就会导致内存泄漏。

以Activity为例,当我们启动一个Activity,并调用getInstance(Context context)方法去获取AppSettings的单例,传入Activity.this作为context,这样AppSettings类的单例sInstance就持有了Activity的引用,当我们退出Activity时,该Activity就没有了,但是因为sInstance作为静态单例,(在应用程序的整个生命周期中存在)会继续持有这个Activity的引用,导致这个Activity对象无法被回收释放,这就造成了内存泄漏。

为了避免这样单例导致内存泄漏,我们可以将context参数改为全局的上下文。

private AppSettings(Context context) {
	this.mContext = context.getApplicationContext();
}

全局的上下文Application Context就是应用程序的上下文,和单例的生命周期一样长,这样就避免了内存泄漏。

单例模式对应 应用程序的生命周期,所以我们再构造单例的时候尽量避免使用Activity的上下文,而是使用Appliction的上下文。

静态变量导致内存泄漏

静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。

比如下面这样的情况,在Activity中为了避免重复的创建info,将info作为静态变量:

public class MainActivity extends AppCompatActivity {
private static Info sInfo;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
		if (sInfo != null) {
			sInfo = new Info(this);
		}
	}
}
class Info {
	public Info(Activity activity) {
	}
}

Info作为Activity的静态成员,并且持有Activity的引用,但是sInfor作为静态变量,生命周期肯定比Activity长。所以当Activity退出后,sInfor仍然引用了Activity,Activity不能被回收,这就导致了内存泄漏。

在Android开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄漏,所以我们再新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系,并且尽量少地使用静态持有的变量,以避免发生内存泄漏。当然,我们也可以在适当的时候将静态变量重置为null,使其不再持用引用,这样也可以避免内存泄漏。

非静态内部内导致内存泄漏

非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄漏。

非静态内部类导致的内存泄漏在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.sendMessage(msg);
	}
	private Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			if (msg.what == 1) {
			// 做相应逻辑
			}
		}
	};
}

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

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

给大家看一下源码,如果想学习Handler消息机制可以看看这个博客https://blog.csdn.net/sjw890821sjw/article/details/142138517写的很好:
在这里插入图片描述

通常在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的回调和发送的消息给移除掉。

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

非静态内部类造成内存泄漏还有一种情况就是使用Thread或者AsyncTask。

比如在Activity中直接new一个子线程Thread:

public class MainActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
			new Thread(new Runnable() {
				@Override
				public void run() {
				// 模拟相应耗时逻辑
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}).start();
	}
}

或者直接新建AsynTask异步任务:

public class MainActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
		new AsyncTask<Void, Void, Void>() {
			@Override
			protected Void doInBackground(Void... params) {
			// 模拟相应耗时逻辑
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				return null;
			}
		}.execute();
	}
}

很多初学者都会像上面这样新建线程和异步任务,殊不知这样的写法非常地不友好,这种方式新建的子线程Thread和AsynTask都是匿名内部类对象,默认就隐式的持有外部Activity的引用,导致Activity内存泄漏。要避免内存泄漏的话还是需要像上面Handler一样使用静态内部类+弱引用的方式。

未取消注册或回调导致内存泄漏

比如我们在Activity中注册广播,如果在Activity销毁后不再取消注册,那么这个广播会一直存在系统中,同上面所说的非静态内部类一样持有Activity引用,导致内存泄漏。因此注册广播后在Activity销毁后一定要取消注册。

public class MainActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		this.registerReceiver(mReceiver, new IntentFilter());
	}
	private BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
		// 接收到广播需要做的逻辑
		}
	};
	@Override
	protected void onDestroy() {
		super.onDestroy();
		this.unregisterReceiver(mReceiver);
	}
}

在注册观察者模式的时候,如果不及时取消也会造成内存泄漏,比如使用Retrofit+RxJava注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候取消注册。

Timer和TimerTask导致内存泄漏

Timer和TimerTask在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销毁的时候要立即cancel掉Timer和TimerTask,以避免发生内存泄漏。

集合中的对象未清理造成内存泄漏

这个比较好理解,如果一个对象放入到ArrayList,HashMap等集合中,这个集合就会持有该对象的引用。当我们不在需要这个对象时,也没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄漏。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄漏了,所以在使用集合时要将不用的对象从集合remove,或者clear集合,以避免内存泄漏。

资源未关闭或释放导致内存泄漏

在使用IO,File流或者Sqlite,Cursor等资源时要及时关闭,这些资源在进行读写操作时通常都会使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄漏。因此我们再不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄漏。

属性动画造成内存泄漏

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

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

WebView造成内存泄漏

关于WebView的内存泄漏,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。

另外再查阅webView内存泄漏相关资料时看到这种情况:

WebView下面的Callback持有Activity引用,造成WebView内存无法释放,即使是调用了WebView.destory()等方法都无法解决问题(Android5.1之后)

最终的解决方案是:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView。详细分析过程请参考这篇文章:https://blog.csdn.net/xygy8860/article/details/53334476?utm_source=itdadao&utm_medium=referral)(http://blog.csdn.net/xygy8860/article/details/53334476)[WebView

@Override 
protected void onDestroy() {
	super.onDestroy();
	// 先从父控件中移除 WebView
	mWebViewContainer.removeView(mWebView);
	mWebView.stopLoading();
	mWebView.getSettings().setJavaScriptEnabled(false);
	mWebView.clearHistory();
	mWebView.removeAllViews();
	mWebView.destroy();
}

总结:

内存泄漏在Android内存优化是一个比较重要的一个方面,很多时候程序中发生了内存泄漏我们不一定就能注意到,所有在编码的过程要养成良好的习惯。总结下来只要做到以下这几点就能避免大多数情况下的内存泄漏:

构造单例的时候尽量别用Activity的引用;
静态引用时注意应用对象的置空或者少用静态引用;
使用静态内部类+软引用代替非静态内部类;
及时取消广播或者观察者注册;
耗时任务,属性动画在Activity销毁时记得Cancel;
文件流,Cursor等资源及时关闭;
Activity销毁时WebView的移除和销毁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/896219.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【element-tiptap】如何修改选中内容时的背景颜色?

前言&#xff1a;element-tiptap 用鼠标选中内容的时候&#xff0c;背景颜色跟系统设置的主题有关&#xff0c;比如的我的就是卡哇伊的pink&#xff0c;默认是淡蓝色 但是我们观察一下语雀&#xff0c;背景颜色是它规定好的颜色 这篇文章来探索一下&#xff0c;怎么自己规定选…

教学平台的智能化升级:Spring Boot应用

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

除GOF23种设计模式之简单工厂模式

文章目录 1. 简介2. 代码2.1 抽象类&#xff1a;Course.java2.2 产品A:JavaCourse.java2.3 产品B:PythonCourse.java2.4 工厂:CourseFactory.java2.5 测试&#xff1a;Test.java 3. 心得参考链接&#xff08;无&#xff09; 1. 简介 简单工厂模式(Simple Factory Patern):又称…

数据结构4——栈

1. 栈的概念及结构 栈的概念&#xff1a; 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则…

【SuperHotSwap】IDEA零配置热更新插件升级

往期往期插件v1.0.0发布的时候我发表了一篇文章&#xff0c;如下&#xff1a; 支持功能 如今插件迭代了数个版本&#xff0c;现在迎来了v1.9.0版本的重大升级。如下是支持功能。 支持功能是否支持说明MybatisXML热更新√Class热更新√增强功能需安装dcevm补丁。支持动态新增类…

git rebase的常用场景: 交互式变基, 变基和本地分支基于远端分支的变基

文章目录 作用应用场景场景一&#xff1a;交互式变基(合并同一条线上的提交记录) —— git rebase -i HEAD~2场景二&#xff1a;变基(合并分支) —— git rebase [其他分支名称]场景三&#xff1a;本地分支与远端分支的变基 作用 使git的提交记录变得更加简洁 应用场景 场景…

Java爬虫:获取数据的入门详解

在数字化时代&#xff0c;数据已成为最宝贵的资产之一。无论是市场研究、客户洞察还是产品开发&#xff0c;获取大量数据并从中提取有价值的信息变得至关重要。Java&#xff0c;作为一种成熟且功能强大的编程语言&#xff0c;为编写爬虫提供了强大的支持。Java爬虫可以帮助我们…

如何提高外贸网站在谷歌的收录速度?

外贸企业在进行网络推广时&#xff0c;经常遇到网站页面无法被谷歌快速收录的问题。即使你的网站内容优质、设计精美&#xff0c;如果没有被谷歌收录&#xff0c;就等于失去了被客户发现的机会&#xff0c;GSI谷歌快速收录服务就是为了解决这一问题而诞生的。它不仅能够帮助网站…

5G智慧医疗的实践先锋:SR830-E工业路由器的理性应用

在医疗科技日新月异的今天&#xff0c;5G技术无疑为智慧医疗注入了新的活力。然而&#xff0c;技术的进步不应仅停留在理论层面&#xff0c;更应该在实践中发挥其真正价值。今天&#xff0c;我们就来探讨SR830-E工业路由器如何在实际医疗场景中扮演关键角色&#xff0c;推动5G智…

vscode 远程linux服务器 连接git

vscode 远程linux服务器 连接git 1. git 下载2. git 配置1&#xff09;github 设置2&#xff09;与github建立连接linux端&#xff1a;创建密钥github端&#xff1a;创建ssh key 3. 使用1&#xff09;初始化repository2&#xff09;commit 输入本次提交信息&#xff0c;提交到本…

UE5 圆周运动、贝塞尔曲线运动、贝塞尔曲线点

圆周运动 贝塞尔曲线路径运动 蓝图函数库创建贝塞尔曲线点 // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "MyBlu…

从MySQL到OceanBase离线数据迁移的实践

本文作者&#xff1a;玉璁&#xff0c;OceanBase 生态产品技术专家。工作十余年&#xff0c;一直在基础架构与中间件领域从事研发工作。现负责OceanBase离线导数产品工具的研发工作&#xff0c;致力于为 OceanBase 建设一套完善的生态工具体系。 背景介绍 在互联网与云数据库技…

【码农必备】CasaOS香橙派安装Code server+cpolar让远程开发更轻松

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基于Spring Boot、Vue和MyBatis的前后端分离座位管理系统:增删改查功能入门指南

在项目开发和工作实践中&#xff0c;创作灵感往往来自于对日常经验的总结与反思。通过记录技术难点和解决方案&#xff0c;不仅可以加深对问题的理解&#xff0c;还能为后续项目的优化提供参考。与此同时&#xff0c;撰写技术笔记、分享职场心得&#xff0c;不仅是对自己成长的…

一款基于 Vue 3 的现代化数据可视化组件库,功能强大,颜值爆表,开发者必备!(带私活源码)

Vue Data UI 是一款基于 Vue 3 的现代化数据可视化组件库&#xff0c;专为开发者提供强大的数据展示功能&#xff0c;旨在帮助用户通过图形化手段生动地讲述数据故事。该库由开源开发者 Graphieros 创建和维护&#xff0c;专注于提升图形渲染性能与交互体验&#xff0c;并致力于…

基于SSM汽车零部件加工系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;员工管理&#xff0c;经理管理&#xff0c;零件材料管理&#xff0c;产品类型管理&#xff0c;产品信息管理&#xff0c;产品出库管理&#xff0c;产品入库管理 员工账号功能包括&#xff1a;系统首页…

linuxdeployqt打包发布软件

文章目录 参考一、安装linuxdeployqt二、配置Qt的环境变量三、打包应用程序四、打包成deb包配置*.desktop桌面快捷方式文件五、创建deb包之control文件六、创建deb包之postrm文件(可以不创建)七、使用dpkg命令构建deb包八、deb包的安装与卸载参考 使用linuxdeployqt在linux下…

电影评论网站开发:Spring Boot技术指南

3系统分析 3.1可行性分析 通过对本电影评论网站实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本电影评论网站采用SSM框架&#xff0c;JAVA作为开发语言&#…

深入解析缓存与数据库数据不一致问题

缓存层是提高系统响应速度和扩展性的关键组件。然而&#xff0c;缓存层的引入也带来了数据一致性的挑战。 当数据库中的数据发生变化时&#xff0c;如何确保这些变化能够及时且准确地反映到缓存中&#xff0c;是确保用户体验和系统可靠性的重要问题。 1. 数据一致性 首先&am…

排序算法详解

稳定性 在排序算法中&#xff0c;稳定性是一个重要的概念&#xff0c;指的是在排序过程中&#xff0c;如果两个元素的值相等&#xff0c;它们在排序后的相对位置与排序前的相对位置保持不变的特性。 稳定排序与不稳定排序 稳定排序&#xff1a;在排序时&#xff0c;相等的元素…