Entrevistador: Habla sobre la optimización de inicio de Android

La velocidad de inicio de una aplicación es muy importante para una aplicación y afectará directamente la experiencia del usuario. Si la velocidad de inicio es demasiado lenta, provocará la pérdida de usuarios. Este artículo analiza la optimización de la velocidad de inicio y proporciona algunas ideas para optimizar la velocidad de inicio.

1. Clasificación de inicio de la aplicación

La aplicación tiene tres estados de inicio, cada uno de los cuales afecta el tiempo necesario para que la aplicación se muestre al usuario: inicio en frío, inicio en caliente y inicio en caliente. En un arranque en frío, la aplicación comienza desde el principio. En los otros dos estados, el sistema necesita poner en primer plano las aplicaciones que se ejecutan en segundo plano.

1.1, arranque en frío

El arranque en frío de la aplicación se puede dividir en dos etapas

El primer escenario

1. Cargue e inicie la aplicación

2. Se mostrará una ventana de inicio en blanco inmediatamente después del inicio.

3. Crea el proceso de la aplicación

Segunda etapa

1. Crea un objeto de aplicación

2. Inicie el hilo principal

3. Crea la actividad principal

4. Cargue el diseño

5. Diseñe la pantalla

6. Dibuja el primer cuadro

Una vez que el proceso de aplicación termina de dibujar el primer marco, el proceso del sistema reemplazará la ventana de fondo que se muestra actualmente y la reemplazará con la Actividad principal. En este punto, el usuario puede comenzar a usar la aplicación.

En el arranque en frío, como desarrollador, las partes principales que pueden intervenir son la fase OnCreate de la Aplicación y la fase onCreate de la Actividad, como se muestra en la siguiente figura:

1.2, arranque en caliente

Durante un inicio en caliente, el sistema pone la actividad en primer plano. Si todas las actividades de la aplicación existen en la memoria, la aplicación puede evitar operaciones repetidas de inicialización, renderizado y dibujo de objetos.

1.3, inicio en caliente

Durante el arranque en caliente, dado que el proceso de la aplicación aún existe, se ejecuta la segunda etapa del arranque en frío.

1. Crea un objeto de aplicación

2. Inicie el hilo principal

3. Crea la actividad principal

4. Cargue el diseño

5. Diseñe la pantalla

6. Dibuja el primer cuadro

Escenarios comunes de inicio en caliente:

1. El usuario reinicia la aplicación después de salir de la aplicación. El proceso puede continuar ejecutándose. Pero la aplicación comienza a ejecutarse nuevamente al llamar al método onCreate de Activity

2. Memoria insuficiente, el sistema cerrará la aplicación y luego el usuario se reiniciará. El proceso y la actividad deben reiniciarse, pero el paquete de instancias guardado pasado a onCreate es útil para completar el inicio

A continuación, veamos cómo obtener la hora de inicio ~

2. Obtenga la hora de inicio

2.1, use el comando adb para obtener

adb shell am start -W [packageName] / [packageName.xxActivity]

adb shell am start -W com.tg.test.gp/com.test.demo.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.tg.test.gp/com.test.demo.MainActivity }
Status: ok
LaunchState: COLD
Activity: com.tg.test.gp/com.test.demo.MainActivity
ThisTime: 1344
TotalTime: 1344
WaitTime: 1346
Complete

ThisTime: La última actividad tarda en comenzar

TotalTime: el tiempo total consumido por todas las actividades experimentadas al inicio

WaitTime: el tiempo total para que AMS inicie todas las actividades (incluido el tiempo antes de iniciar la aplicación de destino)

2.2, use código para administrar

public class LaunchTimer {

    private static long sTime;

    public static void startRecord() {
        sTime = System.currentTimeMillis();
    }
    public static void endRecord() {
        endRecord("");
    }
    public static void endRecord(String msg) {
        long cost = System.currentTimeMillis() - sTime;
        LogUtils.i(msg + "cost " + cost);
    }
}

Este método generalmente establece la marca de tiempo de inicio para el método attachBaseContext de inicialización de la aplicación, y la marca de tiempo de finalización se establece después de que la interfaz operativa del usuario de la aplicación se muestre completamente y esté operativa. La diferencia de dos tiempos es la hora de inicio.

Pero este método no es elegante, si la lógica de inicio es complicada y hay muchos métodos, es mejor usar aop para la optimización.

3. Herramienta de análisis de optimización de inicio de aplicaciones

3.1 、 TraceView

Traceview es una herramienta de análisis de rendimiento que viene con Android. Puede mostrar gráficamente el tiempo de llamada al método, la pila de llamadas y ver toda la información del hilo . Analizar métodos lleva mucho tiempo y la cadena de llamadas es una muy buena herramienta.

El método de uso es utilizar el método de enterramiento de código:

1. Al comienzo de la posición de la colección, ejecute Debug.startMethodTracing("app_trace"), donde el parámetro es el nombre del archivo personalizado.

2. Al final de la posición de recolección, ejecuteDebug.endMethodTracing()

La ruta de generación del archivo es / sdcard / Android / data / package name / files / file name.trace, el archivo se puede abrir usando Android Studio

por ejemplo:

Echemos un vistazo al método de testAdd que consume mucho tiempo

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        testAdd(3, 5);
    }

    private void testAdd(int a, int b) {
        Debug.startMethodTracing("app_trace");
        int c = a + b;
        Log.i("Restart", "c =  a + b = " + c);
        Debug.stopMethodTracing();
    }
}

Después de ejecutar el programa, busque el archivo /sdcard/Android/data/com.restart.perference/files/app_trace.trace, haga doble clic en él en AndroidStudio, puede analizar la información en el archivo y abrirlo de la siguiente manera:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo de enlace anti-sanguijuelas. Se recomienda guardar la imagen y subirla directamente (img-R2s1gpZz-1616421092820) (https://upload-images.jianshu.io/ upload_images / 25094154-7d9a059e870d906c.image? imageMogr2 / auto-orient / strip% 7CimageView2 / 2 / w / 1240)]

Echemos un vistazo a cómo usar CallChart, FlameChart, Top Down y Bottom Up respectivamente. Primero, seleccione la zona horaria. Como en este caso, debido a que el programa es muy simple, el área que se puede analizar es relativamente pequeña. Después de seleccionarla, puede obtener la siguiente imagen:

imagen

En el gráfico obtenido por Call Chart , puede ver la pila de llamadas completa del programa y el método que consume mucho tiempo.

Por ejemplo, el método testAdd de la figura ha llamado sucesivamente a los métodos Debug.startMethodTracing, Log.i y Debug.stopMethodTracing. Al mismo tiempo, se puede ver en la figura que el método startMethodTracing tarda más que el método stopMethodTracing. En la optimización real, encontrar qué método lleva mucho tiempo y la optimización dirigida es muy útil.

Al analizar, generalmente no podemos optimizar programas de terceros y códigos de sistema. CallChart usa colores de manera muy íntima para ayudarnos a distinguir qué códigos están escritos por nosotros mismos. Las naranjas son API del sistema, las azules son generalmente API de terceros y las verdes las escribimos nosotros mismos. Por ejemplo, en el método testAdd de la figura, lo que escribimos nosotros mismos se puede ajustar y optimizar.

Flame Chart es un gráfico inverso de Call Chart, con funciones similares, los gráficos son los siguientes:

Top Down puede ver qué métodos se llaman dentro de cada método, así como la proporción de cada método que consume mucho tiempo y tiempo. En comparación con la búsqueda gráfica de Call Chart, Top Down es más específico y tiene datos de métodos específicos que requieren mucho tiempo .

En la figura, Total representa el tiempo total consumido por el método, self representa el tiempo consumido por el código que no llama al método en el método y Children representa el tiempo consumido por otros métodos llamados en el método.

Tome el método testAdd como ejemplo. El tiempo total consumido por el método testAdd es 3840us, lo que representa el 97% del tiempo de ejecución del programa. El código en el método testAdd toma 342us, lo que representa el 8,65%, y los otros métodos llamados en el El método testAdd toma un total de 3498us. Representa el 88.52%

private void testAdd(int a, int b) {
        Debug.startMethodTracing("app_trace");//算到Children中
        int c = a + b;//这一句是算在self耗时中,耗时其实很短
        Log.i("Restart", "c =  a + b = " + c);//算到Children中
        Debug.stopMethodTracing();//算到Children中
}

Bottom Up es el gráfico inverso de Top Down, puede ver qué método es llamado por el método

imagen

Hay otra opción que vale la pena señalar en TraceView,

En la esquina superior derecha, hay una opción de Wall Clock Time y Thread Time . Wall Clock Time significa el tiempo real consumido por el método y Thread Time se refiere al tiempo de CPU. Por lo general, hablamos de optimización más acerca de optimizar el tiempo de la CPU. Cuando hay operaciones de E / S, es más razonable usar Thread Time para analizar el tiempo

Además, el uso de TraceView debe prestar atención a la sobrecarga de tiempo de ejecución de TraceView, porque en sí mismo lleva mucho tiempo, lo que puede llevarnos a optimizar la dirección.

3.2 、 Systrace

Systrace combina los datos del kernel de Android para generar informes HTML

systrace se encuentra en el directorio Android / sdk / platform-tools / systrace. Debe instalar Python antes de usarlo, porque systrace usa Python para generar html

Reportado, el comando es el siguiente:

python systrace.py -b 32768 -t 10 -a 包名 -o perference.html sched gfx view wm am app

Para parámetros específicos, consulte: developer.android.google.cn/studio/prof ...

Después de ejecutar el comando, abra el informe, se muestra de la siguiente manera

Utilice el navegador Chrome para abrir; de lo contrario, puede aparecer una pantalla en blanco. Si usa Chrome para mostrar una pantalla blanca, puede ingresar chrome: tracing en el navegador Chrome y luego cargar el archivo para mostrar

Al ver la imagen, la tecla A se mueve hacia la izquierda, la tecla D para moverse a la derecha, la tecla S para alejar y la tecla W para acercar

4. Optimización común

4.1. Estrategias de optimización comunes para la carga de arranque

Cuanto más grande sea una aplicación, más módulos involucrados, más servicios e incluso procesos contiene, como la inicialización del módulo de red, la inicialización de datos de bajo nivel, etc. Estas cargas deben prepararse con anticipación y algunas innecesarias no deben colocarse En la aplicacion. Los puntos de partida generalmente se pueden clasificar a partir de las siguientes cuatro dimensiones:

1. Necesario y requiere mucho tiempo: comience la inicialización, considere usar subprocesos para inicializar

2. Necesario y no requiere mucho tiempo: no es necesario ocuparse de

3. Informes de datos, inicialización de complementos y procesamiento bajo demanda que consumen mucho tiempo innecesariamente

4. No es esencial y requiere mucho tiempo: simplemente retírelo y cárguelo cuando sea necesario

El contenido que se ejecutará cuando se inicie la aplicación se clasifica como se describe anteriormente, y la lógica de carga se implementa a pedido. Entonces, ¿cuáles son las estrategias de carga optimizadas comunes?

Carga asincrónica : la carga que consume mucho tiempo se ejecuta de forma asincrónica en el subproceso secundario

Carga diferida : carga diferida de datos no esenciales

Carga anticipada: use ContentProvider para inicializar por adelantado

A continuación se presentan algunos procesos comunes de carga asincrónica y carga retrasada

4.2, carga asincrónica

La carga asincrónica, en términos simples, es utilizar subprocesos para cargar de forma asincrónica. En escenarios reales, a menudo es necesario inicializar varias bibliotecas de terceros durante el inicio. Al colocar la inicialización en el subproceso secundario, el inicio se puede acelerar considerablemente.

Pero, por lo general, parte de la lógica empresarial solo puede ejecutarse normalmente después de la inicialización de la biblioteca de terceros. En este momento, si simplemente la coloca en un subproceso para que se ejecute, es probable que la lógica empresarial se ejecute antes de que se inicie la inicialización. completado sin restricciones.

En esta situación más complicada, puede usar CountDownLatch para lidiar con, o usar la idea de lanzador para lidiar.

Uso de CountDownLatch

class MyApplication extends Application {

    // 线程等待锁
    private CountDownLatch mCountDownLatch = new CountDownLatch(1);

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

    void onCreate() {
		ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
        service.submit(new Runnable() {
            @Override public void run() {
            	//初始化weex,因为Activity加载布局要用到需要提前初始化完成
                initWeex();
                mCountDownLatch.countDown();
            }
        });

        service.submit(new Runnable() {
            @Override public void run() {
            	//初始化Bugly,无需关心是否在界面绘制前初始化完
                initBugly();
            }
        });

        //提交其他库初始化,此处省略。。。

		try {
            //等待weex初始化完才走完onCreate
            mCountDownLatch.await();
		} catch (Exception e) {
            e.printStackTrace();
		}
    }
}

Se recomienda utilizar CountDownLatch cuando la lógica de inicialización no es complicada. Sin embargo, si existen interdependencias entre las bibliotecas inicializadas y la lógica es compleja, se recomienda utilizar el cargador.

Lanzacohetes

El núcleo del lanzador es el siguiente:

  • Aproveche al máximo la capacidad de múltiples núcleos de la CPU, clasifique y ejecute automáticamente las tareas en secuencia;
  • El código está asignado y la tarea de inicio se abstrae en cada tarea;
  • Genere un gráfico acíclico dirigido de acuerdo con la clasificación de todas las dependencias de tareas;
  • El subproceso múltiple se ejecuta en orden de prioridad de subproceso

Para una implementación específica, consulte: github.com/NoEndToLF/A ...

4.3, carga diferida

La inicialización de algunas bibliotecas de terceros en realidad no es de alta prioridad y se puede cargar a pedido. O use IdleHandler para inicializar en lotes cuando el hilo principal está inactivo.

La carga bajo demanda se puede implementar de acuerdo con condiciones específicas, por lo que no entraré en detalles aquí. Aquí hay una introducción al uso de IdleHandler

    private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            //当return true时,会移除掉该IdleHandler,不再回调,当为false,则下次主线程空闲时会再次回调
            return false;
        }
    };

