预置带so的apk,很容易出现一个问题:使用apk手动安装,可以正常执行so相关程序,但是打包到系统内,作为内置应用的话,so文件加载的时候就会报错。
首先来看apk的安装。
apk的安装过程中存 解压这一步。
系统应用集成过程中的一些坑
拷贝结束后,就是对这个 apk 文件进行解压操作,获取里面的文件,将相关文件解压到指定目录,如:
创建 data/data/{包名} 目录,存放应用运行期间所需的数据
扫描 apk 包中 lib 目录的 so 文件结构,解压到应用自身存放 so 库的目录,不同版本系统路径有些不同,我设备的版本是 android 5.1.1,api 22,三方应用的 so 文件存放目录就在 data/app/{包名}-1/lib 下
但是以上只是普通apk的安装过程,系统应用,不会提取so,而是一般从system/lib中读取so。
PackageManagerService启动详解(七)之扫描系统应用安装目录阶段流程分析
// 如果是系统APP,系统APP的native库统一放到/system/lib下
// 所以系统不会提取系统APP目录apk包中native库
if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
pkg.applicationInfo.primaryCpuAbi == null) {
setBundledAppAbisAndRoots(pkg, pkgSetting);
setNativeLibraryPaths(pkg);
}
apk安装路径一般在/system/app,/system/priv-app(系统预置),/vendor/app/(vendor目录一般是厂商定制,例如mtk,qualcomm,rk等,也算系统预置)和/data/app/(普通安装)下。
data/system/packages.xml
中可以app 的相关配置信息,这里有明确指出该去哪里加载 so 文件,以及 app 所运行的 CPU 架构。
root模式下的设备,可以通过cat,查看apk的信息。
cat /data/system/packages.xml | grep {你自己app的包名}
类似这种
系统应用的话,路径是这种:
<package name="com.softard.test" codePath="/system/app/Test" nativeLibraryPath="/system/app/Test/lib" primaryCpuAbi="armeabi-v7a"
虽然系统应用nativeLibraryPath下没有有so存在。
android studio中 Device File Explorer
,直接打开对应的文件去查看packages.xml。
由于系统apk安装的时候,不解压提取so,因此 nativeLibraryPath
这个路径下,没有so,需使用mk文件,copy到指定目录。
System.loadLibrary ()
的执行过程可以参考这篇:
Java 中 System.loadLibrary () 的执行过程
ClassLoader
的那个 findLibrary ()
实际上会在两个部分的 folder
中去寻找 System.loadLibrary ()
要 load
的那个 library
,一个部分是,构造 ClassLoader
时,传进来的那个 library path
,即是 app folder
,另外一个部分是 system property
。在 android
系统中,查找要 load 的 library
,实际上会在如下 3 个 folder
中进行:
1./vendor/lib
2./system/lib
3./data/app/[packagename]/lib或/system/app/[packagename]/lib
System.loadLibrary会优先查找apk中的so目录(system/app/{包名},data/app/{包名}),再查找系统目录,系统目录包括:/vendor/lib(64),/system/lib(64)
所以,如果是缺少so的 文件的话,先确保nativeLibraryPath
下的那个路径或system/lib
(可先手动push后到nativeLibraryPath或system/lib下验证,之后再通过mk文件)下存在正确的so(设备硬件对应架构类型要对的上,例如86和64位别弄混,可通过adb shell getprop | grep cpu
获取信息)。
具体mk写法可参考这篇:
[Android][Framework]带有so的三方应用集成
关于cpu架构适配的问题,可看这篇:
为何大厂APP如微信、支付宝、淘宝、手Q等只适配了armeabi-v7a/armeabi?
适配armeabi基本上适配了全部CPU架构。
然后,在确保了那些目录下存在so的话,加载仍然有问题,一般就是仍存在权限问题,导致无法访问。
要确保3点:
so 存在,正确 且 可访问。
system目录下的权限一直在收紧,随着版本升级一直在变动。
system/core/libcutils/fs_config.c
static const struct fs_path_config android_dirs[] = {
{ 00777, AID_SYSTEM, AID_SYSTEM, 0, "system/app/RemoteAssistance/lib/arm/lib_RongRTC_so.so" }
...................
static const struct fs_path_config android_files[] = {
{ 00777, AID_SYSTEM, AID_SYSTEM, 0, "system/app/RemoteAssistance/lib/arm/lib_RongRTC_so.so" }
这里设置放入的so文件的权限组为AID_SYSTEM,即uid为1000。
这里或许等于init.rc中调用chown+ chmod,修改用户ID(UID)和组ID(GID),以及文件权限,由于设置了system的gid和uid,有了权限。
android的权限,有时候就算在init.rc中加chmod 777 路径 ,修改文件权限也不行,要修改组(GID),甚至可能有的时候需要改Selinux。
android预装应用找不到so库问题
以上是预装不可卸载apk的情况。
如果是要预装可卸载的apk,即预装APP到data/app目录,修改PackageManagerService,以及mk里添加
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
,还有跳过 data 分区下 app 目录加密策略读取和设置。
但是这种卸载后,恢复出厂设置不会还原。
具体请参考如下链接:
Android 源码编译如何确定模块安装的位置
Android O/P/Q 版本如何预装 APK
androidQ(10.0) 预装集成apk到data分区
还有一种是 卸载后,恢复出厂设置会还原。
添加一个system service,并且修改 当系统启动后,准备一个脚本 ,在第一次开机的时候执行脚本将apk拷贝到data目录。(系统启动后,每次开机时,PMS都会在构造函数中对指定目录下的apk进行扫描,没有安装的apk就会触发安装)。
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
......
/**
* 系统每次启动时,都会重新安装一遍系统中的应用程序,但是有些应用程序信息每次安装都需要保持一致,
* 如UID
* 因此需要Setting来保存
*/
mSettings = new Settings(mPackages);
......
synchronized (mPackages) {
......
// /data目录
// 获取数据目录 /data
File dataDir = Environment.getDataDirectory();
// 获取用户自己安装的应用程序目录 /data/app
mAppInstallDir = new File(dataDir, "app");
// 获取受DRM保护的私有应用程序目录 /data/app-private
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
......
// 读取恢复上一次安装应用程序信息
mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));
// 加载FrameWork资源,即资源文件,不包含执行代码
// 先获取系统目录/system , 拿到路径/system/framework
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
......
// 资源文件
scanDirTracedLI(frameworkDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED,
scanFlags | SCAN_NO_DEX, 0);
// 安装受DRM保护的私有程序
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
scanDirTracedLI(privilegedAppDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
// 安装系统自带程序
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirTracedLI(systemAppDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// 保存的是设备厂商提供的应用程序
File vendorAppDir = new File("/vendor/app");
......
// 安装厂商自带程序
scanDirTracedLI(vendorAppDir, mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
......
if (!mOnlyCore) {
......
// 安装用户程序
scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
......
}
......
// 更新所有应用权限,根据 updateFlags = UPDATE_PERMISSIONS_ALL 来标识更新所有
updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
......
// 保存应用程序的安装信息
mSettings.writeLPr();
......
}
还有一种:services/core/java/com/android/server/pm/PackageManagerShellCommand.java下增加一个pm preinstall 命令。
参考如下:
android 预装第三方apk的方法
实战-Android 预置第三方应用
Android 系统添加第三方apk到data/app
Android6.0关于预置三方app卸载(一) copy到data/app下
Android 8.0 平台预置第三方apk到Data目录,使其可以卸载并且恢复出厂设置可以还原
参考链接:
init.rc文件格式及使用注意事项
全方位理解Android权限之底层实现概览
APK安装流程详解4——安装中关于so库的那些事
浅谈Android系统编译apk后so文件在dlopen时出现linker权限问题
浅谈Android系统编译apk后so文件在dlopen时出现linker权限问题
PackageManagerService启动详解(七)之扫描系统应用安装目录阶段流程分析
系统应用集成过程中的一些坑
【Android】动态链接库so的加载原理
Java 中 System.loadLibrary () 的执行过程
问题一:预制的库文件在系统中的权限问题
android 预装第三方apk的方法