Android | Essas coisas sobre OOM

Autor: 345、

prefácio

O sistema Android terá um limite máximo de memória para cada aplicativo. Caso esse limite seja ultrapassado, será lançado o OOM, que é Out Of Memory . É essencialmente uma exceção lançada, geralmente depois que a memória excede o limite. O OOM mais comum é o OOM causado por vazamentos de memória (um grande número de objetos não podem ser liberados) ou o tamanho da memória necessária é maior que o tamanho da memória alocada. Por exemplo, carregar uma imagem muito grande pode causar OOM.

OOMs comuns

estouro de pilha

O estouro de memória heap é o OOM mais comum, geralmente porque a memória heap está cheia e não pode ser recuperada pelo coletor de lixo , resultando em OOM.

estouro de thread

O número máximo de threads permitidos por diferentes celulares é diferente. Em alguns celulares, esse valor é modificado para um valor muito baixo e é mais propenso ao problema de estouro de thread.

Estouro de quantidade FD

Estouro do descritor de arquivo, quando um programa abre ou cria um novo arquivo, o sistema retornará um valor de índice, apontando para a tabela de registros do arquivo aberto pelo processo, por exemplo, quando abrimos um arquivo com um arquivo de fluxo de saída, o o sistema nos retornará um FD, o FD pode vazar.

Memória virtual insuficiente

Ao criar um novo thread, a camada inferior precisa criar um objeto JNIEnv e alocar memória virtual. Se a memória virtual se esgotar, a criação do thread falhará e o OOM será lançado.

A diferença de memória entre Jvm, Dvm e Art

O Android usa a máquina virtual Dalvik/ART baseada na linguagem Java, e tanto Dalvik quanto ART são baseados em JVM , mas deve-se notar que a máquina virtual no Android é diferente da JVM padrão porque eles precisam rodar em dispositivos Android, então eles têm diferentes otimizações e limitações.

Em termos de reciclagem, Dalvik corrige apenas um algoritmo de reciclagem, enquanto o algoritmo de reciclagem ART pode ser selecionado sob demanda durante o tempo de execução, e o ART tem a capacidade de organizar a memória para reduzir falhas de memória.

JVM

JVM é um computador fictício, que é realizado simulando várias funções do computador em um computador real. Possui uma arquitetura de hardware completa (virtual) e um sistema de instruções correspondente, e seu conjunto de instruções é baseado em uma estrutura de pilha. O uso Java虚拟机é para oferecer suporte a programas que são independentes das operações do sistema e podem ser executados em qualquer sistema.

A memória gerenciada pela JVM é dividida nas seguintes partes:

  • área de método

    Compartilhado por cada bloqueio de thread, ele é usado para armazenar informações de classe, constantes, variáveis ​​estáticas, etc. que foram carregadas pela máquina virtual.Quando a área do método não puder atender aos requisitos de alocação de memória, uma exceção OutOfMemoryError será lançada.

    • pool constante

      O pool de constantes também faz parte da área de métodos, que é usada para armazenar vários argumentos e referências de símbolos gerados pelo compilador. O mais usado é String. Quando a nova String é chamada de estagiário, ele verificará se existe tal string no pool constante. Se houver, devolva-o, se não, crie um e devolva-o.

  • pilha java

    A maior parte de memória na memória da máquina virtual. Todos os objetos criados por meio de new serão alocados na memória heap. É a maior parte de memória na máquina virtual e também é a parte que o gc precisa recuperar. No ao mesmo tempo, OOM também tende a ocorrer aqui .

    Do ponto de vista da recuperação de memória, como a maioria dos colecionadores já utiliza o método de coleta geracional, ele também pode ser subdividido em nova geração, geração antiga, etc.

    De acordo com as disposições da máquina virtual Java, o heap Java pode estar em um espaço fisicamente descontínuo, desde que seja logicamente contínuo.Se não houver memória que possa ser alocada, ocorrerá uma exceção OutOfMemoryError.

  • pilha java

    O thread é privado , usado para armazenar todos os dados quando o método java é executado, e é composto por um stack frame. Um stack frame representa a execução de um método. A execução de cada método é equivalente a um stack frame no máquina virtual de pilha em pilha. o processo de. O quadro de pilha inclui principalmente variáveis ​​locais, operandos de pilha, links dinâmicos, etc.

    A pilha Java é dividida em pilha de operandos, dados de quadro de pilha e dados de variáveis ​​locais. As variáveis ​​locais alocadas no método estão na pilha. Ao mesmo tempo, cada chamada de método acompanhará o quadro de pilha na pilha. O tamanho do pilha é uma faca de dois gumes. Muito pequena pode causar estouro de pilha, especialmente quando há recursão, um grande número de operações de loop. Se for muito grande, afetará o número de pilhas que podem ser criadas. Se for um aplicativo multithread, causará estouro de memória.

  • pilha de métodos nativos

    O efeito é basicamente semelhante ao da pilha java, a diferença é que ela é utilizada para servir o método nativo.

  • contador de programa

    É um espaço pequeno e sua função pode ser considerada como o indicador do número da linha do bytecode de execução do bloqueio de thread atual, que é usado para registrar o endereço da instrução de bytecode executada pelo thread, para que o thread possa ser restaurado para o posição de execução correta ao mudar.

