Optimización del rendimiento de Android: la causa raíz de las fugas de memoria

Qué es una pérdida de memoria

¿Qué es una pérdida de memoria?En términos generales, algunos objetos en el montón ya no se usan, pero el recolector de elementos no utilizados no puede borrarlos de la memoria.

Una fuga de memoria es un problema grave porque bloquea los recursos de memoria y degrada el rendimiento del sistema con el tiempo. Si no se lleva a cabo un procesamiento eficaz, el resultado final será que el programa de la aplicación se quedará sin recursos de memoria, no funcionará con normalidad, hará que el programa se bloquee y genere una excepción java.lang.OutOfMemoryError.

En general, hay dos tipos de objetos en la memoria del montón: objetos referenciados y objetos no referenciados. Un objeto al que se hace referencia es aquel que todavía tiene referencias activas en la aplicación, mientras que un objeto sin referencia no tiene ninguna referencia activa.

El recolector de elementos no utilizados reclama objetos sin referencia, pero no aquellos a los que todavía se hace referencia. Esta es también la fuente de pérdidas de memoria.

¿Qué operaciones pueden causar pérdidas de memoria?

A continuación presentamos varias situaciones comunes que causan pérdidas de memoria

1. La declaración accidental de variables globales es el problema de pérdida de memoria más común y más fácil de solucionar, como por ejemplo:

function fn() {
    name = '张三';
}
复制代码

Cuando el intérprete interprete la función anterior, considerará nombre como una variable global, es decir, ventana.nombre = 'Zhang San'. Siempre que no se limpie el objeto de la ventana, el atributo de nombre y el valor del atributo siempre existirán, lo que provocará una fuga de memoria.

Solución:

(1) Simplemente agregue la palabra clave var, let o const delante de la declaración de la variable, de modo que la variable abandone el alcance después de que se ejecute la función.

(2) Utilice esta palabra clave

function fn() {
    this.name = '张三';
}
复制代码

(3) Puede agregar "usar estricto" al principio del archivo JavaScript para usar el modo estricto. De esta manera, analizar JavaScript en modo estricto evita variables globales accidentales

(4) Después de su uso, asígnele un valor de nulo o reasignarlo

2. Fuga causada por el temporizador

let name = '张三';
setInterval(() => {
    console.log(name);
}, 100);
复制代码

En el código anterior, mientras el temporizador siga funcionando, el nombre al que se hace referencia en la función de devolución de llamada siempre ocupará memoria.

3. Cierres, registros de consola y bucles (cuando dos objetos se refieren entre sí y se retienen entre sí, se generará un bucle), veamos un ejemplo de fuga de entrenamiento interno causada por cierres de JavaScript

let fun = function() {
    let name = '张三';
    return function() {
        return name;
    };
};
复制代码

Llamar a fun() hará que la memoria asignada a name se filtre. Después de ejecutar el código anterior, se crea un cierre interno. Mientras exista la función devuelta, el nombre no se puede limpiar, porque el cierre lo ha estado haciendo referencia.

Fugas de memoria comunes

1. El objeto de recurso no está cerrado

资源性对象(如Cursor、File等一些Closeable对象),它们往往使用了缓冲区,缓冲区不仅在JVM内,JVM之外也有。如果仅仅把变量设置为null,而不关闭它们,缓冲区得不到释放,往往造成内存泄露。

解决方案:一般在finally中关闭资源型对象,而后设置对象为null

2.注册对象未注销

订阅者模式中,如果注册对象不再使用时,未及时注销,会导致订阅者列表中维持这对象的引用,阻止垃圾回收,导致内存泄露。常见场景:动态注册BroadcastReceiver,注册PhoneStateListener,注册EventBus等等,

还有自定义使用订阅者模式的情形。

解决方案:一般在onDestroy()中进行解注册

3.非静态内部类的静态实例

非静态内部类持有外部类实例的引用,若非静态内部类的实例是静态的,便拥有app存活期整个生命周期,长期持有外部类的引用,阻止外部类实例被回收。

使用内部类的情况十分常见,尤其是匿名内部类:一些接口的匿名实现类,都是内部类。

解决方案:(1)改为静态内部类,不再持有外部类实例的引用 (2)避免申明非静态内部类的静态实例 (3)将内部类抽取出来封装成一个单例,如果需要Context,没有特殊要求就使用Application Context;如果需要Activity Context,则使用完毕置空,或者使用弱引用

