Android基础——动态加载so库
Android中动态加载so
原因:如果把so文件直接放在libs目录下,在android程序启动的时候会默认加载libs目录下的所有so库,但这些so库可能会在某些地方存在冲突,使用动态加载so库,就可以通过一些条件判断是否要加载这个so库。
介绍:so的动态加载是把so库打包成apk的时候剔除,在合适的时候通过网络包下载的方式,在运行的时候进行分离加载。
优点:so文件是动态加载的,不是绑定死的,更便于修改,在so库有问题的时候可以动态更新;
so库文件动态加载可以极大地减小apk包的体积;
解决多个第三方库文件同时加载可能出现冲突的问题。
一、Android的so库文件的加载
- Android中加载so:
(1)调用load()方法,传递so文件的绝对路径;
(2)调用loadLibrary()方法,传递so文件的名称,而且so文件必须放在apk的lib目录下,而且so的名称必须去掉前面的lib和后边的“.so”。
只能加载两个目录下的so文件:
(1)/system/lib
(2)应用程序安装包的路径:/data/data/packgename/…
对于两种加载so文件的方法,在Android源码System.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); }
- 上边两种方法都调用了Runtime中的getRuntime函数,用来获取Runtime的实例
源码链接
public static Runtime getRuntime() { return currentRuntime; }
- 在加载so时也调用了loadLibrary0方法
源码链接
在loadLibrary0中可以看到根据ClassLoader是否为空,有两种不同的处理形式。
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); } }
- 在查找so文件的过程中,调用了loader.findLibrary的方法去寻找so文件,如果可以找到,会返回so文件的绝对路径,然后交由nativeLoad()去加载。
二、实现so的动态加载
首先把so库放在assets资源目录下,一般会放两个so库,一个32位一个64位的;
2.动态加载,就是在需要使用的时候,从assets资源目录下复制到app/libs目录下;
3.通过 System.load(String filename) 或者 System.loadLibrary(String libname) 方法去加载 so。
·在so原本的加载过程中,是系统通过ClassLoader检索native目录里是否存在so库进行加载的,那就需要把下载存放so的路径添加到ClassLoader的libs路径里,这些libs路径在app启动的时候就已经生成了,那就需要利用反射,在运行时把路径添加进去。
将存放so的路径放到ClassLoader中
利用反射将存放so的路径放到ClassLoader中,开源项目tinker的TinkerLoadLibrary也有实现发方法,我们就不用自己实现了,可以拿过来直接使用。
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 origLibDirs = (List) nativeLibraryDirectories.get(dexPathList); if (origLibDirs == null) { origLibDirs = new ArrayList<>(2); } final Iterator 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 origSystemLibDirs = (List) systemNativeLibraryDirectories.get(dexPathList); if (origSystemLibDirs == null) { origSystemLibDirs = new ArrayList<>(2); } final List 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); } }}
这样路径就可以添加进ClassLoader的nativeLibraryDirectories中了。
来源地址:https://blog.csdn.net/weixin_44901971/article/details/127557644
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341