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

目录
  • 内存泄露
    • 非静态内部类创建静态实例
    • 注册对象未注销或资源对象未关闭
    • 类的静态变量引用耗费资源过多的实例
    • Handler引发的内存泄露
    • 集合引发的内存泄露
  • 检测工具
    • LeakCanary
    • Android Studio Profiler
  • 内存溢出
    • Bitmap优化
  • 内存抖动

内存泄露

内存泄漏就是在当前应用周期内不再使用的对象被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内存优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android内存优化杂谈

    Android内存优化是我们性能优化工作中比较重要的一环,这里其实主要包括两方面的工作: 1.优化RAM,即降低运行时内存.这里的目的是防止程序发生OOM异常,以及降低程序由于内存过大被LMK机制杀死的概率.另一方面,不合理的内存使用会使GC大大增多,从而导致程序变卡. 2.优化ROM,即降低程序占ROM的体积.这里主要是为了降低程序占用的空间,防止由于ROM空间不足导致程序无法安装. 本文的着重点为第一点,总结概述降低应用运行内存的技巧.在这里我们不再细述PSS.USS等概念与Android应

  • Android 中对于图片的内存优化方法

    1. 对图片本身进行操作 尽量不要使用 setImageBitmap.setImageResource. BitmapFactory.decodeResource 来设置一张大图,因为这些方法在完成 decode 后,最终都是通过 Java 层的 createBitmap 来完成的,需要消耗更多内存.因此,改用先通过 BitmapFactory.decodeStream 方法,创建出一个 bitmap,再将其设为 ImageView 的 source,decodeStream 最大的秘密在于其直

  • 总结Android App内存优化之图片优化

    前言 在Android设备内存动不动就上G的情况下,的确没有必要去太在意APP对Android系统内存的消耗,但在实际工作中我做的是教育类的小学APP,APP中的按钮.背景.动画变换基本上全是图片,在2K屏上(分辨率2048*1536)一张背景图片就会占用内存12M,来回切换几次内存占用就会增涨到上百兆,为了在不影响APP的视觉效果的前提下,有必要通过各种手段来降低APP对内存的消耗. 通过DDMS的APP内存占用查看工具分析发现,APP中占用内存最多的是图片,每个Activity中图片占用内存

  • Android 内存优化知识点梳理总结

    目录 RAM 和 ROM 常见内存问题 内存溢出 内存泄漏 常见内存泄漏场景 静态变量或单例持有对象 非静态内部类的实例生命周期比外部类更长导致的内存泄漏 Handler 导致的内存泄漏 postDelayed 导致的内存泄漏 View 的生命周期大于 Activity 时导致的内存泄漏 集合中的对象未释放导致内存泄漏 WebView 导致的内存泄漏 内存抖动 解决方案 其他优化点 App 内存过低时主动清理 前言: Android 操作系统给每个进程都会分配指定额度的内存空间,App 使用内存

  • Android Bitmap详解及Bitmap的内存优化

    Android Bitmap详解及Bitmap的内存优化 一.Bitmap: Bitmap是Android系统中的图像处理的最重要类之一.用它可以获取图像文件信息,进行图像剪切.旋转.缩放等操作,并可以指定格式保存图像文件. 常用方法: public void recycle() // 回收位图占用的内存空间,把位图标记为Dead public final boolean isRecycled() //判断位图内存是否已释放 public final int getWidth() //获取位图的

  • 详解Android内存优化策略

    目录 前言 一.内存优化策略 二.具体优化的点 1.避免内存泄漏 2.Bitmap等大对象的优化策略 (1) 优化Bitmap分辨率 (2) 优化单个像素点内存 (3) Bitmap的缓存策略 (4) drawable资源选择合适的drawable文件夹存放 (5) 其他大对象的优化 (6) 避免内存抖动 3.原生API回调释放内存 4.内存排查工具 (1)LeakCanary监测内存泄漏 (2)通过Proflier监控内存 (3)通过MAT工具排查内存泄漏 总结 前言 在开始之前需要先搞明白一

  • 浅谈Android性能优化之内存优化

    1.Android内存管理机制 1.1 Java内存分配模型 先上一张JVM将内存划分区域的图 程序计数器:存储当前线程执行目标方法执行到第几行. 栈内存:Java栈中存放的是一个个栈帧,每个栈帧对应一个被调用的方法.栈帧包括局部标量表, 操作数栈. 本地方法栈:本地方法栈主要是为执行本地方法服务的.而Java栈是为执行Java方法服务的. 方法区:该区域被线程共享.主要存储每个类的信息(类名,方法信息,字段信息等).静态变量,常量,以及编译器编译后的代码等. 堆:Java中的堆是被线程共享的,

  • 详解Android的内存优化--LruCache

    概念: LruCache 什么是LruCache? LruCache实现原理是什么? 这两个问题其实可以作为一个问题来回答,知道了什么是 LruCache,就只然而然的知道 LruCache 的实现原理:Lru的全称是Least Recently Used ,近期最少使用的!所以我们可以推断出 LruCache 的实现原理:把近期最少使用的数据从缓存中移除,保留使用最频繁的数据,那具体代码要怎么实现呢,我们进入到源码中看看. LruCache源码分析 public class LruCache<

  • 常见Android编译优化问题梳理总结

    目录 编译常见问题 踩坑1 踩坑2 编译常见问题 在开发过程中,有碰到过一些由于编译优化导致的代码修改并不符合我们预期的情况.这也就是之前为什么我经常说编译产物其实是不太可以被信任的. 方法签名变更,底层仓库的方法变更但是上层模块并没有跟随一起重新编译导致的这个问题. 常量优化,将一些常量的调用点直接替换成常量的值. 删除空导包, 没有用的一些导包就会做一次剔除. 踩坑1 我们最近碰到一个 pipeline 相关而且很妖怪的问题.我们一个 pipeline 会检查apk产物中是否存在异常的方法调

  • android内存优化之图片优化

    对图片本身进行操作.尽量不要使用setImageBitmap.setImageResource.BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存.因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的source,decodeStream最大的秘密在于其直接调用JNI>>nativeDeco

  • 详解Android内存泄露及优化方案一

    目录 一.常见的内存泄露应用场景? 1.单例的不恰当使用 2.静态变量导致内存泄露 3.非静态内部类导致内存泄露 4.未取消注册或回调导致内存泄露 5.定时器Timer 和 TimerTask 导致内存泄露 6.集合中的对象未清理造成内存泄露 7.资源未关闭或释放导致内存泄露 8.动画造成内存泄露 9.WebView 造成内存泄露 总结 一.常见的内存泄露应用场景? 1.单例的不恰当使用 单例是我们开发中最常见和使用最频繁的设计模式之一,所以如果使用不当就会导致内存泄露.因为单例的静态特性使得它

  • 详解Android内存泄露及优化方案

    目录 一.常见的内存泄露应用场景? 1.单例的不恰当使用 2.静态变量导致内存泄露 3.非静态内部类导致内存泄露 4.未取消注册或回调导致内存泄露 5.定时器Timer 和 TimerTask 导致内存泄露 6.集合中的对象未清理造成内存泄露 7.资源未关闭或释放导致内存泄露 8.动画造成内存泄露 9.WebView 造成内存泄露 总结 一.常见的内存泄露应用场景? 1.单例的不恰当使用 单例是我们开发中最常见和使用最频繁的设计模式之一,所以如果使用不当就会导致内存泄露.因为单例的静态特性使得它

  • 浅谈Android应用的内存优化及Handler的内存泄漏问题

    一.Android内存基础 物理内存与进程内存 物理内存即移动设备上的RAM,当启动一个Android程序时,会启动一个Dalvik VM进程,系统会给它分配固定的内存空间(16M,32M不定),这块内存空间会映射到RAM上某个区域.然后这个Android程序就会运行在这块空间上.Java里会将这块空间分成Stack栈内存和Heap堆内存.stack里存放对象的引用,heap里存放实际对象数据. 在程序运行中会创建对象,如果未合理管理内存,比如不及时回收无效空间就会造成内存泄露,严重的话可能导致

随机推荐