4.单例模式引起的内存泄露

由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏
复制代码

解决方案:使用Activity的弱引用,或者没特殊需求时使用Application Context

5.Handler临时性内存泄露

非静态Handler持有Activity或Service的引用,Message中的target指向Handler实例,所以当Message在MessageQueue中排队,长时间未得到处理时,Activity边不会被回收,导致临时性内存泄露。

解决方案:(1)使用静态Handler内部类,然后对Handler持有的对象(Activity或Service)使用弱引用 (2)在onDestroy()中移除消息队列中的消息 mHandler.removeCallbacksAndMessages(null)

类似的:AsyncTask内部也是Handler机制,也存在同样的临时性内存泄露风险

6.容器中对象未及时清理导致内存泄露

容器类一般拥有较长的生命周期,若内部不再使用的对象不及时清理,内部对象边一直被容器类引用。上述2中的订阅者列表也属于容器类这中情况。另外常见的容器类还有线程池、对象池、图片缓存池等。线程池中的线程若存在ThreadLocal对象,因为线程对象一直被循环使用,ThreadLocal对象便会一直被引用,要注意对value对象的置空释放。

7.静态View导致内存泄露

有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。 解决办法:在使用静态View时,需要确保在资源回收时,将静态View detach掉。

8.属性动画未及时关闭导致内存泄露

在使用ValueAnimator或者ObjectAnimator时,如果没有及时做cancel取消动画,就可能造成内存泄露。 因为在cancel方法里,最后调用了endAnimation(); ,在endAnimation里,有个AnimationHandler的单例,会持有属性动画对象的引用

解决办法:在在onDestory时,调用动画的cancel方法

9.WebView内存泄露

目前Android中WebView的实现存在很大的兼容性问题,Google支持各个ROM厂商自行定制自己的WebView实现,各个ROM间差异较大,且大多都存在内存泄露问题。除了调用其内部的clearCache()、clearHistory()、removeAllViews()、freeMemory()、destroy()和置null以外,一般比较粗暴有效的解决方法是:将包含WebView的Activity放在一个单独的进程中,不需要时将进程销毁,从而释放所有所占内存。

10.其他的系统控件以及自定义View

在 Android Lollipop 之前使用 AlertDialog 可能会导致内存泄漏

view中有线程或者动画 要及时停止。这是为了防止内存泄漏,可以在onDetachedFromWindow方法中结束,这个方法回调的时机是 当View的Activity退出或者当前View被移除的时候 会调用 这时候是结束动画或者线程的好时机 另外还有一个对应的方法 onAttachedToWindow 这个方法调用的时机是在包含View的Activity启动时 回调 回调在onDraw方法之前

11.其他常见的引起内存泄漏原因

  • (1)构造Adapter时,没有使用缓存的 contentView
  • (2)Bitmap在不使用的时候没有使用recycle()释放内存
  • (3)警惕线程未终止造成的内存泄露;譬如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity时切记结束线程。一个典型的例子就是HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周期,我们必须手动在Activity的销毁方法中调用thread.getLooper().quit();才不会泄露
  • (4)避免代码设计模式的错误造成内存泄露;譬如循环引用,A持有B,B持有C,C持有A,这样的设计谁都得不到释放

以上就是Android开发当中的性能优化重要部分;内存泄漏在开发中占比很重要,也是一个老生常谈的问题。除此之外还有一系列的Android性能优化学习。比喻:卡顿优化、内存优化、启动优化等等。这里想学习性能优化的推荐参考前往传送直达↓↓↓ :link.juejin.cn/?target=htt…里面记录了7大块性能优化学习。

文末

理解内存泄漏的危害,我们举个简单的例子。有一个宾馆,有100间房间,顾客每次都是在前台进行登记,然后拿到房间钥匙。如果有些顾客不需要该房间了,也不归还钥匙,久而久之,前台处可用房间越来越少,收入也越来越少,濒临倒闭。当程序申请了内存,而不进行归还,久而久之,可用内存越来越少,OS就会进行自我保护,杀掉该进程,这就是我们常说的OOM(out of memory)。

Supongo que te gusta

Origin juejin.im/post/7182852816896000059
Recomendado
Clasificación