返回顶部
首页 > 资讯 > 移动开发 >Android内存优化操作方法梳理总结
  • 661
分享到

Android内存优化操作方法梳理总结

摘要

目录内存泄露非静态内部类创建静态实例注册对象未注销或资源对象未关闭类的静态变量引用耗费资源过多的实例Handler引发的内存泄露集合引发的内存泄露检测工具LeakCanaryAndr

内存泄露

内存泄漏就是在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小,通俗点讲,就是无法回收无用对象。这里总结了实际开发中常见的一些内存泄露的场景示例和解决方案。

非静态内部类创建静态实例

该实例的生命周期和应用一样长,非静态内部类会自动持有外部类的引用,这就导致该静态实例一直持有外部类Activity的引用。

class MemoryActivity : AppCompatActivity() {
    compaNIOn object {
        var test: Test? = null
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory)
        test = Test()
    }
    inner class Test {
    }
}

解决方案:将非静态内部类改为静态内部类

class MemoryActivity : AppCompatActivity() {
    companion object {
        var test: Test? = null
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory)
        test = Test()
    }
     //Kotlin的静态内部类
     class Test {
    }
}

注册对象未注销或资源对象未关闭

注册了像BraodcastReceiver,EventBus这种,没有在页面销毁时注销的话,会引发泄露问题,所以应该在Activity销毁时及时注销。

类的静态变量引用耗费资源过多的实例

类的静态变量生命周期等于应用程序的生命周期,若其引用耗资过多的实例,如Context,当引用实例需结束生命周期时,会因静态变量的持有而无法被回收,从而出现内存泄露,这种情况比较常见的有单例持有context。

class SingleTon private constructor(val context: Context) {
    companion object {
        private var instance: SingleTon? = null
        fun getInstance(context: Context) =
            if (instance == null) SingleTon(context) else instance!!
    }
}

当我们在Activity中使用时,当Activity销毁,就会出现内存泄露

class MemoryActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory)
        SingleTon.getInstance(this)
    }
}

这种情况可以使用applicationContext,因为Application的生命周期就等于整个应用的生命周期

class SingleTon private constructor(context: Context) {
    private var context: Context
    init {
        this.context = context.applicationContext
    }
    companion object {
        private var instance: SingleTon? = null
        fun getInstance(context: Context) =
            if (instance == null) SingleTon(context) else instance!!
    }
}

Handler引发的内存泄露

class MemoryActivity : AppCompatActivity() {
    private val tag = javaClass.simpleName
    private val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            Log.i(tag, "handleMessage:$msg")
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory)
        thread(start = true) {
            handler.sendEmptyMessageDelayed(1, 10000)
        }
    }
}

当Activity被finish时,延迟发送的消息仍会存活在UI线程消息队列中,直到10s后才被处理,这个消息持有handler的引用,由于非静态内部类或匿名类会隐式持有外部类的引用,handler隐式持有外部类也就是Activity的引用,这个引用会一直存在直到这个消息被处理,所以垃圾回收机制就没法回收而导致内存泄露。

解决方案:静态内部类+弱引用,静态内部类不会持有外部类的引用,如需handler内调用外部类Activity的方法的话,可以让handler持有外部类Activity的弱引用,这样Activity就不会有泄露风险了。

class MemoryActivity : AppCompatActivity() {
    companion object {
        private const val tag = "uncle"
    }
    private lateinit var handler: Handler
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory)
        handler = MyHandler(this)
        thread(start = true) {
            handler.sendEmptyMessageDelayed(1, 10000)
        }
    }
    class MyHandler(activity: Activity) : Handler(Looper.getMainLooper()) {
        private val reference = WeakReference(activity)
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            if (reference.get() != null) {
                Log.i(tag, "handleMessage:$msg")
            }
        }
    }
}

集合引发的内存泄露

先看个例子,我们定义一个栈,装着所有的Activity

class GlobalData {
    companion object {
        val activityStack = Stack<Activity>()
    }
}

然后每启动一个Activity,就把此Activity加进去,这个时候,如果你没有在Activity销毁时清掉集合中对应的引用,就会出现泄露问题。当然,实际开发中我们不会写这么差的代码,这只是简单提个醒,需要注意一下集合中的一些引用,如果会导致泄露的,记得及时在销毁时清掉。

class MemoryActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory)
        GlobalData.activityStack.push(this)
    }
}

检测工具

排查内存泄露,需要一些工具的支持,这里主要介绍常用的两个,LeakCanary和Android Studio Profiler。

LeakCanary

一行代码引入

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'

当你测试包安装时,手机上就会有个伴生APP,用来记录内存泄露信息的。

就拿上面集合引发的泄露例子来说,LeakCanary就会弹出通知并且Leaks APP中显示内存泄露信息,我们以此来定位内存泄露问题。

Android Studio Profiler

同样,我们拿上面集合的泄漏例子来看,首先,我们点击MEMORY

然后,普通的内存问题选择Capture heap dump就行了