DVM

Originalmente chamada de Dalvik, é uma máquina virtual projetada pelo Google para a plataforma Android. É também uma máquina virtual JAVA em essência. É a base para a execução de programas Java no Android . Suas instruções são baseadas na arquitetura de registro e executam seus comandos exclusivos. formato de arquivo -dex.

Pilha de tempo de execução do DVM

A estrutura de heap do DVM é diferente da JVM, refletida principalmente no fato de que o heap é dividido em heap ativo e heap zigoto . Zygote é um processo de máquina virtual e uma incubadora de instância de máquina virtual. O heap zygote são as classes, recursos e objetos que o processo Zygote pré-carrega quando é iniciado. Além disso, as instâncias e matrizes que criamos no código são armazenadas no Active pilha.

A razão pela qual o heap Dalvik é dividido em dois é principalmente porque o Android cria um novo processo zigoto por meio do método fork, a fim de evitar ao máximo a cópia de dados entre o processo pai e o processo filho.

O Zygote da Dalvik armazena classes pré-carregadas que são classes principais do Android e bibliotecas de tempo de execução Java. Esta parte raramente é modificada. Na maioria dos casos, o processo filho e o processo pai compartilham esta área, portanto, não há necessidade de coleta de lixo para esta classe. Ativo , como o heap de objetos de instância criado no código do programa é a área principal da coleta de lixo, portanto, os dois heaps precisam ser separados.

Mecanismo de reciclagem DVM

A estratégia de coleta de lixo do DVM é padronizada para o algoritmo de marcação e varredura (marcação e varredura), e o processo básico é o seguinte

  1. Fase de marcação: comece a percorrer a partir do objeto raiz, marque todos os objetos acessíveis e marque-os como objetos que não são lixo
  2. Fase de limpeza: percorre todo o heap e limpa todos os objetos não marcados
  3. Fase de compactação (opcional): compacta todos os objetos do inventário para reduzir a fragmentação da memória

Deve-se notar que o coletor de lixo DVM é baseado no algoritmo mark-and-sweep. Este algoritmo irá gerar algoritmos de memória, o que pode reduzir a eficiência da alocação de memória. Portanto, o DVM também suporta algoritmos de recuperação geracional, que podem lidar melhor com problemas de fragmentação de memória.

Na coleta de lixo geracional, a memória é dividida em idades diferentes, e cada idade é processada usando um algoritmo de coleta de lixo diferente.A geração mais jovem usa o algoritmo mark-copy e a geração mais velha usa o método mark-clear, que pode equilibrar melhor a memória eficiência de alocação e eficiência de coleta de lixo

ARTE

ART é uma máquina virtual introduzida no Android 5.0. Em comparação com DVM, ART usa tecnologia de compilação AOT (Ahead of Time) , o que significa que converte o bytecode do aplicativo em código de máquina nativo em vez de interpretar o bytecode um por um em tempo de execução , esta tecnologia de compilação pode melhorar a eficiência de execução do aplicativo e reduzir o tempo de inicialização e o uso de memória do aplicativo

