Android dynamic loading mechanism

Android can implement a dynamic loading mechanism. Thanks to the class loading designed by the Java Virtual Machine team, the action of "Get a binary byte stream describing this class through the fully qualified name of a class" in the class loading stage is placed in the Java virtual It is implemented outside the machine to allow the application to decide how to obtain the required classes. The code module that implements this action is called a "class loader" (refer to "In-depth understanding of the Java Virtual Machine-JVM advanced features and best practices" 7.4 Class loader).

1. ClassLoader in Android

ClassLoader in Java is to load class files, and the virtual machine in Android can only recognize dex files whether it is dvm or art. Therefore, ClassLoader in Java is not applicable in Android. The java.lang.ClassLoader class in Android is also different from java.lang.ClassLoader in Java.
Why does Android have to create its own dex instead of java class?

  • dvm is a register-based virtual machine and jvm execution is a virtual machine based on a virtual stack. Register access speed is much faster than the stack, dvm can achieve maximum optimization according to hardware, more suitable for mobile devices.
  • The traditional Class file is a Java source file that will generate a .class file, while Android merges and optimizes all Class files, and then generates a final class.dex. The purpose is to keep only one copy of different class files If our Android application does not perform dex processing, the apk of the last application will only have one dex file.

The ClassLoader type in Android can also be divided into system ClassLoader and custom ClassLoader. The system ClassLoader includes 3 types which are:

  • BootClassLoader, the Android system will use BootClassLoader to preload commonly used classes. Unlike Bootstrap ClassLoader in Java, it is not implemented by C / C ++ code, but by Java. BootClassLoader is an internal class of ClassLoader.
  • PathClassLoader, the full name is dalvik / system.PathClassLoader, can load the installed Apk, that is, the apk file under / data / app / package, and can also load the nativeLibrary under / vendor / lib, / system / lib.
  • DexClassLoader, full name is dalvik / system.DexClassLoader, can load an uninstalled apk file.

Print the current ClassLoader in 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();
        }
    }
}

The results are as follows:

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

It can also be confirmed from the printed results: App system class loader is PathClassLoader, and BootClassLoader is its parent class loader.

Second, ClassLoader analysis
In Android, we are mainly concerned about PathClassLoader and DexClassLoader.
PathClassLoader and DexClasLoader both inherit from dalviksystem.BaseDexClassLoader, and their class loading logic is all written in BaseDexClassLoader. PathClassLoader is used to manipulate the collection of files and directories in the local file system. DexClassLoader can load an uninstalled APK or other JAR / ZIP files containing dex files. DexClassLoader needs a private and readable and writable folder for the application to cache the optimized class files. And be careful not to store the optimized files on external storage to avoid subjecting your application to code injection attacks.
In Android, it is not the ClassLoader that is specifically responsible for class loading, but the loading through the DexFile's defineClassNative () method.
Insert picture description hereThree, dynamic loading (hot repair) example
1. Create a new module called dextest, add this is the module we want to dynamically load in.
Create a new class called ShowToast:

public class ShowToast {

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

There is only one showToast method in the ShowToast class, which requires a context parameter to display a toast.
2. After the java class is packaged in dex and
compiled, the corresponding class file will be generated in the directory as shown in the figure (the path may be different in different versions of android studio). Since the class file is used by the java virtual machine, However, the Dalvik virtual machine in Android uses dex files, so we need to convert it again.
Insert picture description hereStill add the last build.gradle file in this module:

//删除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)

This will package the files we need into the jar package, but the jar package is not what we need, continue.
A dx command is provided for us in the Android SDK (found under \ android-sdk \ build-tools \ version [23.0.1] or \ android-sdk \ platform-tools); the command usage is: dx- -dex --output = out.jar in.jar, this command converts the in.jar containing class into the out.jar file containing dex.
3. App side call
Copy the generated out.jar package to the assets directory in the app project:
Insert picture description herewe are here for convenience, but the scenario may be that the remote server downloads to the specified location.

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();
        }
    }
}

The FileUtils file here is to copy the jar package from the assets directory to the app's data directory:

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();
            }
        }
    }
}

At the beginning, I encountered a ClassNotFoundException exception:

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)

Because at the beginning, I directly used the jar package in the assets directory, as if it could not be loaded, then I used FileUtils to copy the jar package to another directory.
Click the button on the test machine to see the effect:
Insert picture description here
To sum up, use DexClassLoader to get the class in dex, and then run the method in the class through reflection.

There are still a few questions:
a. If the original class has bugs, how to do it now to replace the original class dynamically?
Refer to the Android hot fix and use Javassist.
b. If you want to dynamically load new activities, how to manage the life cycle?
c. How to load resource files?

Reference: class loading mechanism series 2-- Android in-depth understanding of the class loader
Android dynamically loaded dex Getting
Android Dex dynamic loading process

Published 230 original articles · Like 94 · Visit 270,000+

Guess you like

Origin blog.csdn.net/yu75567218/article/details/105070236