[Android Pro] https://blog.csdn.net/gaugamela/article/details/79143309
原文地址:https://blog.csdn.net/gaugamela/article/details/79143309
最近遇到这样一个问题:
第三方的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<File> 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<String> addedPaths) {
..........
//这两个对象保存最终的结果,
//其中libPaths保存的是so相关的路径
final List<String> zipPaths = new ArrayList<>(10);
final List<String> 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<String> outZipPaths,
List<String> 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<String> 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);
}
经过测试,此种方法可以满足我们当前的各种集成场景的需求。
[Android Pro] https://blog.csdn.net/gaugamela/article/details/79143309的更多相关文章
- Mui本地打包笔记(一)使用AndroidStudio运行项目 转载 https://blog.csdn.net/baidu_32377671/article/details/79632411
转载 https://blog.csdn.net/baidu_32377671/article/details/79632411 使用AndroidStudio运行HBuilder本地打包的Mui项目 ...
- https://blog.csdn.net/u011489043/article/details/68488459
转自https://blog.csdn.net/u011489043/article/details/68488459 String 字符串常量 StringBuffer 字符串变量(线程安全) ...
- 程序员的沟通之痛https://blog.csdn.net/qq_35230695/article/details/80283720
个人理解: 一般刚工作的程序员总觉得技术最重要.但是当工作年限超过3年.或者岗位需要涉及汇报.需求对接等就会发现沟通非常重要.也许在大公司还不那么明显,但是在小公司.小团队或者创业,沟通甚至可以说是第 ...
- https://blog.csdn.net/uftjtt/article/details/79044186
https://blog.csdn.net/uftjtt/article/details/79044186
- 自动车牌识别(ALPR)---https://blog.csdn.net/ELEVEN_ZOU/article/details/80893579
1.基本功能:从一张或者一系列的图片中提取车牌信息,比如车牌号码.车牌颜色等信息. 2.功能扩展:车型.车品牌.车牌类型等. 3.应用方向:电子交易系统(停车自动收费.收费站自动支付等).交通执法.交 ...
- Nginx 配置location root 转自https://blog.csdn.net/rofth/article/details/78581617
nginx指定文件路径有两种方式root和alias,root与alias主要区别在于nginx如何解释location后面的uri,这会使两者分别以不同的方式将请求映射到服务器文件上. 最基本的区别 ...
- golang操作memcached 转自https://blog.csdn.net/weixin_37696997/article/details/78760397
go使用memcached需要第三方的驱动库,这里有一个库是memcached作者亲自实现的,代码质量效率肯定会有保障 1:安装 go get github.com/bradfitz/gomemcac ...
- 爬虫出现Forbidden by robots.txt(转载 https://blog.csdn.net/zzk1995/article/details/51628205)
先说结论,关闭scrapy自带的ROBOTSTXT_OBEY功能,在setting找到这个变量,设置为False即可解决. 使用scrapy爬取淘宝页面的时候,在提交http请求时出现debug信息F ...
- https://blog.csdn.net/doegoo/article/details/50749817
因为使用DiscuzX3.2进行系统的整合后,因为只是想在原J2EE的系统上增加论坛功能,而且J2EE中已经有一套用户的注册认证的体系,所以不需要在Discuz的系统中去注册以及登录功能,而是通过在J ...
随机推荐
- 两道SQL题目
1.查询省内所有城市气温都大于35度的省份(表名:Temp) SELECT province FROM Temp WHERE province NOT IN ( SELECT province FRO ...
- Sudo的用法和Visudo设置
身为程序员,你可以活在一个没有Windows的世界,当你离不开Unix(Linux,Mac...).而在Unix下面,你离不开terminal,离不开sudo. 你知道sudo command,然后输 ...
- CCF2016093炉石传说(C语言版)
问题描述 <炉石传说:魔兽英雄传>(Hearthstone: Heroes of Warcraft,简称炉石传说)是暴雪娱乐开发的一款集换式卡牌游戏(如下图所示).游戏在一个战斗棋盘上进行 ...
- 向集合中添加自定义类型--建议在自定义类型的时候要重写equals方法
package com.bjpowernode.t01list; import java.util.ArrayList; /* * 向集合中添加自定义类型 */public class TestLis ...
- Mac 下 Redis 5.0 的卸载与安装
卸载 停止 redis 服务器 redis-cli shutdown 检测 #检测后台进程是否存在 ps -ef |grep redis #检测6379端口是否在监听 netstat -lntp | ...
- 通过生成支付二维码来实现微信支付的解决方案 - EasyWechat版(转)
上一篇我们讲了在微信浏览器内实现微信支付的功能,它特别适合于一些基于微信公众号的h5站点等,支付流程也相当流畅,但是... 还有一种情况,比如现在北哥兄弟连PC版,是生成了一个二维码,这个二维码是专属 ...
- Pycharm 有些库(函数)没有代码提示
问题描述 如图,输入变量im. 后没有关于第三方库相应的函数或其他提示,当然,此文档的前提是有相关的函数说明以及已有相关设置等 解决方案 python是动态强类型语言,IDE无法判断Image.op ...
- django的FormView中,自定义初始化表单数据的曲折方法
这个技巧,主要是用于表单初始化及回显. 也就是说,如果用户的数据库里有数据,则要将相应的数据显示在表单里, 如果用户的数据库里没有数据,才会生成一个空白的表单给用户, 这样才显得专业塞! 而我面对的尴 ...
- 【LOJ】#150. 挑战多项式
原题链接 多项式全家桶!快乐!(好像少个除法,不过有除法好像不太快乐) (说真的这是我第一次写exp和开根...水平不行.. 从最基础要实现的操作开始吧.. 多项式取模\(x^n\) 这个..很简单了 ...
- <构建之法>阅读笔记6
第九章:项目经理 是讲项目经理的作用功能和重要性,书里面主要讲的是微软的PM(Programe Manager)和其他团队PM(Project Manager)的区别,还介绍了PM的能力要求以及人物, ...