A diferença entre JIT e AOT
  • Na hora certa

    O DVM usa um compilador JIT, que traduz parte do bytecode dex em código de máquina em tempo real cada vez que o aplicativo é executado. Durante a execução do programa, mais código é compilado e armazenado em cache. Como o JIT traduz apenas parte do código, ele consome menos memória e ocupa menos espaço de memória física

  • Antes do tempo

    ART possui um compilador AOT integrado. Durante a instalação do aplicativo, ela compila o bytecode dex em código de máquina e o armazena na memória do dispositivo. Este processo foi projetado para ocorrer quando o aplicativo é instalado no dispositivo. Como a compilação JIT não é mais necessário, a velocidade de execução do código é muito mais rápida

Pilha de tempo de execução do ART

Diferente do DVM, o ART adota uma variedade de esquemas de coleta de lixo, e cada esquema executará diferentes coletores de lixo. O padrão é usar o esquema CMS (Concurrent Mark-Sweep), que é a remoção simultânea de marcas. Este esquema usa principalmente sticky-CMS e CMS parcial. De acordo com diferentes esquemas, o espaço do heap de tempo de execução do ART também será dividido de forma diferente. Por padrão, ele é composto por quatro áreas.

Eles são compostos por Zygote, Active, Image e Large Object, entre os quais a função de Zygote e Active é a mesma do DVM, a área Image é usada para armazenar algumas classes pré-carregadas e o Large Object é usado para alocar objetos grandes (tamanho padrão 12kb), onde Zygote e Image são compartilhados entre processos,

Mecanismo de gerenciamento de memória LMK

LMK (Low Memory Killer) faz parte do mecanismo de gerenciamento de memória do sistema Android. LMK libera processos desnecessários no sistema quando a memória é insuficiente para garantir o funcionamento normal do sistema.

O princípio subjacente do mecanismo LMK é usar o mecanismo OOM do kernel para gerenciar a memória.Quando a memória do sistema for insuficiente, o kernel alocará memória para processos importantes de acordo com a prioridade de cada processo e encerrará processos sem importância ao mesmo tempo para evitar falhas no sistema.

Os cenários de uso do mecanismo LKM incluem:

  • Memória insuficiente do sistema: O mecanismo LMK ajuda o sistema a gerenciar a memória para garantir a operação normal do sistema
  • Vazamento de memória: Quando houver vazamento de memória no aplicativo, o LMK liberará a memória vazada para garantir o funcionamento normal do sistema
  • Otimização de processos: ajude o sistema a gerenciar processos para garantir a utilização razoável dos recursos do sistema

Quando a memória do sistema está limitada, o mecanismo LMK pode liberar memória encerrando processos sem importância para garantir a operação normal do sistema.

Mas também pode causar instabilidade no aplicativo se usado incorretamente.

Por que ocorre o OOM?

O OOM aparece porque o sistema Android limitou o heap da máquina virtual. Quando o espaço solicitado exceder esse limite, o OOM será lançado. O objetivo disso é permitir que o sistema permita que mais processos residam na memória ao mesmo tempo. para que o programa não precise ser recarregado na memória toda vez que o programa for iniciado, o que pode dar ao usuário uma resposta mais rápida

Android obtém o tamanho da memória alocada

val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
manager.memoryClass

Retorna a classe de memória aproximada por aplicativo para o dispositivo atual. Isso permite que você saiba quanto limite de memória você deve impor ao seu aplicativo para que todo o sistema funcione melhor. O valor de retorno é em megabytes; a classe de memória básica do Android é 16 (que é o limite de heap Java para esses dispositivos); alguns dispositivos com mais memória podem retornar um número de 24 ou até mais.

A memória do celular que utilizo é de 16g, e a ligação retorna 256Mb,

manager.memoryClass corresponde a dalvik.vm.heapgrowthlimit em build.prop

Solicite uma memória heap maior

val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
manager.largeMemoryClass

O limite máximo de memória que pode ser alocado precisa ser ativado definindo android:largeHeap="true" no arquivo de manifesto

manager.largeMemoryClass corresponde a dalvik.vm.heapsize em build.prop

Tempo de execução.maxMemory

Obtenha o limite máximo de memória que pode ser obtido pelo processo, que é igual a um dos dois valores acima

/sistema/build.prop

Este diretório é um arquivo relacionado à configuração da memória do Android, que armazena dados como o limite de memória do sistema. Execute o comando adb para ver as informações relacionadas à memória da configuração do Android:

