第五章 内置jar和内置so

This commit is contained in:
dqzg12300 2023-03-08 23:54:15 +08:00
parent a41fb3a5d8
commit 2845bd6f70
3 changed files with 249 additions and 3 deletions

View File

@ -387,8 +387,254 @@ android_app_import {
通过修改Android源码的方式同样可以让开发人员将自己经常使用的jar包也内置到系统中又或者将定制的业务功能包装在jar包中在调整时就仅需要修改jar包的代码最后更新到系统中即可。如此可以节省臃肿的编译时间还能更加便捷的管理业务代码。
内置jar包的方式是有多种的下面将使用两种方式将一个自己编写的jar包集成到系统中。
内置jar包的方式是有多种的下面将使用两种方式将一个自己编写的jar包集成到系统中。首先创建一个no Activity的Android项目项目命名为MyJar如下图所示。
![image-20230308211039320](.\images\create_no_activity.png)
接着简单的写两个测试函数。在最后内置成功后,将对这个函数进行调用测试是否内置成功。
```java
public class MyCommon {
public String getMyJarVer(){
return "v1.0";
}
public int add(int a,int b){
return a+b;
}
}
```
然后就可以编译这个项目。编译结束后得到`./build/output/debug/app-debug.apk`文件需要内置的是一个JAR文件所以接下来解压apk文件java代码在解压结果的classes.dex文件中应用程序编译过程中如果生成的DEX 文件大小超过 65536 字节,则编译工具链将尝试在同一 APK 包中生成多个 classes.dex 文件以存储所有的字节码。为了方便内置可以编译前在build.gradle中添加配置声明不要生成多个DEX文件相关配置如下。
```
plugins {
id 'com.android.application'
}
android {
compileSdk 33
defaultConfig {
applicationId "cn.mik.kframework"
minSdk 29
targetSdk 32
versionCode 1
versionName "1.0"
multiDexEnabled false // 禁止生成多个dex文件
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
...
}
```
经过前面的流程拿到的DEX 文件虽然都是存储着java指令但是和JAR 文件是有一定区别的。他们的区别如下所示。
1. 目标平台不同JAR 文件是为 Java 虚拟机JVM设计的而 DEX 文件是为 Dalvik 虚拟机DVM和 ARTAndroid Run Time设计的。
2. 字节码格式不同JAR 文件包含 Java 编译器生成的字节码,而 DEX 文件包含经过转换和优化的字节码,以适应 Android 平台的内存限制和设备特性。
3. 加载速度不同:由于 Dalvik 虚拟机使用预先处理的 DEX 文件,因此加载速度更快,而 JVM 在运行 JAR 文件时需要实时编译字节码,因此加载速度较慢。
4. 版本兼容性不同JAR 文件可以在不同版本的 JVM 上运行,但 DEX 文件只能在支持 DVM 或 ART 的 Android 设备上运行。
综上所述所以DEX文件需要先转换为JAR然后再将这个JAR文件拷贝到AOSP源码中进行内置。以下是具体的实现步骤。
```
// 进入编译输出结果目录
cd ./app/build/outputs/apk/debug/
// APK的本质就是一个压缩文件直接解压apk文件即可
unzip app-debug.apk -d ./app-debug
// 将解压目录的DEX文件拷贝到当前目录
cp ./app-debug/classes.dex ./
// 通过dx工具将DEX转换为JAR
dx --dex --min-sdk-version=26 --output=./kjar.jar ./classes.dex
// 创建一个目录来存放需要内置的JAR文件
mkdir /root/android_src/aosp12/frameworks/native/myjar
// 将转换后的JAR文件放入AOSP源码目录中
cp ./myjar.jar /root/android_src/aosp12/frameworks/native/myjar/kjar.jar
```
最后就可以修改编译时的规则将这个JAR文件拷贝到指定分区中。找到文件`build/target/product/base_system.mk`,在构建规则中添加如下配置,表示将源码路径下的文件拷贝到目标目录。
```
PRODUCT_COPY_FILES += \
frameworks/native/myjar/kjar.jar:system/framework/kjar.jar \
```
`base_system.mk `定义了构建 Android 系统镜像时需要包含哪些模块和库并指定了这些模块和库在系统镜像中的位置和顺序以及它们之间的依赖关系。在build目录下的多个mk文件都能添加这个配置进行拷贝文件并不是只能加在这个`base_system.mk`文件中,在不同的 mk 文件中定义的 PRODUCT_COPY_FILES 规则可能会相互覆盖,因此需要确保它们之间没有冲突,并且按照预期的顺序执行。通常情况下,建议将自己添加的所有的 PRODUCT_COPY_FILES 规则放在同一个文件中,以避免混乱和错误。
重新编译系统并刷入手机中先来到刚刚指定的目录中查看kjar文件是否存在。
```
adb shell
cd /system/framework
ls -all |grep kjar
-rw-r--r-- 1 root root 3705442 2023-03-08 21:55:46.000000000 +0800 kjar.jar
```
最后写一个普通的App来对kjar中的函数进行调用有两种方式加载这个jar文件。DexClassLoader 和 PathClassLoader 是 Android 应用程序中常用的两种类加载器,它们之间的主要区别如下。
1. 加载路径不同DexClassLoader 可以从任意路径中加载 .dex 文件,包括应用程序中的私有目录和外部存储器等位置;而 PathClassLoader 只能从预定义的系统路径中加载 .dex 文件,如 /system/framework、/system/app 等。
2. 加载方式不同DexClassLoader 是通过指定 .dex 文件的路径和输出目录,将该文件加载到内存中的;而 PathClassLoader 则是通过指定 Classpath 路径来加载 .dex 文件,包括系统类库和应用程序类库等。
3. 安全性和隐私性不同:由于 DexClassLoader 可以加载任意路径中的 .dex 文件,因此可能存在潜在的安全风险和隐私问题,特别是对于多个应用程序之间共享代码的场景;而 PathClassLoader 则更加安全可靠,因为只能加载预定义的路径中的文件,并且具有较高的权限限制。
下面是两种加载方式对函数进行调用的实现例子。
```java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 使用PathClassLoader加载jar文件
String jarPath = "/system/framework/kjar.jar";
ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
String javaPath= System.getProperty("java.library.path");
PathClassLoader pathClassLoader=new PathClassLoader(jarPath,javaPath,systemClassLoader);
Class<?> clazz1 = null;
try {
// 通过反射调用函数
clazz1 = pathClassLoader.loadClass("cn.mik.myjar.MyCommon");
Method method = clazz1.getDeclaredMethod("getMyJarVer");
Object result = method.invoke(null);
Log.i("MainActivity","getMyJarVer:"+result);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// 使用DexClassLoader加载jar文件
String dexPath = "/system/framework/kjar.jar";
String dexOutputDir = getApplicationInfo().dataDir;
ClassLoader classLoader = new DexClassLoader(dexPath, dexOutputDir, null,
getClass().getClassLoader());
Class<?> clazz2 = null;
try {
// 通过反射调用函数
clazz2 = classLoader.loadClass("cn.mik.myjar.MyCommon");
Method addMethod = clazz2.getDeclaredMethod("add", int.class,int.class);
Object result = addMethod.invoke(null, 12,25);
Log.i("MainActivity","getMyJarVer:"+result);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
```
## 5.5 系统内置so动态库
前文介绍的内置方式非常简单只需通过配置PRODUCT_COPY_FILES即可将指定文件从源码中复制到目标目录中。除了JAR文件外其他文件也可以使用这种方式进行内置。为了内置so文件将采用另一种方式。并且第二种内置方式同样适用于JAR文件。
第二种方式是前文采用内置apk的方式对构建规则进行细节的描述在内置apk的同时将指定的so动态库内置到`/system/lib``/system/lib64`目录中。并且同时将调用so动态库的JAR文件也内置在`/system/framework`目录中在内置完成后将调用JAR文件来访问so动态库以及直接调用动态库进行测试。
首先准备一个测试项目创建Native C++的项目。见下图。
![image-20230308232615347](.\images\create_so_project.png)
这个项目并不需要启动所以直接删除MainActivity文件重新创建一个类来加载动态库。并且修改cpp中对应的函数名称相关修改如下。
```java
// 在这个类中进行加载动态库
public class NativeCommon {
static {
System.loadLibrary("mysodemo");
}
public native String stringFromJNI();
}
// native-lib.cpp文件中调整名称来对应新的类
extern "C" JNIEXPORT jstring JNICALL
Java_cn_mik_mysodemo_NativeCommon_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
```
成功编译测试项目后的步骤和前文基本一致唯一的区别就是在这里多拷贝了apk文件和so动态库文件下面是具体流程。
```
// 进入编译输出结果目录
cd ./app/build/outputs/apk/debug/
// 解压apk文件
unzip app-debug.apk -d ./app-debug
// dex转换为jar
dx --dex --min-sdk-version=26 --output=./mysodemo.jar ./app-debug/classes.dex
// 创建目录存放要内置的文件
mkdir /root/android_src/aosp12_mikrom/frameworks/base/packages/apps/mysodemo
// 拷贝apk到需要内置的目录
cp ./app-debug.apk /root/android_src/aosp12/frameworks/base/packages/apps/mysodemo/mysodemo.apk
// 拷贝jar到需要内置的目录
cp ./mysodemo.jar /root/android_src/aosp12/frameworks/base/packages/apps/mysodemo/mysodemo.jar
// 拷贝64位动态库到需要内置的目录
cp ./app-debug/lib/arm64-v8a/libmysodemo.so /root/android_src/aosp12/frameworks/base/packages/apps/mysodemo/libmysodemo_arm64.so
// 拷贝32位动态库到需要内置的目录
cp ./app-debug/lib/armeabi-v7a/libmysodemo.so /root/android_src/aosp12/frameworks/base/packages/apps/mysodemo/libmysodemo_arm.so
```
需要内置的文件准备就绪后创建一个构建规则Android.mk文件将相关依赖文件都内置进去。
```
cd ./frameworks/base/packages/apps/mysodemo
touch Android.mk
gedit Android.mk
// 添加下面的内容到文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := mysodemo.apk
LOCAL_MODULE := kmodule
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_TAGS := optional
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_MODULE_PATH := $(TARGET_OUT)/framework
LOCAL_INSTALLED_MODULE_STEM := mysodemo.jar
LOCAL_DEX_PREOPT := false
LOCAL_SHARED_LIBRARIES := liblog
include $(BUILD_PREBUILT)
#--------------------------------
include $(CLEAR_VARS)
LOCAL_MODULE := libmysodemo
LOCAL_SRC_FILES_arm := libmysodemo_arm.so
LOCAL_SRC_FILES_arm64 := libmysodemo_arm64.so
LOCAL_MODULE_TARGET_ARCHS:= arm arm64
LOCAL_MULTILIB := both
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE_TAGS := optional
LOCAL_SHARED_LIBRARIES := liblog
include $(BUILD_PREBUILT)
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB