Mecanismo de carga dinámica de Android

Android puede implementar un mecanismo de carga dinámica. Gracias a la carga de clase diseñada por el equipo de Java Virtual Machine, la acción de "Obtener una secuencia de bytes binarios que describe esta clase a través del nombre completo de una clase" en la etapa de carga de clase se coloca en el virtual de Java Se implementa fuera de la máquina para permitir que la aplicación decida cómo obtener las clases requeridas. El módulo de código que implementa esta acción se denomina "cargador de clases" (consulte "Comprensión profunda de las características y prácticas avanzadas de Java Virtual Machine-JVM" 7.4 Cargador de clases).

1. ClassLoader en Android

ClassLoader en Java es para cargar archivos de clase, y la máquina virtual en Android solo puede reconocer archivos dex, ya sea dvm o art. Por lo tanto, ClassLoader en Java no es aplicable en Android. La clase java.lang.ClassLoader en Android también es diferente de java.lang.ClassLoader en Java.
¿Por qué Android tiene que crear su propia dex en lugar de la clase java?

  • dvm es una máquina virtual basada en registros y la ejecución de jvm es una máquina virtual basada en una pila virtual. La velocidad de acceso al registro es mucho más rápida que la pila, dvm puede lograr la máxima optimización según el hardware, más adecuada para dispositivos móviles.
  • El archivo de clase tradicional es un archivo fuente de Java que generará un archivo .class, mientras que Android fusiona y optimiza todos los archivos de clase, y luego genera un class.dex final. El propósito es mantener solo una copia de diferentes archivos de clase. Si nuestra aplicación de Android no realiza el procesamiento de dex, el apk de la última aplicación solo tendrá un archivo dex.

El tipo ClassLoader en Android también se puede dividir en el sistema ClassLoader y el ClassLoader personalizado. El sistema ClassLoader incluye 3 tipos que son:

  • BootClassLoader, el sistema Android usará BootClassLoader para precargar las clases de uso común. A diferencia de Bootstrap ClassLoader en Java, no está implementado por código C / C ++, sino por Java. BootClassLoader es una clase interna de ClassLoader.
  • PathClassLoader, el nombre completo es dalvik / system.PathClassLoader, puede cargar el Apk instalado, es decir, el archivo apk en / data / app / package, y también puede cargar nativeLibrary en / vendor / lib, / system / lib.
  • DexClassLoader, el nombre completo es dalvik / system.DexClassLoader, puede cargar un archivo apk desinstalado.

Imprima el ClassLoader actual en MainActivity,

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ClassLoader classLoader = getClassLoader();
        while (classLoader != null) {
            System.out.println("classLoader: " + classLoader);
            classLoader = classLoader.getParent();
        }
    }
}

Los resultados son los siguientes:

dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.sososeen09.classloadtest-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
java.lang.BootClassLoader@aced87d

También se puede confirmar a partir de los resultados impresos: el cargador de clases del sistema de aplicaciones es PathClassLoader, y BootClassLoader es su cargador de clases padre.

En segundo lugar, el análisis de ClassLoader
En Android, estamos principalmente preocupados por PathClassLoader y DexClassLoader.
PathClassLoader y DexClasLoader heredan de dalviksystem.BaseDexClassLoader, y su lógica de carga de clases está escrita en BaseDexClassLoader. PathClassLoader se utiliza para manipular la colección de archivos y directorios en el sistema de archivos local. DexClassLoader puede cargar un APK desinstalado u otros archivos JAR / ZIP que contengan archivos dex. DexClassLoader necesita una carpeta privada, legible y grabable para que la aplicación almacene en caché los archivos de clase optimizados. Y tenga cuidado de no almacenar los archivos optimizados en un almacenamiento externo para evitar someter su aplicación a ataques de inyección de código.
En Android, no es el ClassLoader el responsable específico de la carga de clases, sino la carga a través del método defineClassNative () de DexFile.
Inserte la descripción de la imagen aquíTres, ejemplo de carga dinámica (reparación en caliente)
1. Cree un nuevo módulo llamado dextest, agregue este es el módulo que queremos cargar dinámicamente.
Cree una nueva clase llamada ShowToast:

public class ShowToast {

    public void showToast(Context context) {
        Toast.makeText(context, "动态加载ShowToast", Toast.LENGTH_SHORT).show();
    }
}

Solo hay un método showToast en la clase ShowToast, que requiere un parámetro de contexto para mostrar un brindis.
2. Después de que la clase java esté empaquetada en dex y
compilada, el archivo de clase correspondiente se generará en el directorio como se muestra en la figura (la ruta puede ser diferente en diferentes versiones de android studio). Dado que la máquina virtual java utiliza el archivo de clase, Sin embargo, la máquina virtual Dalvik en Android usa archivos dex, por lo que debemos convertirla nuevamente.
Inserte la descripción de la imagen aquíTodavía agregue el último archivo build.gradle en este módulo:

//删除isshowtoast.jar包任务
task clearJar(type: Delete) {
    delete 'build/libs/in.jar'
}
task makeJar(type:org.gradle.api.tasks.bundling.Jar){
    //指定生成的jar名
    baseName 'in'
    //从哪里打包class文件
    from('build/intermediates/javac/debug/classes/com/example/dextest/')
    //打包到jar后的目录结构
    into('com/example/dextest/')
    //去掉不需要打包的目录和文件
    exclude('test/','BuildConfig.class','R.class')
    //去掉R$开头的文件
    exclude{it.name.startsWith('R$')}
}
makeJar.dependsOn(clearJar,build)

Esto empaquetará los archivos que necesitamos en el paquete jar, pero el paquete jar no es lo que necesitamos, continúe.
Se nos proporciona un comando dx en el SDK de Android (que se encuentra en \ android-sdk \ build-tools \ version [23.0.1] o \ android-sdk \ platform-tools); el uso del comando es: dx- -dex --output = out.jar in.jar, este comando convierte la clase que contiene in.jar en el archivo out.jar que contiene dex.
3. Llamada del lado de la aplicación
Copie el paquete out.jar generado en el directorio de activos en el proyecto de la aplicación:
Inserte la descripción de la imagen aquíestamos aquí por conveniencia, pero el escenario puede ser que el servidor remoto se descargue en la ubicación especificada.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                loadDex();
            }
        });
    }

    /**
     * 点击事件
     */
    public void loadDex() {
        File cacheFile = getDir("dex",0);
        String internalPath = cacheFile.getAbsolutePath() + File.separator + "out.jar";
        File desFile=new File(internalPath);
        try {
            if (!desFile.exists()) {
                desFile.createNewFile();
                FileUtils.copyFiles(this,"out.jar",desFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //下面开始加载dex class
        //1.待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限,
        //2.解压后的dex存放位置,此位置一定要是可读写且仅该应用可读写
        //3.指向包含本地库(so)的文件夹路径,可以设为null
        //4.父级类加载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()取到。
        DexClassLoader dexClassLoader=new DexClassLoader(internalPath,cacheFile.getAbsolutePath(),null,getClassLoader());
        try {
            Class clz = dexClassLoader.loadClass("com.example.dextest.ShowToast");
            Method method = clz.getDeclaredMethod("showToast", Context.class);
            method.invoke(clz.newInstance(), this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

El archivo FileUtils aquí es para copiar el paquete jar del directorio de activos al directorio de datos de la aplicación:

public class FileUtils {
    // 把assets目录的文件复制到desFile中
    public static void copyFiles(Context context, String fileName, File desFile){
        InputStream in=null;
        OutputStream out=null;

        try {
            in=context.getApplicationContext().getAssets().open(fileName);
            out=new FileOutputStream(desFile.getAbsolutePath());
            byte[] bytes=new byte[1024];
            int len=0;
            while ((len=in.read(bytes))!=-1)
                out.write(bytes,0,len);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (in!=null)
                    in.close();
                if (out!=null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Al principio, me encontré con una excepción ClassNotFoundException:

ClassLoader referenced unknown path: android.content.res.AssetManager@d75e3f9/out.jar

 java.lang.ClassNotFoundException: Didn't find class "com.example.dextest.ShowToast" on path: DexPathList[[],nativeLibraryDirectories=[/system/lib, /system/vendor/lib]]
     at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
     at com.example.test.MainActivity.loadDex(MainActivity.java:44)

Porque al principio, utilicé directamente el paquete jar en el directorio de activos, como si no pudiera cargarse, luego usé FileUtils para copiar el paquete jar a otro directorio.
Haga clic en el botón en la máquina de prueba para ver el efecto:
Inserte la descripción de la imagen aquí
para resumir, use DexClassLoader para obtener la clase en dex, y luego ejecute el método en la clase a través de la reflexión.

Todavía hay algunas preguntas:
a. Si la clase original tiene errores, ¿cómo hacerlo ahora para reemplazar la clase original dinámicamente?
Consulte el hotfix de Android y use Javassist.
b) Si desea cargar dinámicamente nuevas actividades, ¿cómo gestionar el ciclo de vida?
c. ¿Cómo cargar archivos de recursos?

Referencia: serie mecanismo de carga clase 2-- Android la comprensión en profundidad del cargador de clases
Android dex dinámicamente cargado Obtención
Android Dex dinámico proceso de carga

230 artículos originales publicados · Me gusta 94 · Visita 270,000+

Supongo que te gusta

Origin blog.csdn.net/yu75567218/article/details/105070236
Recomendado
Clasificación