adb shell
cat /system/build.prop

Por padrão, ele não pode ser aberto, não há permissão e é necessário root

Após a abertura, encontre a configuração relacionada ao dalvik.vm

dalvik.vm.heapstartsize=5m	#单个应用程序分配的初始内存
dalvik.vm.heapgrowthlimit=48m	#单个应用程序最大内存限制,超过将被Kill,
dalvik.vm.heapsize=256m  #所有情况下(包括设置android:largeHeap="true"的情形)的最大堆内存值,超过直接oom。

Quando android:largeHeap="true" não estiver definido, oom será acionado enquanto a memória solicitada exceder o heapgrowthlimit, e quando android:largeHeap="true" estiver definido, oom será acionado somente se a memória exceder o heapsize. O heapsize já é a memória máxima que a aplicação pode solicitar (a memória solicitada pelo nativo não está incluída aqui).

Demonstração de OOM

Falha na alocação de memória heap

A falha de alocação de memória heap corresponde a /art/runtime/gc/heap.cc, o código a seguir

oid Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType allocator_type) {
  // If we're in a stack overflow, do not create a new exception. It would require running the
  // constructor, which will of course still be in a stack overflow.
  if (self->IsHandlingStackOverflow()) {
    self->SetException(
        Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
    return;
  }
  //....
  std::ostringstream oss;
  size_t total_bytes_free = GetFreeMemory();
  oss << "Failed to allocate a " << byte_count << " byte allocation with " << total_bytes_free
      << " free bytes and " << PrettySize(GetFreeMemoryUntilOOME()) << " until OOM,"
      << " target footprint " << target_footprint_.load(std::memory_order_relaxed)
      << ", growth limit "
      << growth_limit_;
  
  self->ThrowOutOfMemoryError(oss.str().c_str());
}

Pela análise acima, sabemos também que o sistema possui uma restrição máxima de memória para cada aplicação, e OOM ocorrerá caso esse valor seja ultrapassado. Vamos demonstrar esse tipo de OOM por meio de um trecho de código abaixo

fun testOOM() {
    val manager = requireContext().getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    Timber.e("app maxMemory ${manager.memoryClass} Mb")
    Timber.e("large app maxMemory ${manager.largeMemoryClass} Mb")
    Timber.e("current app maxMemory ${Runtime.getRuntime().maxMemory() / 1024 / 1024} Mb")
    var count = 0
    val list = mutableListOf<ByteArray>()
    while (true) {
        Timber.e("count $count    total ${count * 20}")
        list.add(ByteArray(1024 * 1024 * 20))
        count++
    }
}

No código acima, 20mb são aplicados para cada vez, e o teste é dividido em dois casos,

  1. LargeHeap não está habilitado:
 E  app maxMemory 256 Mb
 E  large app maxMemory 512 Mb
 E  current app maxMemory 256 Mb
 E  count 0    total 0
 E  count 1    total 20
 E  count 2    total 40
 E  count 3    total 60
 E  count 4    total 80
 E  count 5    total 100
 E  count 6    total 120
 E  count 7    total 140
 E  count 8    total 160
 E  count 9    total 180
 E  count 10    total 200
 E  count 11    total 220
 E  count 12    total 240
java.lang.OutOfMemoryError: Failed to allocate a 20971536 byte allocation with 12386992 free bytes and 11MB until OOM, target footprint 268435456, growth limit 268435456
......
可以看到一共分配了 12次,在第十二次的时候抛出了异常,显示 分配 20 mb 失败,空闲只有 11 mb,
  1. Abra grande Heap
app maxMemory 256 Mb                      
large app maxMemory 512 Mb
current app maxMemory 512 Mb
E  count 0    total 0
E  count 1    total 20
E  count 2    total 40
E  count 3    total 60
E  count 4    total 80
E  count 5    total 100
E  count 6    total 120
E  count 7    total 140
E  count 8    total 160
E  count 9    total 180
E  count 10    total 200
E  count 11    total 220
E  count 12    total 240
E  count 13    total 260
E  count 14    total 280
E  count 15    total 300
E  count 16    total 320
E  count 17    total 340
E  count 18    total 360
E  count 19    total 380
E  count 20    total 400
E  count 21    total 420
E  count 22    total 440
E  count 23    total 460
E  count 24    total 480
E  count 25    total 500
FATAL EXCEPTION: main
Process: com.dzl.duanzil, PID: 31874
java.lang.OutOfMemoryError: Failed to allocate a 20971536 byte allocation with 8127816 free bytes and 7937KB until OOM, target footprint 536870912, growth limit 536870912
可以看到分配了25 次,可使用的内存也增加到了 512 mb

Falha ao criar tópico

A criação de threads consome muitos recursos de memória. O processo de criação envolve a camada java e a camada nativa, que é essencialmente concluída na camada nativa, correspondente a /art/runtime/thread.cc, conforme mostrado no código a seguir

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
  //........
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
  {
    std::string msg(child_jni_env_ext.get() == nullptr ?
        StringPrintf("Could not allocate JNI Env: %s", error_msg.c_str()) :
        StringPrintf("pthread_create (%s stack) failed: %s",
                                 PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
    ScopedObjectAccess soa(env);
    soa.Self()->ThrowOutOfMemoryError(msg.c_str());
  }
}

Aqui peguei emprestada uma foto da Internet para ver o processo de criação de um tópico

De acordo com a figura acima, você pode ver que existem duas partes principais, ou seja, a criação de JNI Env e a criação de threads

Falha ao criar ambiente JNI
  1. Estouro de FD faz com que a criação de JNIEnv falhe
E/art: ashmem_create_region failed for 'indirect ref table': Too many open files java.lang.OutOfMemoryError:Could not allocate JNI Env at java.lang.Thread.nativeCreate(Native Method) at java.lang.Thread.start(Thread.java:730)
  1. Memória virtual insuficiente faz com que a criação do JNIEnv falhe
E OOM_TEST: create thread : 1104
W com.demo: Throwing OutOfMemoryError "Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log." (VmSize 2865432 kB)
E InputEventSender: Exception dispatching finished signal.
E MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
MessageQueue-JNI: java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
E MessageQueue-JNI:      at java.lang.Thread.nativeCreate(Native Method)
E MessageQueue-JNI:      at java.lang.Thread.start(Thread.java:887)

E AndroidRuntime: FATAL EXCEPTION: main
E AndroidRuntime: Process: com.demo, PID: 3533
E AndroidRuntime: java.lang.OutOfMemoryError: Could not allocate JNI Env: Failed anonymous mmap(0x0, 8192, 0x3, 0x22, -1, 0): Operation not permitted. See process maps in the log.
E AndroidRuntime:        at java.lang.Thread.nativeCreate(Native Method)
E AndroidRuntime:        at java.lang.Thread.start(Thread.java:887)
Falha ao criar tópico
  1. A máquina virtual falha devido à memória insuficiente

    Native define o tamanho do thread por meio de FixStackSize

static size_t FixStackSize(size_t stack_size) {
  if (stack_size == 0) {
    stack_size = Runtime::Current()->GetDefaultStackSize();
  }
  stack_size += 1 * MB;
  if (kMemoryToolIsAvailable) {
    stack_size = std::max(2 * MB, stack_size);
  }  if (stack_size < PTHREAD_STACK_MIN) {
    stack_size = PTHREAD_STACK_MIN;
  }
  if (Runtime::Current()->ExplicitStackOverflowChecks()) {
    stack_size += GetStackOverflowReservedBytes(kRuntimeISA);
  } else {
    stack_size += Thread::kStackOverflowImplicitCheckSize +
        GetStackOverflowReservedBytes(kRuntimeISA);
  }
  stack_size = RoundUp(stack_size, kPageSize);
  return stack_size;
}

W/libc: pthread_create failed: couldn't allocate 1073152-bytes mapped space: Out of memory
W/tch.crowdsourc: Throwing OutOfMemoryError with VmSize  4191668 kB "pthread_create (1040KB stack) failed: Try again"
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
        at java.lang.Thread.nativeCreate(Native Method)
        at java.lang.Thread.start(Thread.java:753)
  1. O número de threads excede o limite

    Teste-o com um simples trecho de código

fun testOOM() {
    var count = 0
    while (true) {
        val thread = Thread(Runnable {
            Thread.sleep(1000000000)
        })
        thread.start()
        count++
        Timber.e("current thread count $count")
    }
}
通过打印日志发现,一共创建了 2473 个线程,当然这些线程都是没有任务的线程,报错信息如下所示
pthread_create failed: couldn't allocate 1085440-bytes mapped space: Out of memory
Throwing OutOfMemoryError "pthread_create (1040KB stack) failed: Try again" (VmSize 4192344 kB)

FATAL EXCEPTION: main
Process: com.dzl.duanzil, PID: 18085
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
	at java.lang.Thread.nativeCreate(Native Method)
通过测试可以看出来,具体的原因也是内存不足引起的,而不是线程数量超过限制,可能是测试的方法有问题,或者说是还没有达到最大线程的限制,由于手机没有权限,无法查看线程数量限制,所以等有机会了再看。

Monitoramento de OOM

Todos sabemos que grande parte dos motivos para a ocorrência de OOM se deve a vazamentos de memória, que fazem com que a memória não possa ser liberada, então ocorre OOM. Portanto, o monitoramento monitora principalmente vazamentos de memória. Agora o mercado está muito maduro em termos de inspeção de vazamento de memória. Vejamos alguns métodos de monitoramento comumente usados

VazamentoCanário

É muito fácil de usar, você só precisa adicionar dependências e pode usá-lo diretamente. Você pode implementar a detecção de vazamento de memória sem inicialização manual. Quando ocorrer um vazamento de memória, uma notificação será enviada automaticamente e você pode clicar para visualizar o informações específicas da pilha vazada.

LeakCannary só pode ser usado no ambiente de depuração, pois despeja o instantâneo da memória do processo atual e congela o processo atual por um período de tempo, portanto não é adequado para uso no ambiente oficial.

Perfil Android

Você pode visualizar visualmente o uso de memória na forma de imagens e capturar diretamente o despejo de heap ou capturar memória nativa (C/C++) e alocação de memória Java/Kotlin. Ele só pode ser usado offline e a função é muito poderosa, mas há vazamentos de memória, jitter, GC forçado, etc.

RecursosCanário

ResourceCanary é um submódulo do Matrix, que expõe vazamentos de atividades difíceis de encontrar e bitmaps redundantes que foram criados repetidamente e fornece informações como cadeias de referência para ajudar a solucionar esses problemas.

ResourceCanary separa detecção e análise, e o cliente é responsável apenas por detectar e despejar arquivos de imagem de memória e cortar os arquivos Hprof gerados na parte de inspeção para remover a maioria dos dados inúteis. A detecção de objetos bitmap também foi adicionada para facilitar a redução do consumo de memória, reduzindo o número de bitmaps redundantes.

KOOM

Ambos os itens acima só podem ser usados ​​offline, enquanto o KOOM pode ser usado online.KOOM é uma solução completa desenvolvida pela Kuaishou, que pode realizar monitoramento de vazamentos de Java, nativo e thread

Direção de otimização

otimização de imagem

A memória ocupada pela imagem não tem nada a ver com o tamanho da imagem. Depende principalmente da largura e altura da imagem e do método de carregamento da imagem. Por exemplo, ARGB__8888 é duas vezes maior que a memória ocupada por RGB_565 . Para calcular quanto a imagem ocupa na memória, aqui estão várias maneiras de otimizar as imagens

  • Galeria de imagens unificada

    No projeto, você deve evitar o uso de múltiplas bibliotecas de imagens, o que levará a uma série de problemas, como cache repetido de imagens

  • Compressão de imagem

    Para alguns arquivos de recursos de imagem, você pode compactar a imagem ao adicioná-la ao projeto. Aqui está um plug-in recomendado. CodeLocatorEste plug-in parece estar inutilizável há algum tempo. Aqui está um plug-in recomendado McImagepara compactar a imagem quando embalagem.Processamento de compressão.

    Use da seguinte maneira

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.smallsoho.mobcase:McImage:1.5.1'
    }
}
然后在你想要压缩的Module的build.gradle中应用这个插件,注意如果你有多个Module,请在每个Module的build.gradle文件中apply插件
apply plugin: 'McImage'
最后将我代码中的mctools文件夹放到项目根目录
mctools
配置

