Otimização de desempenho do Android ---- otimização do tempo de execução

Autor: lu todo mundo sabe

Quando o aplicativo está fazendo otimização de inicialização, o aplicativo fará algum trabalho de inicialização, mas não fará operações demoradas no aplicativo. No entanto, algum trabalho de inicialização pode ser demorado, então o que devo fazer? A operação de inicialização pode ser concluída iniciando um thread filho.

Calcular o tempo de execução

  • Solução convencional (marca manual de ponto enterrado)
  • AOP maneira de obter

1. Plano convencional

A solução convencional é marcar a hora de início antes da execução, marcar a hora de término após a execução e então calcular a diferença entre a hora de início e a hora de término.A diferença de tempo é o tempo que consome tempo.

A implementação específica do cálculo demorado é mostrada no código a seguir. Muitos métodos de inicialização são chamados no método onCreate em Application. Calculamos o tempo demorado de cada método enterrando manualmente a marca.

//Application.java
@Override
public void onCreate() {
    initSharedPreference();
    initImageLoader();
    initSQLite();
    //.....
}

private void initSharedPreference() {
    long startTime = System.currentTimeMillis();
    //init SharedPreference
    Log.d(TAG, "initSharedPreference cost :" + (System.currentTimeMillis() - startTime));    
}

private void initImageLoader() {
    long startTime = System.currentTimeMillis();
    Fresco.initialize(this);
    Log.d(TAG, "initImageLoader cost :" + (System.currentTimeMillis() - startTime));
}
private void initSQLite() {
    long startTime = System.currentTimeMillis();
    //init bugly
    Log.d(TAG, "initSQLite cost :" + (System.currentTimeMillis() - startTime));    
}

O método de cálculo acima é um método de implementação fácil de pensar, mas as desvantagens também são óbvias:

  • Cada método é demorado para marcar e calcular, e o código não é elegante o suficiente.
  • Muito invasivo para o projeto e muito trabalho.

2. AOP maneira de obter

AOP é o que costumamos dizer 面向切面编程, pode ser 同一类问题direcionado 统一处理.

A seguir, usaremos o AOP para obter elegantemente o tempo de execução de cada método da Aplicação.

2.1 Apresentando AspectJ

Use o AOP no Android por meio da biblioteca AspectJ e, em seguida, apresente esta biblioteca:

  • Introduza o plug-in AspectJ na raiz do projeto build.gradle
    • classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
  • Plug-in do aplicativo Build.gradle no módulo
    • aplicar plug-in: 'android-aspectjx'
  • Introduza a biblioteca aspectj em build.gradle no Módulo
    • implementação 'org.aspectj:aspectjrt:1.8.9'

2.2 Uso específico de AOP

  • Defina uma classe PerformanceAop usando a anotação @Aspect
  • @Around("execution(* com.lu.aop.MyApplication.**(...))") indica que cada método em MyApplication precisa ser conectado.
  • Registre os carimbos de data/hora antes e depois da execução do método e calcule a diferença de tempo correspondente.
@Aspect
public class PerformanceAop {

    private static final String TAG = PerformanceAop.class.getSimpleName();

    @Around("execution(* com.lu.aop.MyApplication.**(..))")
    public void getTime(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        //得到方法的名字,例如:MyApplication.attachBaseContext(..)
        String name = signature.toShortString();
        //记录方法执行前的时间戳
        long startTime = System.currentTimeMillis();

        try {
            //执行该方法
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        //记录执行该方法的时间
        long costTime = System.currentTimeMillis() - startTime;

        Log.e(TAG, "method " + name + " cost:" + costTime);
    }
}

运行结果
2019-08-18 17:09:12.946 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.attachBaseContext(..) cost:1
2019-08-18 17:09:12.979 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.initSQLite() cost:11
2019-08-18 17:09:13.002 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.initImageLoader() cost:17
2019-08-18 17:09:13.002 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.onCreate() cost:28

O AOP é implementado de maneira mais elegante, não é invasivo para o código existente e é fácil de modificar.

Execute tarefas demoradas de forma assíncrona

Executar a inicialização dessas bibliotecas de terceiros no aplicativo retardará o processo de inicialização de todo o aplicativo. Portanto, o subthread e o thread principal são usados ​​em paralelo para compartilhar o trabalho do thread principal, reduzindo assim a execução hora da thread principal.

Executar tarefas em threads filho

Podemos facilmente pensar nas duas maneiras a seguir:

  • método um
  public void onCreate(){
      new Thread() {
          public run() {
            //执行任务1
            //执行任务2
            //执行任务3
          }
      }.start();
    }
  • caminho dois
public void onCreate(){
    new Thread() {
        public run() {
            //执行任务1
        }
    }.start();
    
    new Thread() {
        public run() {
            //执行任务2
        }
    }.start();
    
    new Thread() {
        public run() {
            //执行任务3
        }
    }.start();
}

O segundo método faz uso total dos recursos da CPU, mas não é elegante o suficiente para criar threads diretamente, portanto, é melhor usar um pool de threads para gerenciar esses threads.

Gerenciamento de pool de threads

O pool de threads correspondente é obtido, mas o número de threads não pode ser preenchido à vontade. Devemos aproveitá-lo ao máximo CPU 资源, para que possamos nos referir a AsyncTaskcomo ele é definido 核心线程数.

Executors service = Executors.newFixedThreadPool(核心线程个数);

Consulte o código-fonte AsyncTask para entender a configuração do número do thread principal

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//CORE_POOL_SIZE 就是核心线程数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));

Desta forma, podemos definir o número de threads principais

//参考AsyncTask来设置线程的个数。
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);

Implemente a execução de tarefa assíncrona em MyApplication:

@Override
public void onCreate() {
    super.onCreate();
    //参考AsyncTask来设置线程的个数。
    ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
    service.submit(new Runnable() {
        @Override
        public void run() {
            initSQLite();
        }
    });
    service.submit(new Runnable() {
        @Override
        public void run() {
            initImageLoader();
        }
    });
}

异步加载的代码执行结果
2019-08-18 19:09:38.022 13948-13948/com.lu.aop E/PerformanceAop: method MyApplication.attachBaseContext(..) cost:1
2019-08-18 19:09:38.062 13948-13948/com.lu.aop E/PerformanceAop: method MyApplication.onCreate() cost:4
2019-08-18 19:09:38.078 13948-13967/com.lu.aop E/PerformanceAop: method MyApplication.initSQLite() cost:9
2019-08-18 19:09:38.094 13948-13968/com.lu.aop E/PerformanceAop: method MyApplication.initImageLoader() cost:15

A partir da comparação dos dados do Log log, pode-se observar que o tempo de execução do método onCreate executado pela thread principal foi reduzido dos 28ms originais para 4ms. Portanto, é muito bom usar sub-threads para executar tarefas demoradas. Mas há outro problema aqui, ou seja, existem alguns métodos que devem ser inicializados antes que a execução do Application onCreate seja concluída, pois precisam ser utilizados na MainActivity, então haverá problemas com o assíncrono acima, então como resolver este problema Pano de lã?

Tarefas assíncronas devem ser executadas em um determinado estágio

Tomando initImageLoader() como exemplo, não sei quando as sub-threads na Aplicação irão completar a tarefa de inicialização, mas neste momento, elas já entraram na MainActivity, e ImageLoader é usado. ImageLoader não pode ser usado antes da inicialização é concluído no aplicativo, portanto, o ImageLoader deve ser controlado. A inicialização é concluída antes que a execução onCreate do aplicativo seja concluída. Então você precisa usar o CountDownLatch.

CountDownLatch é uma classe de ferramenta de sincronização no pacote java.util.concurrent que permite que um ou mais encadeamentos esperem até que um conjunto de operações em outros encadeamentos seja concluído.

  • Definir CountDownLatch no aplicativo
//Application
private CountDownLatch countDownLatch = new CountDownLatch(1);
  • Quando o método initImageLoader for executado, execute countDownLatch.countDown()
private void initImageLoader() {
  Fresco.initialize(this);
   //try {
     //模拟耗时
     //Thread.sleep(3000);
   //} catch (Exception e) {
      // e.printStackTrace();
   //}
   //Log.e(TAG, "初始化initImageLoader完毕");
   //数量减一
   countDownLatch.countDown();
   
}
  • await countDownLatch.await()

Aguarde no final do método onCreate, se countDownLatch.countDown() for chamado antes aqui, pule diretamente, caso contrário, aguarde aqui.

public void onCreate() {
   super.onCreate();
   //参考AsyncTask来设置线程的个数。
   ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
   service.submit(new Runnable() {
       @Override
       public void run() {
           initSQLite();
       }
   });
   service.submit(new Runnable() {
       @Override
       public void run() {
           initImageLoader();
       }
   });
   
   
   //在 onCreate 方法中等待,如果在此处之前之前调用了countDownLatch.countDown(),那么就直接跳过,否则就在此等待。
   try {
       countDownLatch.await();
   } catch (InterruptedException e) {
       e.printStackTrace();
   }
   Log.e(TAG, "Application onCreate 执行完毕");
}

Dessa forma, nosso método onCreate do aplicativo aguardará a conclusão da tarefa assíncrona initImageLoader antes de encerrar o ciclo de vida do método onCreate.

Resumir

  • Compreendendo o cálculo do tempo de execução da tarefa
  • Compreender o conhecimento de programação orientada a aspectos AOP
  • Entenda o número do thread principal e a aplicação do AsyncTask
  • Aprendeu o esquema de otimização assíncrona ao inicializar dados

Para ajudar todos a entender melhor a otimização de desempenho de maneira abrangente e clara, preparamos notas básicas relevantes (retornando à lógica subjacente):https://qr18.cn/FVlo89

Notas básicas de otimização de desempenho:https://qr18.cn/FVlo89

Otimização de inicialização

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

de interface do usuário

Otimização de rede

Otimização de bitmap e otimização de compressã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 aprendizado do Android Framework":https://qr18.cn/AQpN4J

  1. Processo de inicialização de inicialização
  2. Inicie o processo Zygote na inicialização
  3. Inicie o processo do 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 iniciador
  8. Os quatro principais componentes do Android
  9. Serviço do sistema Android - processo de distribuição do evento de entrada
  10. Análise de código-fonte do mecanismo de atualização de tela de renderização subjacente do Android
  11. Análise de código-fonte do Android na prática

Acho que você gosta

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