Utilice IdleHandler para realizar la inicialización por lotes, ¿por qué desea realizar el lote? Cuando el subproceso principal está inactivo, IdleHandler se ejecuta, pero si el contenido de IdleHandler es demasiado, aún provocará una congelación. Por lo tanto, es mejor agrupar la operación de inicialización cuando el hilo principal está inactivo.

public class DelayInitDispatcher {

    private Queue<Task> mDelayTasks = new LinkedList<>();

    private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            //每次执行一个Task,实现分批进行
            if(mDelayTasks.size()>0){
                Task task = mDelayTasks.poll();
                new DispatchRunnable(task).run();
            }
            //当为空时,返回false,移除IdleHandler
            return !mDelayTasks.isEmpty();
        }
    };

    //添加初始化任务
    public DelayInitDispatcher addTask(Task task){
        mDelayTasks.add(task);
        return this;
    }

    //给主线程添加IdleHandler
    public void start(){
        Looper.myQueue().addIdleHandler(mIdleHandler);
    }

}

4.4, carga anticipada

El tiempo más rápido para inicializar en el esquema anterior es en onCreate de la aplicación, pero hay una forma anterior. El onCreate del ContentProvider se lleva a cabo entre los métodos attachBaseContext y onCreate de la Aplicación. En otras palabras, se ejecuta antes que el método de aplicación onCreate. Entonces puede usar esto para cargar por adelantado la inicialización de bibliotecas de terceros.