你可以在build.gradle中配置插件的几个属性,如果不设置,所有的属性都使用默认值
McImageConfig {
        isCheckSize true //是否检测图片大小,默认为true
        optimizeType "Compress" //优化类型,可选"ConvertWebp","Compress",转换为webp或原图压缩,默认Compress,使用ConvertWep需要min sdk >= 18.但是压缩效果更好
        maxSize 1*1024*1024 //大图片阈值,default 1MB
        enableWhenDebug false //debug下是否可用,default true
        isCheckPixels true // 是否检测大像素图片,default true
        maxWidth 1000 //default 1000 如果开启图片宽高检查,默认的最大宽度
        maxHeight 1000 //default 1000 如果开启图片宽高检查,默认的最大高度
        whiteList = [ //默认为空,如果添加,对图片不进行任何处理

        ]
        mctoolsDir "$rootDir"
        isSupportAlphaWebp false  //是否支持带有透明度的webp,default false,带有透明图的图片会进行压缩
        multiThread true  //是否开启多线程处理图片,default true
        bigImageWhiteList = [
                "launch_bg.png"
        ] //默认为空,如果添加,大图检测将跳过这些图片
}
  • monitoramento de imagem

    Ao monitorar todas as fotos do aplicativo, verifique o tamanho da memória ocupada pelas fotos em tempo real, para saber se o tamanho da imagem está adequado, se há vazamento, etc., recomende uma ferramenta de análise, que possa obter o número e tamanho das imagens na memória e pode AndroidBitmapMonitorobter pilha de criação de bitmap, etc.

    Além disso, o monitoramento também pode ser realizado por meio de personalização. Ao monitorar setImageDrawable e outros métodos, o tamanho da imagem e o tamanho do próprio ImageView podem ser obtidos e, em seguida, julgar se o prompt precisa ser modificado. O esquema específico é como segue:

    1. Personalize ImageView, substitua setImageResource, setBackground e outros métodos e detecte o tamanho da imagem nele
    2. Usando um ImageView personalizado, definitivamente não é realista substituí-los um por um. Aqui estão duas maneiras. A primeira é modificar o bytecode durante a compilação e substituir todos os ImageViews por ImageViews personalizados. A segunda é reescrever Layoutinflat .Fractory2, ao criar um Visualize, substitua o ImageVIew por um ImageView personalizado, que pode ser encapsulado e substituído com um clique.
    3. A verificação pode ser feita de forma assíncrona ou quando o thread principal está ocioso.
  • Otimizar método de carregamento

    Em circunstâncias normais, usamos o Glide ou outras bibliotecas de carregamento de imagens para carregar imagens. Por exemplo, o formato de carregamento de imagem padrão do Glide é ARGB_8888. Para esse formato, cada pixel precisa ocupar quatro bytes. Para alguns computadores de baixo custo, digite, em para reduzir a taxa de ocupação de memória, você pode modificar o formato de carregamento da imagem para RGB_565, em comparação com ARGB_8888, cada pixel possui apenas dois bytes, então pode economizar metade da memória, o custo é menos canal transparente, para quem não precisa de canal transparente Para fotos, carregar desta forma é sem dúvida a melhor escolha.

