博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[Android Pro] https://blog.csdn.net/gaugamela/article/details/79143309
阅读量:5810 次
发布时间:2019-06-18

本文共 9888 字,大约阅读时间需要 32 分钟。

原文地址:

最近遇到这样一个问题:

第三方的SDK除了Jar包外,还提供了对应的so文件。
APK集成SDK后进行测试,发现一切正常。
但将APK作为系统应用集成到ROM时,发现so获取失败。

看了一下SDK的代码,发现由于底层库的需求,SDK没有直接利用System.loadLibrary来加载so,

而是主动获取so的路径,对应的代码如下:

protected static String findNativeLibraryPath(Context context, String libraryName) {    if (context == null) {        return null;    }    if (TextUtils.isEmpty(libraryName)) {        return null;    }    String dexPath = context.getPackageCodePath();    //注意这个地方    //取的是ApplicationInfo中的nativeLibraryDir    String nativeLibraryDir = context.getApplicationInfo().nativeLibraryDir;    //创建PathClassLoader    PathClassLoader pathClassLoader = new PathClassLoader(dexPath, nativeLibraryDir,            ClassLoader.getSystemClassLoader());    //利用PathClassLoader来获取so的路径    return pathClassLoader.findLibrary(libraryName);}

为了解释这个问题及寻找解决方案,我们就必须查找对应的源码了。 

接下来,我们就以8.0的代码为例,看看相应的流程。

 一、PathClassLoader相关内容 

我们先来看看PathClassLoader相关的源码:

public class PathClassLoader extends BaseDexClassLoader {    ...........    //前文传入ApplicationInfo的nativeLibraryDir    //对应此处的librarySearchPath    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {        super(dexPath, null, librarySearchPath, parent);    }}

PathClassLoader继承BaseDexClassLoader,仅实现了两个构造函数, 

主要的逻辑还是实现于BaseDexClassLoader中。

1.1 BaseDexClassLoader相关内容 

我们来看看BaseDexClassLoader中的代码:

public class BaseDexClassLoader extends ClassLoader {    .............    public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String librarySearchPath, ClassLoader parent) {        super(parent);        //注意这里构造了DexPathList, 同样传入了librarySearchPath        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);        .................    }    ...........    @Override    public String findLibrary(String name) {        return pathList.findLibrary(name);    }    .......}

从代码不难发现,findLibrary查找的实际上是DexPathList, 

后者于BaseDexClassLoader的构造函数中创建。

1.2 DexPathList相关内容 

继续跟进DexPathList对应的代码:

final class DexPathList {    .......    public DexPathList(ClassLoader definingContext, String dexPath,            String librarySearchPath, File optimizedDirectory) {        ...........        //从下面的代码,容易看出native path包括传入的librarySearchPath        //及"java.library.path"对应的路径        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);        this.systemNativeLibraryDirectories =            splitPaths(System.getProperty("java.library.path"), true);        List
allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); //最后将信息保存到nativeLibraryPathElements this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories); ..................... } ........... //容易看出DexPathList就是从nativeLibraryPathElements中获取结果 public String findLibrary(String libraryName) { String fileName = System.mapLibraryName(libraryName); for (NativeLibraryElement element : nativeLibraryPathElements) { String path = element.findNativeLibrary(fileName); if (path != null) { return path; } } return null; } ...............}

至此,我们应该可以得出结论,so路径无法找到的原因就是:

ApplicationInfo.nativeLibraryDir及”java.library.path”对应的路径中没有so。
那么APK被集成到系统时,so的路径到底在哪里?

为了解决这个问题,我们就要看看APK创建过程相关的代码了。

二、APK创建过程相关内容 

APK被系统创建时,会调用handleBindApplication函数,我们截取其中一部分:

private void handleBindApplication(AppBindData data) {    ...........    //判断APK是否支持了测试框架    if (ii != null) {        .........        //创建LoadedApk        final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,                //初次调用ContextImpl的classLoader                appContext.getClassLoader(), false, true, false);        //以LoadedApk重新创建Context        final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);        try {            //再次调用ContextImpl的classLoader            final ClassLoader cl = instrContext.getClassLoader();        ........    }    ...........}

当APK支持测试框架时才会进入上述逻辑。

不过在Apk加载service、activity等组件时,
最终都会用到ContextImpl的getClassLoader函数。
因此,可以以这段代码为例进行分析。

我们来看看ContextImpl的getClassLoader函数:

public ClassLoader getClassLoader() {    //初次调用时,返回的是ClassLoader.getSystemClassLoader()    //再次调用时,调用的是LoadedApk.getClassLoader()    return mClassLoader != null ? mClassLoader            : (mPackageInfo != null ? mPackageInfo.getClassLoader()                    : ClassLoader.getSystemClassLoader());}

当Apk信息解析完毕后,会生成LoadedApk信息。

一旦有了LoadedApk信息后,ContextImpl返回的就是LoadedApk.getClassLoader的结果。

我们仅关注APK解析完毕,有了LoadedApk信息后的代码流程。

因此,直接跟进LoadedApk相关的代码:

public ClassLoader getClassLoader() {    synchronized (this) {        if (mClassLoader == null) {            //初次调用时,会进入此分支            createOrUpdateClassLoaderLocked(null /*addedPaths*/);        }        return mClassLoader;    }}

我们来看看createOrUpdateClassLoaderLocked函数,重点关注路径相关的信息:

private void createOrUpdateClassLoaderLocked(List
addedPaths) { .......... //这两个对象保存最终的结果, //其中libPaths保存的是so相关的路径 final List
zipPaths = new ArrayList<>(10); final List
libPaths = new ArrayList<>(10); //判断是否为BundledApp(主要为系统应用) final boolean isBundledApp = mApplicationInfo.isSystemApp() && !mApplicationInfo.isUpdatedSystemApp(); //构建libPaths等 makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths); //之后利用libPaths等信息构造ClassLoader ...........}

按照代码流程,接下来我们分析下makePaths函数, 

主要关注libPaths相关的内容:

public static void makePaths(ActivityThread activityThread,                         boolean isBundledApp,                         ApplicationInfo aInfo,                         List
outZipPaths, List
outLibPaths) { //取出app对应的sourceDir final String appDir = aInfo.sourceDir; //这里取出了ApplicationInfo中的nativeLibraryDir final String libDir = aInfo.nativeLibraryDir; final String[] sharedLibraries = aInfo.sharedLibraryFiles; //清空结果并增加sourceDir outZipPaths.clear(); outZipPaths.add(appDir); ........... if (outLibPaths != null) { outLibPaths.clear(); } ............ if (outLibPaths != null) { if (outLibPaths.isEmpty()) { //输出结果中增加nativeLibraryDir outLibPaths.add(libDir); } //当ApplicationInfo指定了Abi时 if (aInfo.primaryCpuAbi != null) { if (aInfo.targetSdkVersion < Build.VERSION_CODES.N) { outLibPaths.add("/system/fake-libs" + (VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : "")); } //前文中outZipPaths已经增加了ApplicationInfo.sourceDir //增加abi对应的路径 for (String apk : outZipPaths) { outLibPaths.add(apk + "!/lib/" + aInfo.primaryCpuAbi); } } //系统APK if (isBundledApp) { ........ outLibPaths.add(System.getProperty("java.library.path")); } //如果还有shared library, 还需要增加shared library对应的路径 if (sharedLibraries != null) { for (String lib : sharedLibraries) { if (!outZipPaths.contains(lib)) { outZipPaths.add(0, lib); appendApkLibPathIfNeeded(lib, aInfo, outLibPaths); } } } ........... }}

至此我们应该可以看出,对于一个APK而言: 

ApplicationInfo.nativeLibraryDir只是so存储路径的一部分。 
整体的so路径可能还包括!/lib/abi部分、system/lib部分及shared library部分,例如:

nativeLibraryDirectories=[/data/app/test-app/lib/arm, /data/app/test-app/base.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]]

关于so何时被拷贝到上述目录,就需要分析so拷贝的流程,本博客暂不对此进行分析。

 

三、解决方案

了解问题的原因后,解决方案其实就呼之欲出了。
从前文的代码,我们容易看出:
APK对应的so路径就保存Context对应的ClassLoader中。
该ClassLoader调用Context.getClassLoader就能获取。

在前文提及的LoadedApk的createOrUpdateClassLoaderLocked中,

创建ClassLoader的代码如下:

private void createOrUpdateClassLoaderLocked(List
addedPaths) { ............. if (mClassLoader == null) { ......... mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader); ....... }}

我们跟进一下ApplicationLoaders.getClassLoader函数:

private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,                               String librarySearchPath, String libraryPermittedPath,                               ClassLoader parent, String cacheKey) {    ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();    synchronized (mLoaders) {        if (parent == null) {            parent = baseParent;        }        if (parent == baseParent) {            .............            PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(                                                  zip,                                                  librarySearchPath,                                                  libraryPermittedPath,                                                  parent,                                                  targetSdkVersion,                                                  isBundled);            .........        }        ...........        PathClassLoader pathClassloader = new PathClassLoader(zip, parent);        ...........        return pathClassloader;    }}

容易看出,上述代码无论进入哪个分支, 

getClassLoader最终返回的是一个PathClassLoader函数。

于是,文中开头部分提及findNativeLibraryPath改为如下方式即可:

protected static String findNativeLibraryPath(AVLContext context, String libraryName) {    if (context == null) {        return null;    }    if (TextUtils.isEmpty(libraryName)) {        return null;    }    PathClassLoader classLoader = (PathClassLoader) context.getClassLoader();    return classLoader.findLibrary(libraryName);}

经过测试,此种方法可以满足我们当前的各种集成场景的需求。

 

 
 

 

转载于:https://www.cnblogs.com/0616--ataozhijia/p/10007246.html

你可能感兴趣的文章
如何用纯 CSS 为母亲节创作一颗像素画风格的爱心
查看>>
Linux基础命令---rmdir
查看>>
优秀程序员共有的7种优秀编程习惯
查看>>
iOS sqlite3(数据库)
查看>>
粤出"飞龙",打造新制造广东样本
查看>>
编玩边学获数千万元A轮融资,投资方为君联资本
查看>>
蓝图(Blueprint)详解
查看>>
Spark之SQL解析(源码阅读十)
查看>>
Android图片添加水印图片并把图片保存到文件存储
查看>>
C#字符串的不变性
查看>>
前端路由简介以及vue-router实现原理
查看>>
比特币系统采用的公钥密码学方案和ECDSA签名算法介绍——第二部分:代码实现(C语言)...
查看>>
分享15款很实用的 Sass 和 Compass 工具
查看>>
AMD优势: 与众不同 选择丰富
查看>>
玩转高性能超猛防火墙nf-HiPAC
查看>>
简单按日期查询mysql某张表中的记录数
查看>>
自动化部署之jenkins发布PHP项目
查看>>
C/C++编程可用的Linux自带工具
查看>>
如何判断webview是不是滑到底部
查看>>
海贼王十大悲催人物
查看>>