点击Record,就会抓取一段时间的内存分配信息

Leaks就是记录内存泄漏的,然后我们点击进去,就可以看到具体类位置了

再点击进去具体的类,就可以看到泄漏的原因啦

内存溢出

Android系统中每个应用程序可以向系统申请一定的内存,当申请的内存不够用的时候,就会产生内存溢出,俗称OOM,全称Out Of Memory,就是内存用完了。在实际开发中,出现这种现象通常是因为内存泄露太多或大图加载问题,内存泄露上面已经讲了,那么,下面就主要讲讲图片的优化吧!

Bitmap优化

(1)及时回收Bitmap内存,这时可能有人就要问了,Android有自己的垃圾回收机制,为什么还要我们去回收呢?因为生成Bitmap最终是通过JNI方法实现的,也就是说,Bitmap的加载包含两部分的内存区域,一是Java部分,一是C部分。Java部分会自动回收,但是C部分不会,所以需要调用recycle来释放C部分的内存。那如果不调用就一定会出现泄露吗?那也不是的,Android每个应用都在独立的进程,进程被关掉的话,内存也就都被释放了。

        if (bitmap != null && !bitmap.isRecycled) {
            bitmap.recycle()
            bitmap = null
        }

(2)捕获异常,Bitmap在使用的时候,最好捕获一下OutOfMemoryError以免crash掉,你还可以设置一个默认的图片。

        var bitmap: Bitmap? = null
        try {
            bitmap = BitmapFactory.decodeFile(filePath)
            imageView.setImageBitmap(bitmap)
        } catch (e: OutOfMemoryError) {
            //捕获异常
        }
        if (bitmap == null) {
            imageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.picture))
        }

(3)压缩,对于分辨率比较高的图片,我们应该加载一个缩小版,这里采用的是采样率压缩法。

        val options = BitmapFactory.Options()
        //设置为true可以让解析方法禁止为bitmap分配内存,返回null,同时能获取到长宽值,从而根据情况进行压缩
        options.inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, R.drawable.large_picture, options)
        val imgHeight = options.outHeight
        val imgWidth = options.outWidth
        //通过改变inSampleSize的值来压缩图片
        var inSampleSize = 1
        //imgWidth为图片的宽,viewWidth为实际控件的宽
        if (imgHeight > viewHeight || imgWidth > viewWidth) {
            val heightRatio = round(imgHeight / viewHeight.toFloat()).toInt()
            val widthRatio = round(imgWidth / viewWidth.toFloat()).toInt()
            //选择最小比率作为inSampleSize的值,可保证最终图片的宽高一定大于等于目标的宽高
            inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio
        }
        options.inSampleSize = inSampleSize
        //计算完后inJustDecodeBounds重置为false
        options.inJustDecodeBounds = false
        val bitmap = BitmapFactory.decodeResource(resources, R.drawable.large_picture, options)
        imageView.setImageBitmap(bitmap)

如果程序中的图片是本地资源或者是自己服务器上的,那这个大小我们可以自行调整,只要注意图片不要太大,及时回收Bitmap,就能避免OOM的发生。如果图片来源是外界,这个时候就要特别注意了,可以采用压缩图片或捕获异常,避免OOM的产生而导致程序崩溃。

内存抖动

频繁地创建对象,会导致内存抖动,最终可能会导致卡顿或OOM,因为大量临时对象频繁创建会导致内存碎片,当需要分配内存时,虽然总体上还有剩余内存,但由于这些内存不连续,无法整块分配,系统会视为内存不够,故导致OOM。

常见场景为大循环中创建对象,自定义View的onDraw方法中创建对象,因为屏幕绘制会频繁调用onDraw方法。我们可以将这些操作放在循环外或onDraw方法外,避免频繁创建对象。

到此这篇关于Android内存优化操作方法梳理总结的文章就介绍到这了,更多相关Android内存优化内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Android内存优化操作方法梳理总结