fun loadRGB565Image(context: Context, url: String?, imageView: ImageView) {
    Glide.with(context)
        .load(url)
        .apply(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
        .into(imageView)
}
也可以指定 Bitmap 的格式
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
  • Definir taxa de amostragem de imagem
Options options = new BitmapFactory.Options();
options.inSampleSize = 5; // 原图的五分之一,设置为2则为二分之一
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
                       R.id.myimage, options);
inSampleSize 值越大,图片质量越差,需谨慎设置

Otimização de vazamento de memória

Se a memória não for liberada dentro do ciclo de vida especificado, será considerado um vazamento de memória e, se houver muitos vazamentos de memória, ocupará grande parte da memória, resultando na impossibilidade de aplicação do novo objeto. para memória disponível. OOM aparecerá.

Como observar vazamentos de memória A solução foi proposta acima. Aqui falamos principalmente sobre como evitar alguns vazamentos de memória comuns no processo diário de desenvolvimento.

  1. destruição de memória

    A instabilidade da memória refere-se principalmente à criação frequente de objetos dentro de um período de tempo, e a velocidade de reciclagem não é tão rápida quanto a velocidade de criação e leva a muitas fatias de memória descontínuas.

    Observado pelo Profiler, se o GC ocorrer com frequência e a curva de memória for irregular, é muito provável que ocorra jitter de memória, conforme mostrado na figura a seguir:

Esta situação deve ser causada pela criação frequente de objetos, por exemplo: criação frequente de objetos repetidos no onDraw, criação constante de variáveis ​​locais em loops, etc. São problemas que podem ser evitados diretamente no desenvolvimento, e utilizar o pool de Cache, manualmente libere os objetos no pool de cache, multiplex e assim por diante.

2. Vários métodos comuns de vazamento

  • vazamento de coleção

  • vazamento único

  • Vazamentos anônimos de classe interna

O relacionamento entre a classe interna anônima estática e a classe externa: se não houver nenhum parâmetro passado, não há relacionamento de referência, o chamado não precisa de uma instância da classe externa , o método e a variável da classe externa não podem ser chamados , e tem um ciclo de vida independente

A relação entre a classe interna anônima não estática e a classe externa obtém automaticamente uma referência forte à classe externa.Quando chamada, uma instância da classe externa é necessária, e métodos e variáveis ​​da classe externa podem ser chamados, dependendo de a classe externa, e ainda mais longa que a classe externa.

  • Vazamento causado por arquivos de recursos não fechados
    1. Transmissão da sequência principal
    2. Fechar fluxos de entrada e saída
    3. Reciclar bitmap
    4. Parar de destruir a animação
    5. Destruir o WebView
    6. Efetuar logout do eventBus e dos componentes que precisam ser desconectados a tempo
  • Vazamento de memória causado pelo Handler