uso de androidx-startup

如何使用:
第一步,写一个类实现Initializer,泛型为返回的实例,如果不需要的话,就写Unit
class TimberInitializer : Initializer<Unit> {

    //这里写初始化执行的内容,并返回初始化实例
    override fun create(context: Context) {
        if (BuildConfig.DEBUG) {
            Timber.plant(Timber.DebugTree())
            Timber.d("TimberInitializer is initialized.")
        }
    }

    //这里写初始化的东西依赖的另外的初始化器,没有的时候返回空List
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }

}

第二步,在AndroidManifest中声明provider,并配置meta-data写初始化的类
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="com.test.pokedex.androidx-startup"
    android:exported=“false"
    //这里写merge是因为其他模块可能也有同样的provider声明,做合并操作
    tools:node="merge">
    //当有相互依赖的情况下,写顶层的初始化器就可以,其依赖的会自动搜索到
    <meta-data
        android:name="com.test.pokedex.initializer.TimberInitializer"
        android:value="androidx.startup" />
</provider>

4.5. Otras optimizaciones

1. En la aplicación, agregue una imagen predeterminada de inicio o personalice un tema, y ​​primero use una interfaz predeterminada en la Actividad para resolver el problema de la pantalla en blanco y negro corta de inicio parcial. Como android: theme = "@ style / Theme.AppStartLoad"

5. Resumen

1. El procesamiento principal de arranque en frío, arranque en caliente y arranque en caliente y sus diferencias.

2. Cómo obtener la hora de inicio, introdujo las dos formas de usar el comando adb y la administración de código

3. Cómo usar herramientas para encontrar el código que consume mucho tiempo en el programa e introducir el uso de TraceView y Systrace

4. Introducción a implementaciones y métodos de optimización de inicio comunes (carga asincrónica, carga retrasada, carga temprana, etc.), ideas clave: carga asincrónica, retrasada, diferida

Supongo que te gusta

Origin blog.csdn.net/zhireshini233/article/details/115101211
Recomendado
Clasificación