本文链接: https://lsjlt.com/news/170521.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Android内存优化操作方法梳理总结
    目录内存泄露非静态内部类创建静态实例注册对象未注销或资源对象未关闭类的静态变量引用耗费资源过多的实例Handler引发的内存泄露集合引发的内存泄露检测工具LeakCanaryAndr...
    99+
    2022-11-13
    Android内存优化方案 Android内存优化总结 Android内存优化
  • Android 内存优化知识点梳理总结
    目录RAM 和 ROM常见内存问题内存溢出内存泄漏常见内存泄漏场景静态变量或单例持有对象非静态内部类的实例生命周期比外部类更长导致的内存泄漏Handler 导致的内存泄漏postDe...
    99+
    2024-04-02
  • Rxjs TakeUntil 操作符内容梳理总结
    TakeUntil 的官方文档对这个操作符的解释是: Emit values until provided observable emits. 即它可以被赋予另一个起锚定作用的 Ob...
    99+
    2024-04-02
  • java内存优化的方法总结
    1、不必要的自动装箱。 自动装箱是将基础数据类型转换为相应的复杂类型,在HashMap的追加删除调查中充满了自动装箱问题,因此尽量避免HashMap代替SparseArray和Arr...
    99+
    2024-04-02
  • 总结Android App内存优化之图片优化
    前言 在Android设备内存动不动就上G的情况下,的确没有必要去太在意APP对Android系统内存的消耗,但在实际工作中我做的是教育类的小学APP,APP中的按钮、背景、动...
    99+
    2022-06-06
    图片 优化 app Android
  • 梳理总结25个JavaScript数组操作方法实例
    目录1.删除数组重复项2. 获取数组的片段3.Array.from 达到 .map 的效果4.置空数组5. 将数组转换为对象6. 用数据填充数组7. 数组合并8.求两个数组的交集9....
    99+
    2024-04-02
  • JavaString类常用方法梳理总结
    目录一、String类概述概述特点二、使用步骤三、常用方法判断功能的方法获取功能的方法转换功能的方法分割功能的方法一、String类概述 概述 java.lang.String 类代...
    99+
    2024-04-02
  • 梳理总结Java static关键字的方法作用
    目录概述定义和使用格式类变量静态方法调用格式静态原理图解静态代码块概述 关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 ...
    99+
    2024-04-02
  • Android 文件读写操作方法总结
    Android 文件读写操作方法总结 在Android中的文件放在不同位置,它们的读取方式也有一些不同。 本文对android中对资源文件的读取、数据区文件的读取、SD卡文件...
    99+
    2022-06-06
    方法 Android
  • android操作XML的几种方法总结
    XML作为一种业界公认的数据交换格式,在各个平台与语言之上,都有广泛使用和实现。其标准型,可靠性,安全性......毋庸置疑。在android平台上,我们要想实现数据存储和数据...
    99+
    2022-06-06
    XML 方法 Android
  • python中列表的常见操作梳理总结(二)
    目录python中列表的常见操作列表的索引与切片什么是索引?什么是切片?列表通过索引/切片赋值及索引的获取pop函数删除元素索引&切片在元组中的特殊性字符串的索引与切片字符串...
    99+
    2024-04-02
  • python中列表的常见操作梳理总结(一)
    目录python中列表的常见操作列表元组的简单操作列表/元组的长度列表/元组之间的累加与乘法判断列表/元组中是否包含某元素列表的append()函数列表的insert()函数列表的c...
    99+
    2024-04-02
  • CodeReview方法论与实践总结梳理
    目录引言为什么要CR他山之石2.1 某大厂A2.1.1 代码评审准则2.1.2 代码评审原则2.1.3 代码审核者应该看什么2.2 某大厂B2.3 某大厂C我们怎么做 CR3.1 作...
    99+
    2023-02-07
    Code Review 方法论 Code Review
  • 梳理总结JavaScript的23个String方法
    目录简单介绍1、charAt()2、charCodeAt()3、codePointAt()4、indexOf()5、lastIndexOf()6、startsWith()7、ends...
    99+
    2024-04-02
  • C语言 超详细梳理总结动态内存管理
    目录一.为什么存在动态内存分配二.动态内存函数的介绍1.malloc和free2.calloc3.realloc三.常见的动态内存错误1.对NULL指针的解引用操作2.对动态开辟空间...
    99+
    2024-04-02
  • PHP操作MySQL的常用代码段梳理与总结
    这篇文章为大家介绍,实用的PHP网站实际开发中常用到的操作mysql数据库的代码段,所有代码均可靠执行,此文将持续更新!!! 1、向数据库插入数据表 <?php ...
    99+
    2024-04-02
  • Redis缓存三大异常的处理方案梳理总结
    目录前言一、背景二、缓存雪崩(一)是什么(二)为什么(三)怎么办三、缓存击穿(一)是什么(二)为什么(三)怎么办四、缓存穿透(一)是什么(二)为什么(三)怎么办五、其他(一)缓存预热(二)缓存降级六、总结前言 Redis...
    99+
    2022-06-23
    Redis缓存异常处理方案 Redis缓存异常
  • Django项目优化数据库操作总结
    目录合理的创建索引设置数据库持久连接 减少SQL的执行次数仅获取需要的字段数据使用批量创建、更新和删除,不随意对结果排序参考网址:Django官方数据库优化 使用 QuerySet...
    99+
    2024-04-02
  • MySQL过滤数据操作方法梳理
    目录前言1. where 字句的使用2. where 字句操作符2.1 检查单个值2.2 不匹配检查2.3 范围值检查2.4 空值检查3. 扩展3.1 SQL过滤与应用过滤3.2 引号的使用3.3 N...
    99+
    2024-04-02
  • MySQL检索数据操作方法梳理
    目录前言:1. select语句-用于检索所有列2. 注释的使用前言: 本实验中所用数据库创建SQL代码以及插入数据SQL代码链接: 链接: https://pan.bai...
    99+
    2022-11-13
    MySQL检索数据 MySQL数据检索
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作