如果 Handler 中有延时任务或者等待的任务队列过长,都有可能因为 Handler 的继续执行从而导致内存泄露

解决方法:静态内部类+弱引用

```java
private static class MyHalder extends Handler {

		private WeakReference<Activity> mWeakReference;

		public MyHalder(Activity activity) {
			mWeakReference = new WeakReference<Activity>(activity);
		}

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			//...
		}
	}
	最后在Activity退出时,移除所有信息
	移除信息后,Handler 将会跟Activity生命周期同步
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		mHandler.removeCallbacksAndMessages(null);
	}
}
  • Multithreading leva a vazamentos de memória

    1. Threads iniciados usando uma classe interna anônima manterão uma referência ao objeto externo por padrão
    2. Se o número de threads ultrapassar o limite, ocorrerá um vazamento, neste caso o pool de threads poderá ser utilizado.

Monitoramento de memória e estratégias de ida e volta

Use os vários métodos descritos acima para realizar o monitoramento de memória online/offline.

Você também pode iniciar um thread sozinho para monitorar o uso da memória em tempo real em intervalos regulares.Se a memória estiver em uma emergência, você pode limpar alguns caches que ocupam muita memória, como o Glide, para ajudar.

Use a estratégia bottom-up da Activity, faça alguma remoção do monitoramento de View no onDestory da BaseActivity, defina o plano de fundo como nulo e assim por diante.

Para ajudar todos a compreender melhor a otimização do desempenho de forma abrangente e clara, preparamos notas principais relevantes (voltando à lógica subjacente):https://qr18.cn/FVlo89

Notas principais de otimização de desempenho:https://qr18.cn/FVlo89

Otimização de inicialização

Otimização de memória Otimização

de UI

Otimização de rede

Otimização de bitmap e otimização de compactação de imagem : Otimização de simultaneidade multi-thread e otimização de eficiência de transmissão de dados Otimização de pacote de volumehttps://qr18.cn/FVlo89




"Estrutura de monitoramento de desempenho do Android":https://qr18.cn/FVlo89

"Manual de estudo da estrutura Android":https://qr18.cn/AQpN4J

  1. Processo de inicialização
  2. Inicie o processo Zygote na inicialização
  3. Inicie o processo SystemServer na inicialização
  4. Driver de fichário
  5. Processo de inicialização do AMS
  6. O processo de inicialização do PMS
  7. Processo de inicialização do Launcher
  8. Os quatro principais componentes do Android
  9. Serviço do sistema Android - processo de distribuição do evento Input
  10. Análise do código-fonte do mecanismo de atualização da tela de renderização subjacente do Android
  11. Análise de código-fonte Android na prática

Acho que você gosta

Origin blog.csdn.net/weixin_61845324/article/details/132474657
Recomendado
Clasificación