目录
记录一下自己的学习过程
什么是交叉编译
交叉编译是在一个平台上生成另一个平台上的可执行代码。同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。
举例来说,我们常说的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是Intel x86体系结构和Windows NT for x86操作系统的简称
为什么需要交叉编译
在android中如果想要使用类似于FFmpeg、opencv、openGL等开源的库,就需要用到交叉编译。同时一些比较大的公司,也会将一些核心模块打成动态库,给其多个应用去使用,也需要交叉编译成对应平台的可执行文件。
动态库与静态库的区别
动态库
通常以lib开头,以.so作为后缀名,在程序运行的时候,才会去加载动态库。
静态库
静态库命名方式区别于动态库,通常在编译的时候,会把静态库打到主包里面,所以解压安装包查看的时候,看不到静态库。
编译动态库
环境准备
linux环境
编译动态库需要一个linux环境,我是用VM ware安装了一个ubuntu16的虚拟机。
下载ndk
下载linux使用的ndk: NDK 下载 | Android NDK | Android Developers
我下载的是最新的 android-ndk-r25c
编译代码
我这边写了一个简单的例子,两数相加。
add.h
#include<stdio.h>
int add(int a,int b);
add.c
#include "add.h"
int add(int a,int b){
return a+b;
}
现在ndk的交叉编译是使用的clang,而不是gcc,需要先配置一下编译器的路径
添加到 /etc/profile里面
export NDK="/home/user/ndk/android-ndk-r25c"
export NDK_CLANG="$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi28-clang"
使用命令 $NDK_CLANG -fPIC -shared add.c -o libaddndk.so 编译生成动态库
编写android代码
添加动态库
在android代码中的main目录下面,新建jniLibs文件夹,在jniLibs下面需要根据手机的平台,添加对应的文件夹
对应平台的获取,可以通过下面命令获取
adb shell getprop ro.product.cpu.abi
build.gradle修改
需要在build.gradle里面添加对应的cmake和ndk的平台配置
在android的defaultCongfig中添加
externalNativeBuild {
cmake {
// cppFlags "" 默认包含四大平台
abiFilters "armeabi-v7a"
}
}
ndk {
abiFilters "armeabi-v7a"
}
java代码
添加一个native方法
public class MainActivity extends AppCompatActivity {
// Used to load the 'ndktest' library on application startup.
static {
System.loadLibrary("native-lib");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(" 3 + 5 = " + add(3,5));
}
/**
* A native method that is implemented by the 'ndktest' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int add(int a,int b);
}
jni代码
#include <jni.h>
#include <string>
extern "C" { // 由于库,是c编写的,所有采用c的编译方式
extern int add(int ,int); //
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndktest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_ndktest_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b) {
return add(a,b);
}
cmake代码
我是用的是set方式设置动态库的目录,再通过target_link_libraries连接到对应的动态库。
cmake_minimum_required(VERSION 3.22.1)
# Declares and names the project.
project("ndktest")
# 批量导入
file(GLOB appCPP *.cpp)
add_library(
native-lib
SHARED # 动态库
${appCPP})
#include_directories("${CMAKE_SOURCE_DIR}/cpp/")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/armeabi-v7a")
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib}
addndk
)
运行结果
遇到的问题
cmake还有一种添加动态库的方式,通过add_library指定添加方式,再通过set设置动态库的路径,但是我是用这种方式,在android 12的小米手机上面就会直接crash,提示找不到动态库,我在低版本的android 9 模拟器上面就是可以运行的。
家人们谁懂啊,搞了一天也没走通。最后是用上面setflag的方式设置才能在小米手机上运行。有懂的家人们可以帮忙指导一下。
add_library(addndk SHARED IMPORTED)
set_target_properties(addndk PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/armeabi-v7a/libaddndk.so)
Process: com.example.ndktest, PID: 28839 java.lang.UnsatisfiedLinkError: dlopen failed: library "D:/Code/NdkTest/app/src/main/cpp/../jniLibs/armeabi-v7a/libaddndk.so" not found: needed by /data/app/~~prrC2yw6f71milydnH_J3Q==/com.example.ndktest-6CZEdK9q_P6Zrwfh-gzZNQ==/base.apk!/lib/armeabi-v7a/libnative-lib.so in namespace classloader-namespace at java.lang.Runtime.loadLibrary0(Runtime.java:1077) at java.lang.Runtime.loadLibrary0(Runtime.java:998) at java.lang.System.loadLibrary(System.java:1661) at com.example.ndktest.MainActivity.<clinit>(MainActivity.java:14) at java.lang.Class.newInstance(Native Method) at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95) at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:45) at android.app.Instrumentation.newActivity(Instrumentation.java:1340) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3707) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3963) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2381) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:210) at android.os.Looper.loop(Looper.java:299) at android.app.ActivityThread.main(ActivityThread.java:8237) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)