Android几种常见的内存泄漏+解决方案
项目源代码
1.概述
Android检测性能问题,排查性能问题至关重要,特别是在工控,自动化领域,一点点的内存泄漏,随时间累计起来造成的OOM问题就会很致命
2.工具
1> LeakCanary,用于检测我们代码中的内存泄漏,添加依赖
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
}
这样就可以啦,2.2版本默认已经帮我们初始化了,我们不需要进行初始化操作,但要注意的是它默认是只在debug环境在会被初始化
运行程序,查看Logca日志
D LeakCanary: Installing AppWatcher
2> AS 自带Profiler
AS低版本不支持Android10。Profiler在运行程序时才会显示,同时只能存在一个Profiler的。也就是说,如果你一个项目已经运行并且打开Proiler面板,那么是无法在另外一个项目再打开Profiler的,你必须关闭之前的Profiler,才能打开新的窗口
在项目没有运行的时候,这个面板默认是隐藏的,只有运行项目才会显示出来。运行项目后默认会在左侧显示出当前运行的手机和程序,我们选择第二行的MEMORY即可
如果左侧面板没有显示,就点击红圈中的+号选择自己需要监控的设备和程序

双击MEMORY的任意一点可以获得特定时刻的内存信息

3> 检测方法
Android内存泄漏本质上就是新Actiivty产生后旧Activity却无法被及时的销毁,所以我们可以通过系统返回键(原本点击后Actiivty应该被销毁)配合LeakCanary来验证我们的内存泄漏
不过退出Activity Profiler就会终止了,所以也可以通过跳转到新Activity+finish原来的Activity来进行同样的操作,为了快速检验,下面的示例我都是通过这种方法来验证
3.例子
1> 单例模式引发的内存泄漏
class CommUtil(private val context: Context) {
companion object {
//AS提示我们可能造成内存泄漏
@SuppressLint("StaticFieldLeak")
private var instance: CommUtil? = null
fun getInstance(context: Context): CommUtil? {
if (instance == null) {
instance = CommUtil(context)
}
return instance
}
}
}
Activity中使用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CommonUtil.getInstance(this)//单例模式引发的内存泄漏
}
}
写好之后旋转屏幕,当旋转屏幕时Actiivty会被重新的创建,是由于原先的单例模式还引用了旧Activity,导致久Actiivty无法被GC自动回收,就造成了内存泄漏
LeakCanary检测:
我们之前已经依赖了LeakCanary,这时候旋转屏幕我们的通知栏就会弹出消息,点击它就会帮我们自动生成报告

AS Profiler检测:
在运行程序后点击下载Logo,会帮我们记录一小段时间的内存占用情况,右下角的漏斗Logo点击后我们就可以在输入框中输入我们想要搜索的类名,搜索完成之后点击MainActivity可以看到有两个实例,分别是旧Activity和旋转后新生成的Actiivty

垃圾桶Logo为主动GC按钮,通常Actiivty被旋转时会创建新的Activity,就实例还是会存在于栈中,当我们的系统内存充足时并不会去主动回收销毁,只有当系统的内存不足的时候,才会去销毁这些栈中没有被用到的Activity
而我们主动的点击GC按钮,就会主动将当前栈中没有被显示的页面释放掉,
如果我们点击垃圾桶进行主动GC,再点击下载按钮,搜索MainActivity理论上应该只剩下一个实例(当前页面显示的Activity),但由于我们单例模式内存泄漏的原因,旧实例并不会被释放掉,由此可以看出确实是产生了内存泄漏
解决方法:
单例模式使用Application的Context而非Activity的Context,因为Application本来就是伴随着程序一直在启动的,所以不存在内存泄漏
class App : Application() {
//对外提供的Context
companion object {
var _context:Application? = null
fun getContext(): Context {
return _context!!
}
}
override fun onCreate() {
super.onCreate()
_context = this
}
}
引用Application的Context
CommonUtil.getInstance(App.getContext())//引用Application的Context正确
2>非静态内部类创建静态实例造成的内存泄漏
public class InnerActivity extends AppCompatActivity {
private static TextResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inner);
mResource = new TextResource();
}
//错误示例
private class TextResource {
}
}
LeakCanary检测:

运行Profiler观察

该实例在退出Actiivty时才会被检测到,解决方法为将类改成静态内部类
//错误示例
private static class TextResource {
}
修改后运行Profiler观察也可以看到修改后mResource变量小了很多

3>线程类引发的内存泄漏
/**
* 线程问题引发的内存泄漏
*/
class ThreadActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_thread)
Thread(Runnable { SystemClock.sleep(6 * 60 * 1000.toLong()) }).start()
}
}
检测:

解决方法:
在onDestroy时结束你的线程操作
/**
* 线程问题引发的内存泄漏
*/
class ThreadActivity : AppCompatActivity() {
private var mThread: Thread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_thread)
/*
* 错误案例
* 异步任务和Runable都是一个匿名内部类,因此他们对当前Activity都有一个隐式引用,如果Activity在退出之前
* 任务还未完成,那么将导致Activity的内存资源无法回收,造成内存泄漏
*/
// Thread(Runnable { SystemClock.sleep(6 * 60 * 1000.toLong()) }).start()
mThread= Thread(MyRunnable())
mThread?.start()
}
override fun onDestroy() {
super.onDestroy()
Log.d("ThreadActivity","onDestroy")
mThread?.join()
}
internal class MyRunnable : Runnable {
override fun run() {
SystemClock.sleep((6 * 60 * 1000).toLong())
}
}
}
参考自:
https://blog.csdn.net/lijia1201900857/article/details/82877316(单例模式引发的内存泄漏)
网友评论