Otimização de desempenho do Android - a causa raiz dos vazamentos de memória

O que é um vazamento de memória

O que é um vazamento de memória?De modo geral, alguns objetos na pilha não são mais usados, mas o coletor de lixo não pode limpá-los da memória.

Um vazamento de memória é um problema sério porque bloqueia recursos de memória e degrada o desempenho do sistema ao longo do tempo. Se nenhum processamento efetivo for executado, o resultado final será que o programa aplicativo ficará sem recursos de memória, falhará em atender normalmente, fará com que o programa trave e lance uma exceção java.lang.OutOfMemoryError.

Geralmente, existem dois tipos de objetos na memória heap: objetos referenciados e objetos não referenciados. Um objeto referenciado é aquele que ainda possui referências ativas no aplicativo, enquanto um objeto não referenciado não possui referências ativas.

O coletor de lixo recupera objetos não referenciados, mas não aqueles que ainda são referenciados. Essa também é a fonte de vazamentos de memória.

Quais operações podem causar vazamentos de memória?

Abaixo, apresentamos várias situações comuns que causam vazamentos de memória

1. A declaração acidental de variáveis ​​globais é o problema de vazamento de memória mais comum e fácil de corrigir, como:

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

Quando o interpretador interpretar a função acima, ele considerará name como uma variável global, ou seja, window.name = 'Zhang San'. Enquanto o objeto janela não for limpo, o atributo name e o valor do atributo sempre existirão, causando um vazamento de memória.

Solução:

(1) Basta adicionar a palavra-chave var, let ou const na frente da declaração da variável, para que a variável saia do escopo após a execução da função.

(2) Use esta palavra-chave

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

(3) Você pode adicionar "usar estrito" no início do arquivo JavaScript para usar o modo estrito. Desta forma, a análise do JavaScript no modo estrito evita variáveis ​​globais acidentais

(4) Após o uso, atribua a ele um valor nulo ou realoque-o

2. Vazamento causado pelo temporizador

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

No código acima, enquanto o timer continuar funcionando, o nome referenciado na função callback sempre ocupará a memória.

3. Closures, console logs e loops (quando dois objetos se referem um ao outro e retêm um ao outro, um loop será gerado), vejamos um exemplo de vazamento de treinamento interno causado por encerramentos de JavaScript

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

Chamar fun() fará com que a memória alocada para name vaze. Depois que o código acima é executado, um encerramento interno é criado.Enquanto a função retornada existir, o nome não pode ser limpo, pois o encerramento está fazendo referência a ela.

Vazamentos de memória comuns

1. O objeto de recurso não está fechado

资源性对象(如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)。

Acho que você gosta

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