Android foundation - dynamic loading so library

Dynamically load so in Android

Reason: If you put the so file directly in the libs directory, all the so libraries in the libs directory will be loaded by default when the android program starts, but these so libraries may conflict in some places. If you use dynamic loading of the so library, it will Some conditions can be used to determine whether to load this so library.
Introduction: The dynamic loading of so is to remove the so library when it is packaged into apk, and download it through the network package at an appropriate time, and perform separate loading at runtime.
Advantages: so files are dynamically loaded, not bound to death, more convenient to modify, and can be dynamically updated when there is a problem with the so library;
dynamic loading of so library files can greatly reduce the size of the apk package;
solve multiple third-party There may be conflicts when library files are loaded at the same time.

1. Loading of Android's so library file

  1. Loading so in Android:
    (1) Call the load() method, passing the absolute path of the so file;
    (2) Call the loadLibrary() method, passing the name of the so file, and the so file must be placed in the lib directory of the apk, and the so The name of the name must remove the lib in front and the ".so" in the back.
    Only so files in two directories can be loaded:
    (1) /system/lib
    (2) The path of the application installation package: /data/data/packgename/...
    For the two methods of loading so files, in the Android source code System. You can see the source code link in java
    public static void load(String filename) {
    
    
        Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
    }
        public static void loadLibrary(String libname) {
    
    
        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
    }
  1. The above two methods both call the getRuntime function in Runtime to obtain the instance
    source code link of Runtime
    public static Runtime getRuntime() {
    
    
        return currentRuntime;
    }

  1. The loadLibrary0 method source code link is also called when loading so
    In loadLibrary0, you can see that there are two different processing forms depending on whether the ClassLoader is empty or not.
    private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
    
    
        if (libname.indexOf((int)File.separatorChar) != -1) {
    
    
            throw new UnsatisfiedLinkError(
    "Directory separator should not appear in library name: " + libname);
        }
        String libraryName = libname;
        //ClassLoader不为空时(程序中通过System.loadlibrary()方式,这个loader就不会为空)
        if (loader != null && !(loader instanceof BootClassLoader)) {
    
    
        //通过ClassLoader的findLibrary方法查找so的文件名称,寻找so文件是否存在
            String filename = loader.findLibrary(libraryName);
            //判断,如果未找到so文件,并且类加载器存在
            if (filename == null &&
                    (loader.getClass() == PathClassLoader.class ||
                     loader.getClass() == DelegateLastClassLoader.class)) {
    
    
                filename = System.mapLibraryName(libraryName);//拼接so文件名
            }
            if (filename == null) {
    
    
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            //so文件存在,加载它
            String error = nativeLoad(filename, loader);
            if (error != null) {
    
    
            //加载异常,加载失败
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }
        //ClassLoader为空时,传入library name和System.mapLibraryName获得真正的library name。
        //具体深入的实现过程,可以到Android源码继续查看getLibPaths()和mapLibraryName的实现
        getLibPaths();
        String filename = System.mapLibraryName(libraryName);//mapLibraryName用于拼接so文件名的前缀:lib和后缀.so
        //例如传入的是wudong,得到的就会是libwudong.so,然后在mLibPaths查找'libwudong.so',最终确定library的path。
        String error = nativeLoad(filename, loader, callerClass);
        if (error != null) {
    
    //加载异常
            throw new UnsatisfiedLinkError(error);
        }
    }
  1. In the process of finding the so file, the loader.findLibrary method is called to find the so file. If it can be found, the absolute path of the so file will be returned, and then it will be loaded by nativeLoad().

2. Realize the dynamic loading of so

1. First put the so library in the assets resource directory, usually two so libraries, one 32-bit and one 64-bit; 2.
Dynamic loading, that is, when it needs to be used, copy it from the assets resource directory to app/ libs directory;
3. Use the System.load(String filename) or System.loadLibrary(String libname) method to load so.
·In the original loading process of so, the system uses ClassLoader to search whether there is a so library in the native directory for loading, then it is necessary to add the path of downloading and storing so to the libs path of ClassLoader, and these libs paths are started when the app starts It has already been generated, then you need to use reflection to add the path at runtime.

Put the path where so is stored in ClassLoader

Use reflection to put the storage path of so into ClassLoader. The TinkerLoadLibrary of the open source project tinker also has an implementation method, so we don't need to implement it ourselves, we can take it and use it directly.

private static final class V25 {
    
    
        private static void install(ClassLoader classLoader, File folder)  throws Throwable {
    
    
            final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
            final Object dexPathList = pathListField.get(classLoader);

            final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");

            List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
            if (origLibDirs == null) {
    
    
                origLibDirs = new ArrayList<>(2);
            }
            final Iterator<File> libDirIt = origLibDirs.iterator();
            while (libDirIt.hasNext()) {
    
    
                final File libDir = libDirIt.next();
                if (folder.equals(libDir)) {
    
    
                    libDirIt.remove();
                    break;
                }
            }
            origLibDirs.add(0, folder);

            final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");
            List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
            if (origSystemLibDirs == null) {
    
    
                origSystemLibDirs = new ArrayList<>(2);
            }

            final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
            newLibDirs.addAll(origLibDirs);
            newLibDirs.addAll(origSystemLibDirs);

            final Method makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class);

            final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);

            final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");
            nativeLibraryPathElements.set(dexPathList, elements);
        }
    }
}

This path can be added to the nativeLibraryDirectories of ClassLoader.

Guess you like

Origin blog.csdn.net/weixin_44901971/article/details/127557644