SO库动态加载

45 篇文章 0 订阅
4 篇文章 0 订阅

一、SO加载源码分析

Java层加载

我们在加载so文件时会调System.loadLibrary(so)用以下代码:

 System.loadLibrary("main");

我们继续看System.loadLibrary方法:

public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
    }

我们看Runtime的loadLibrary0方法里面怎么处理的:

 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;
         if (loader != null && !(loader instanceof BootClassLoader)) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
               
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                              System.mapLibraryName(libraryName) + "\"");
            }
            String error = nativeLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }
        getLibPaths();
        String filename = System.mapLibraryName(libraryName);
        String error = nativeLoad(filename, loader, callerClass);
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
    }

第一步会通过ClassLoader通过我们之前传入的so名称找到文件名,当fileNeme为null时,就会报错找不到so文件,
ClassLoader最终会进去BaseDexClassLoader的findLibrary方法里面:

@Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }

继续查看pathList(DexPathList )的findLibrary方法:

public String findLibrary(String libraryName) {
        // 通过 so 的名称拼接成文件路径
        String fileName = System.mapLibraryName(libraryName);
        for (NativeLibraryElement element : nativeLibraryPathElements) {
            String path = element.findNativeLibrary(fileName);

            if (path != null) {
                return path;
            }
        }

        return null;
    }

System.mapLibraryName("main); 通过so的名称拼接成文件路径返回的是libmain.so,nativeLibraryPathElements 是 native library 路径的集合, 它的是 DexPathList 初始化的时候赋值

 public String findNativeLibrary(String name) {
            maybeInit();

            if (zipDir == null) {
                String entryPath = new File(path, name).getPath();
                if (IoUtils.canOpenReadOnly(entryPath)) {
                    return entryPath;
                }
            } else if (urlHandler != null) {
                // Having a urlHandler means the element has a zip file.
                // In this case Android supports loading the library iff
                // it is stored in the zip uncompressed.
                String entryName = zipDir + '/' + name;
                if (urlHandler.isEntryStored(entryName)) {
                  return path.getPath() + zipSeparator + entryName;
                }
            }

            return null;
        }

我们看一下DexPathList的构造函数:

DexPathList(ClassLoader definingContext, String dexPath,
    String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
  
    ...

    this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);

       
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    ...
}

在 DexPathList 的构造函数中,我们可以知道 nativeLibraryPathElements 是所有 Native Library 的集合。
DexPathList 是在 ActivityThread 中创建,ActivityThread 是在 App 启动时候创建的。关于 App 启动的启动流程,可以去找这方面的资料,自行查看。
总结一些 Native Library 的路径来源:
一个是 Native 库的原始路径 System.getProperty(“java.library.path****”), /system/lib/; /vendor/lib/; /product/lib/
另外一个是App启动时的 Lib 库路径

二、动态加载so库

根据上面Java层so加载流程我们可以知道,在System.loadLibrary(so名称)后把我们的so路径存储到了ClassLoader的pathList的nativeLibraryPathElements里面,我们知道咋热修复的知识里面是把解决bug的dex文件插入到PathList的Elements的前面,这样就可以保证我们ClassLoader findClass时会加载正确的class,那么我们得so是不是也可以这样做呢,把我们动态下载的so文件插入到nativeLibraryPathElements数组的前面。

1.将so文件复制到data目录下

 Log.d("Leon", getExternalCacheDir().getAbsolutePath());
        // 先调用 sdk 方法动态加载或者修复
        File mainSoPath = new File(Environment.getExternalStorageDirectory(),"so/libmain.so");

        File libSoPath = new File(getDir("lib",Context.MODE_PRIVATE),"so");
        if(!libSoPath.exists()){
            libSoPath.mkdirs();
        }
        File dst = new File(libSoPath,"libmain.so");
        try {
            FileUtil.copyFile(mainSoPath,dst);
        } catch (IOException e) {
            e.printStackTrace();
        }
 /**
     *
     * copy file
     *
     * @param src
     *            source file
     * @param dest
     *            target file
     * @throws IOException
     */
    public static void copyFile(File src, File dest) throws IOException {
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            if (!dest.exists()) {
                dest.createNewFile();
            }
            inChannel = new FileInputStream(src).getChannel();
            outChannel = new FileOutputStream(dest).getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } finally {
            if (inChannel != null) {
                inChannel.close();
            }
            if (outChannel != null) {
                outChannel.close();
            }
        }
    }

2.通过反射将我们的so文件插入到classLoader pathList的nativeLibraryPathElements前面

  ClassLoader classLoader = mContext.getClassLoader();

//        Field pathListField = ReflectUtil.getField(classLoader,"pathList");
//        pathListField.setAccessible(true);
        Object pathListObject = ReflectUtil.getFieldValue(classLoader, "pathList");

        Field nativeElementField = ReflectUtil.getField(pathListObject, "nativeLibraryPathElements");

        Object nativeLibraryElenentObject = nativeElementField.get(pathListObject);

        Class<?> elementClass = nativeLibraryElenentObject.getClass().getComponentType();
        Constructor<?> elementConstructor = elementClass.getConstructor(File.class);
        elementConstructor.setAccessible(true);
        Object firstElement = elementConstructor.newInstance(new File(absolutePath));


        Object newNativeElemenet = insertElementAtFirst(firstElement, nativeLibraryElenentObject);

        nativeElementField.set(pathListObject,newNativeElemenet);
 public static Object insertElementAtFirst(Object firstObj, Object array) {
        Class<?> localClass = array.getClass().getComponentType();
        int len = Array.getLength(array) + 1;
        Object result = Array.newInstance(localClass, len);
        Array.set(result, 0, firstObj);
        for (int k = 1; k < len; ++k) {
            Array.set(result, k, Array.get(array, k - 1));
        }
        return result;
    }

来自:https://www.yuque.com/u435816/omigec/ucyor0

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值