美文网首页面试题
Android几种常见的内存泄漏+解决方案

Android几种常见的内存泄漏+解决方案

作者: 唐_夏影 | 来源:发表于2020-03-01 18:56 被阅读0次

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即可

如果左侧面板没有显示,就点击红圈中的+号选择自己需要监控的设备和程序

选择项目.png

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

特定内存信息.png
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,这时候旋转屏幕我们的通知栏就会弹出消息,点击它就会帮我们自动生成报告

具体详情.jpg

AS Profiler检测:

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

微信图片_20200225190036.png

垃圾桶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检测:

非静态内存生成静态变量.jpg

运行Profiler观察

inner_failed.png

该实例在退出Actiivty时才会被检测到,解决方法为将类改成静态内部类

  //错误示例
    private static class TextResource {

    }

修改后运行Profiler观察也可以看到修改后mResource变量小了很多

inner_success.png
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()
    }
}

检测:

线程引发的内存泄漏.jpg

解决方法:

在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(单例模式引发的内存泄漏)

相关文章

  • Android几种常见的内存泄漏+解决方案

    Android几种常见的内存泄漏+解决方案 项目源代码 1.概述 Android检测性能问题,排查性能问题至关重要...

  • Android内存泄漏原因及解决的总结

    分三步说明Android内存泄漏的原因及解决,“内存泄漏与内存溢出的区别”,“引用方式”,“常见引发原因与解决方案...

  • Android - 常见内存泄露

    转自:Android常见的几种内存泄漏小结 1.什么是内存泄露: 在Android程序开发中,当一个对象已经不需要...

  • 2016.8

    8.4 Android 开发常见错误解决方案(不断更新中)安卓面试题 5 – 关于内存泄漏 Android 开发人...

  • Android常见的几种内存泄漏

    一、背景 最近在项目的版本迭代中,出现了一些内存问题的小插曲,然后自己花了一些时间优化了APP运行时内存大小的问题...

  • Android性能分析的几种方法

    Android性能分析的几种方法 通过Memory Monitor 查找内存泄漏Android Profiler中...

  • Android内存泄漏检测和定位

    建议阅读Android常见内存泄漏这篇文章,本文的例子来源于文章中的内存泄漏典型例子 内存泄漏检测工具 Profi...

  • Handler究竟是如何泄漏内存的

    常见有内存泄漏的写法 解决方案 为什么会出现内存泄漏 注:以下均以在主线程new Handler()举例,在其他线...

  • 深入理解Java中的内存泄漏

    内存泄漏内存泄漏发生的原因造成内存泄露的常见情形内存泄露的解决方案 Java的一个最显著的优势是内存管理。你只需要...

  • android性能优化总结

    1,UI优化:这篇文章总结的不错 2,内存泄漏优化 常见的几种形式: 资源对象没关闭造成的内存泄漏: 资源对象没关...

网友评论

    本文标题:Android几种常见的内存泄漏+解决方案

    本文链接:https://www.haomeiwen.com/subject/fgmhkhtx.html