NDK即Native Development Kit,是Android上用来开发c/c++的开发工具包。
安装步骤:https://developer.android.com/studio/projects/install-ndk
ndk.dir=/Users/bc/android-ndk-r17c
sdk.dir=/Users/bc/Library/Android/sdk
android {
defaultConfig {
ndk {
// 指定编译的abi架构
abiFilters "armeabi-v7a", "arm64-v8a"
}
externalNativeBuild {
// cmake配置
cmake {
//Sets optional flags for the C++ compiler.
cppFlags "-std=c++11 -frtti -fexceptions"
// Passes optional arguments to CMake.
arguments "-DANDROID_STL=c++_shared"
arguments "-DANDROID_TOOLCHAIN=gcc"
}
}
}
// Use this block to link Gradle to your CMake or ndk-build script
// 将gralde关联到cmake构建脚本
externalNativeBuild {
cmake {
// 构建脚本路径
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
public class NativeDemo {
private String libPath = "/data/data/com.bc.sample/files/download/nativeLib2.so";
public void initNativeLib() {
// 1.对于APK内的so文件,使用loadLibrary来加载
System.loadLibrary("nativeLib");
// 2.对于文件系统中的so文件,使用load()来加载
System.load(libPath);
}
}
NDK支持的编译方式有两种:
(1)CMake:NDK的默认构建工具,可在CMakeLists.txt 构建脚本中配置编译选项,CMake的C++运行时默认值为c++_static,CMake和独立工具链默认启用C++异常,默认启用 RTTI。
(2)ndk-build:可在Android.mk 和 Application.mk文件中配置编译选项,ndk-build的C++运行时默认值为none,ndk-build中默认停用C++异常,默认停用RTTI。
CMake官网介绍如下,翻译过来就是,CMake是开源的、跨平台的编译工具,可以根据所指定的编译环境生成对应的makefile文件。
CMake is an open-source, cross-platform family of tools
designed to build, test and package software.
CMake is used to control the software compilation process
using simple platform and compiler independent configuration
files, and generate native makefiles and workspaces that can
be used in the compiler environment of your choice.
CMake使用工具链来执行编译、链接等任务,对不同语言需要使用不同的工具链;
NDK的工具链文件位于 NDK目录中的 {NDK_root}/build/cmake/android.toolchain.cmake 内,并可以在build.gradle中的android.defaultConfig.externalNativeBuild.cmake.arguments闭包下给CMake工具链传递参数。
CMakeLists.txt是CMake的构建脚本,在CMakeLists.txt中可以使用cmake的一些命令来自定义构建过程,以下列举了一些常用的cmake命令:
# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.
# cmake最低版本号
cmake_minimum_required(VERSION 3.4.1)
# set:给变量赋值
set(libs "${CMAKE_SOURCE_DIR}/../jniLibs")
set(SOURCES src/main/cpp/native-lib.cpp src/main/cpp/native-lib2.cpp)
# message:打印信息
message("jniLibs folder: " ${libs})
# option:定义宏
option(IS_ANDROID "编译ANDROID则定义为'ON',其他定义为'OFF'" ON)
# if-endif:cmake的逻辑控制
if(IS_ANDROID)
message("Building for Android")
add_definitions(-DIS_ANDROID)
else()
message("Building for others")
endif(IS_ANDROID)
# include_directories:Add the given directories to those the compiler uses to search for include files.把指定目录添加到编译器查找include头文件的列表中
include_directories(Thirdparty/opencv)
# find_library:This command is used to find a library.查找NDK库并将其路径存储为一个变量
find_library( # Sets the name of the path variable.
# 也可以直接用log,像下面的jnigraphics一样
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add_library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.
# add_library:把一个library添加到工程
add_library( # Specifies the name of the library.
native-lib
# Sets the library as a shared library.
# 动态库用SHARED 静态库用STATIC
SHARED
# Provides a relative path to your source file(s).
# 把所有需要用到的cpp都添加进来;或者用前面set的SOURCES变量
src/main/cpp/native-lib.cpp
src/main/cpp/native-lib2.cpp)
# 添加一个已构建的库,使用IMPORTED
add_library(opencv_java3 SHARED IMPORTED)
# 对应已构建的库,需要指定库的路径
set_target_properties(opencv_java3
# Specifies the target library.
PROPERTIES IMPORTED_LOCATION
# Provides the path to the library you want to import.
"${libs}/${ANDROID_ABI}/libopencv_java3.so")
# target_link_libraries( ... - ... ...) 关联target和item
# Links your native library against one or more other native libraries.
# 首个参数是target,后面的参数是item;target必须先用add_library()创建过;
target_link_libraries( # Specifies the target library.
native-lib
opencv_java3
jnigraphics
${log-lib} )
在build.gradle中配置cmake,即可将二者关联起来,关联后在gralde构建的过程中就会构建native代码:
android {
defaultConfig {
ndk {
// 指定编译的abi架构
abiFilters "armeabi-v7a", "arm64-v8a"
}
externalNativeBuild {
// cmake配置
cmake {
//Sets optional flags for the C++ compiler.
cppFlags "-std=c++11 -frtti -fexceptions"
// Passes optional arguments to CMake toolchain.
arguments "-DANDROID_STL=c++_shared"
arguments "-DANDROID_TOOLCHAIN=gcc"
}
}
}
// Use this block to link Gradle to your CMake or ndk-build script
// 将gralde关联到cmake构建脚本
externalNativeBuild {
cmake {
// 构建脚本路径
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
如果项目中包含多个CMake项目,可以使用一个 CMakeLists.txt 文件作为顶级 CMake 构建脚本,并添加其他 CMake 项目作为此构建脚本的依赖项。
例如,项目中需要构建native-lib1和native-lib2两个so,则可以新建一个顶层 CMakeLists.txt,并将顶层 CMakeLists.txt配置到build.gralde中,然后在顶层 CMakeLists.txt中添加native-lib1和native-lib2:
cmake_minimum_required(VERSION 3.4.1)
# add_subdirectory:将另一个 CMakeLists.txt 文件指定为构建依赖项,然后关联其输出;
add_subdirectory(
# Specifies the directory of the CMakeLists.txt file.
./src/main/cpp/native-lib1
# Specifies the directory for the build outputs.
./src/main/cpp/native-lib1/outputs
)
add_subdirectory(
# Specifies the directory of the CMakeLists.txt file.
./src/main/cpp/native-lib2
# Specifies the directory for the build outputs.
./src/main/cpp/native-lib2/outputs
)
详细介绍见https://developer.android.com/ndk/guides/ndk-build (本文不重点介绍),在build.gradle中配置如下:
externalNativeBuild {
ndkBuild {
path file('src/main/jni/Android.mk')
}
}
在常用的cmake命令中介绍过,cmake可以使用find_library命令找到 NDK native api库并将其路径存储为一个变量;或者也可以在target_link_libraries直接使用NDK native api。
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
下面主要列举一些常用的NDK native api:
native代码在构建后,有两种产物,生成哪一种产物是由编译配置决定的:
(1)native shared library动态库,即.so文件,CMakeList.txt中配置如下:
add_library(native-lib
SHARED
src/main/cpp/native-lib.cpp)
(2)native static library静态库,即.a文件,CMakeList.txt中配置如下:
add_library(native-lib
STATIC
src/main/cpp/native-lib.cpp)
二者的区别:.so文件可以在运行过程中由java代码调用加载,.a文件不能在运行过程中直接由java代码加载;so文件在运行中可以去加载其他的so文件或者a文件;
JNI即java native interface,是java和native代码进行交互的接口;
在java中使用native关键字声明jni方法:
public class NativeDemo {
public native String stringFromJNI();
public static native void invole();
}
在kotlin中使用externl关键字声明jni方法:
class NativeDemo {
external fun stringFromJNI(): String;
}
有两种方式可以实现对应的native代码:
(1)静态注册
在cpp目录下,新建native_lib.cpp,添加对应的native实现:
#include
#include
// JNIEXPORT JNICALL、参数里的前两个参数JNIEnv* env,jobject obj等是固定格式;固定参数中的jobject obj表示this
extern "C"
JNIEXPORT jstring JNICALL
Java_com_bc_sample_NativeDemo_stringFromJNI(
JNIEnv* env,
jobject obj) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
// static的native方法,参数默认是jclass
extern "C"
JNIEXPORT void JNICALL
Java_com_bc_sample_NativeDemo_stringFromJNI(
JNIEnv* env,
jclass clazz) {
}
(2)动态注册
在cpp目录下,新建native_lib.cpp,在JNI_OnLoad时调用env->RegisterNatives进行注册(JNI_OnLoad是在动态库被加载时由系统进行调用):
// 需要注册jni方法所在的类
static const char *jniClassName = "com/bc/sample/NativeDemo";
// 需要注册的jni方法,分别表示java方法名、方法签名、native函数指针
// 方法签名可以用javap命令查看:
// javap -s /Users/bc/Demo/app/build/intermediates/javac/debug/classes/com/bc/sample/NativeDemo.class
static JNINativeMethod methods[] = {{"stringFromJNI", "()Ljava/lang/String", (jstring *) native_stringFromJNI}};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
if (!registerNatives(env))
return JNI_ERR;
result = JNI_VERSION_1_6;
return result;
}
static int registerNatives(JNIEnv *env) {
jclass clazz = env->FindClass(jniClassName);
if (clazz == NULL)
return JNI_FALSE;
jint methodSize = sizeof(methods) / sizeof(methods[0]);
if (env->RegisterNatives(clazz, methods, methodSize) < 0)
return JNI_FALSE;
return JNI_TRUE;
}
// native实现
jstring native_stringFromJNI(JNIEnv *env, jobject obj) {
return env->NewStringUTF("hello");
}
JNI 定义了两个关键数据结构“JavaVM”和“JNIEnv”,两者本质上都是指向函数表的二级指针。
Android中每个进程只允许有一个JavaVM。JNIEnv作用域为单个线程,可通过JavaVM的getEnv来获得当前线程的JNIEnv,JNIEnv可通过GetJavaVM来获得JavaVM。
// 保存JavaVM,方便在子线程中获取
static JavaVM *_my_jvm;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
env->GetJavaVM(&_my_jvm)
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
return result;
}
在java代码中,可以通过Thread.start()启动一个线程;
对于在native代码中通过pthread_create() 或 std::thread 启动的线程,是没有JNIEnv的,也就无法调用JNI,可以使用 AttachCurrentThread() 或 AttachCurrentThreadAsDaemon() 函数将JavaVM附加到线程,附加后的线程可以调用JNI代码:
// 保存JavaVM,方便在子线程中获取
static JavaVM *_my_jvm;
// 保存java层obj,方便在子线程获取后回调结果
static java_obj;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
env->GetJavaVM(&_my_jvm)
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
return result;
}
jstring native_stringFromJNI(JNIEnv *env, jobject obj) {
java_obj = env->NewGlobalRef(obj);
}
void native_callback() {
JNIEnv *env = NULL;
_my_jvm->AttachCurrentThread(&env, nullptr);
jmethodId method_call_back = env->GetMethodId(user_class, "callback", "()V");
env->CallVoidMethod(java_obj, method_call_back);
// 使用完后需要detach
_my_jvm->DetachCurrentThread();
}
/* jni.h源码 */
/* Primitive types that match up with Java equivalents. */
/* jni基础数据类型与java一一对应,二者可直接转换 */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
/* "cardinal indices and sizes" */
typedef jint jsize;
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
#else /* not __cplusplus */
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
#endif /* not __cplusplus */
extern "C"
JNIEXPORT jstring JNICALL
Java_com_bc_sample_NativeDemo_stringFromJNI(
JNIEnv* env,
jobject obj, jstring str) {
jstring loacl_str = env->NewStringUTF("local_str");
const char * c = env->GetStringUTFChars(str, false);
// 删除避免内存泄漏
env->ReleaseStringUTFChars(str, c);
return env->NewStringUTF(hello.c_str());
}
例如:java中定义了一个User类如下:
package com.bc.sample;
class User {
public String userName;
public static String TAG;
public void setName() {
}
public static void show() {
}
}
在jni中调用User相关方法的方式如下:
extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_NativeDemo_objectFromJNI(
JNIEnv* env,
jobject obj, jobject user) {
jclass user_class = env->GetObjectClass(user);
// 1.1 变量
jfieldID user_name_id = env->GetFiledId(user_class, "userName", "Ljava/lang/String");
jstring name = env->NewStringUTF("jack");
env->SetObjectFiled(user, user_name_id, name);
// 1.2 静态变量
env->GetStaticFieldID(user_class, "TAG", "Ljava/lang/String");
// 2.1 方法
jmethodId method_set_name = env->GetMethodId(user_class, "setName", "()V");
env->CallVoidMethod(user, method_set_name);
// 2.2 静态方法
jmethodId method_show = env->GetStaticMethodId(user_class, "show", "()V");
env->CallStaticObjectMethod(user_class, method_show);
// 3 构造函数,用表示
jclass usr_class = env->FindClass("com/bc/sample/User");
jmethodId method_init = env->GetMethodId(user_class, "", "()V");
jobject usr = env->NewObject(usr_class, method_init);
jobjectArray usr_array = env->NewObjectArray(5, usr_class, nullptr);
// 局部引用超过512会报错local reference table overflow (max=512);所以用完后要释放
env->DeleteLocalRef(user_class);
}
不管是基础类型的数组jintArray、jbooleanArray还是引用类型的数组jobjectArray,都是继承了jarray,是一个引用类型。
extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_NativeDemo_objectFromJNI(
JNIEnv* env,
jobject obj, jintArray int_array,
jstringArray str_array,
jobjectArray user_array) {
int len = env->GetArrayLength(user_array);
// 1 基本数据类型数组
jint n = env->GetIntArrayElement(int_array, 0);
// 2 string数据类型数组
jstring str = static_cast(env->GetObjectArrayElement(str_array, 0));
const char * c = env->GetStringUTFChars(str, false);
env->ReleaseStringUTFChars(str, c);
// 3 引用类型数组;获得jobject后可按4.3.3中获得user数据
jobject user = env->GetObjectArrayElement(user_array, 0);
env->DeleteLocalRef(user);
}
可以使用javap命令生成类的方法及参数签名:
>javap -s java.lang.String
java类型及签名对应关系如下:
# 基本类型
V void 一般用于表示方法的返回值
Z boolean
B byte
C char
S short
I int
J long
F float
D double
# 引用类型前要加L
String Ljava/lang/String
Class Ljava/lang/Class
# 数组前要加[
int[] [I
Object[] [Ljava/lang/Object
# 方法签名:括号内表示参数,括号后表示返回类型(引用类型后要用;分隔)
String fun() () Ljava/lang/String;
int fun( int i, String str) (ILjava/lang/String;)I
static java_obj;
jstring native_stringFromJNI(JNIEnv *env, jobject obj) {
// 局部引用
jclass str_clazz = env->FindClass("Ljava/lang/String");
env->DeleteLocalRef(str_clazz);
// 全局引用,手动调用DeleteGlobalRef后才释放
java_obj = env->NewGlobalRef(obj);
env->DeleteGlobalRef(obj);
// 弱引用,使用前需要判断是否为null
java_obj = env->NewWeakGlobalRef(obj);
jboolean is_null = env->IsSameObject(java_obj, nullptr);
}
假设java中的user类中定义如下
class User {
public void setName() throws RuntimeException {
}
public native void stringFromJNI();
}
在native层调用java代码发生异常时,可按如下方式捕获:
jstring native_stringFromJNI(JNIEnv *env, jobject obj) {
jclass user_class = env->GetObjectClass(user);
jmethodId method_set_name = env->GetMethodId(user_class, "setName", "()V");
env->CallVoidMethod(user, method_set_name);
// 1.如果调用java方法setName时发生异常;可使用如下方法判断是否发生crash并捕获;否则会直接crash
jthrowable throwable_occurred = env->ExceptionOccurred();
if (throwable_occurred) {
env->ExceptionDescribe();
env->ExceptionClear();
}
// 2.或者当异常发生时,native层可以向java层抛出一个异常,在java层完成try-catch
jclass throwable_clazz = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(throwable_clazz, "exception occurred");
}
native开发常用的线程库:
#include
// 线程互斥锁
pthread_mutex_t mutex;
// 线程条件变量
pthread_cond_t cont;
// 需要在子线程中执行的方法
void *sayHello(void * arg) {
pthread_mutex_lock(&mutex);
jstring local_arg = static_cast (arg);
LOG(local_arg);
pthread_mutex_unlock(&mutex);
return nullptr;
}
jstring native_stringFromJNI(JNIEnv *env, jobject obj) {
// 1 创建线程
pthread_t handle;
jstring loacl_str = env->NewStringUTF("local_str");
int result = pthread_create(&handle, nullptr, sayHello, loacl_str);
// 2 线程同步常用方法
// pthread_mutex_lock() pthread_mutex_unlock()
pthread_mutex_init(&mutex, nullptr);
pthread_mutex_destroy(&mutex);
// pthread_cond_wait() pthread_cond_signal()
pthread_cond_init(&cond, nullptr);
pthread_cond_destroy(&cond);
}
不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口 (ABI)。CPU 与指令集的每种组合都有专属的应用二进制接口 (ABI)。
默认情况下,Gradle会针对所有非弃用ABI进行构建。要限制应用支持的ABI集,可以在build.gradle中设置以下配置:
android {
defaultConfig {
ndk {
// 限定编译的abi
abiFilters "armeabi", "arm64-v8a"
}
}
}
cpu架构及支持的abi如下:
cpu \ abi | armeabi | armeabi-v7a | arm64-v8a | x86 | x86_64 | mips | mips64 |
---|---|---|---|---|---|---|---|
ARMv5 | 支持 | ||||||
ARMv7 | 支持 | 支持 | |||||
ARMv8 | 支持 | 支持 | 支持 | ||||
x86 | 支持 | 支持 | 支持 | ||||
x86_64 | 支持 | 支持 | 支持 | ||||
MIPS | 支持 | ||||||
MIPS64 | 支持 | 支持 |
目前市场上主流CPU架构是ARM v7;mips/mips64、x86 / x86_64市场占有率很少,可以忽略;
常见native crash:https://source.android.com/devices/tech/debug/native-crash
当native层发生crash时,crash堆栈如下所示:
A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 8427 (com.bc.sample), pid 8427 (com.bc.sample)
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
A/DEBUG: Build fingerprint: 'Meizu/meizu_16th_CN/16th:8.1.0/OPM1.171019.026/1554749207:user/release-keys'
A/DEBUG: Revision: '0'
A/DEBUG: ABI: 'arm64'
A/DEBUG: pid: 8427, tid: 8427, name: com.bc.sample >>> com.bc.sample <<<
A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
A/DEBUG: Cause: null pointer dereference
A/DEBUG: x0 0000000000000000 x1 0000000000000000 x2 000000000000000e x3 0000007feb60313f
A/DEBUG: x4 0000000000000000 x5 0080000000000000 x6 7266206f6c6c6548 x7 2b2b43206d6f7266
A/DEBUG: x8 0101010101010101 x9 406a834e06fb5ba3 x10 0000000000000000 x11 000000000000000e
A/DEBUG: x12 0000000000000001 x13 0000000000000004 x14 00000074aa8bca40 x15 00000074a9b7a8a8
A/DEBUG: x16 000000740b3bae98 x17 00000074a9aa1890 x18 0000007425800080 x19 00000074256c2a00
A/DEBUG: x20 0000007425288220 x21 00000074256c2a00 x22 0000007feb60340c x23 000000740b68273a
A/DEBUG: x24 0000000000000004 x25 00000074aa8bca40 x26 00000074256c2aa0 x27 0000000000000001
A/DEBUG: x28 0000000000000002 x29 0000007feb603070 x30 000000740b3957f4
A/DEBUG: sp 0000007feb603060 pc 00000074a9aa18a0 pstate 0000000080000000
A/DEBUG: backtrace:
A/DEBUG: #00 pc 000000000001d8a0 /system/lib64/libc.so (strlen+16)
A/DEBUG: #01 pc 000000000000f7f0 /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/lib/arm64/libnative-lib.so (std::__ndk1::char_traits::length(char const*)+20)
A/DEBUG: #02 pc 000000000000fdf8 /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/lib/arm64/libnative-lib.so (std::__ndk1::basic_string, std::__ndk1::allocator>::assign(char const*)+44)
A/DEBUG: #03 pc 000000000000f2f4 /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/lib/arm64/libnative-lib.so
A/DEBUG: #04 pc 000000000000f1f8 /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/lib/arm64/libnative-lib.so (Java_com_bc_sample_MainActivity_stringFromJNI+76)
A/DEBUG: #05 pc 000000000000909c /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/oat/arm64/base.odex (offset 0x9000)
可以看到,crash信息描述了发生crash的abi架构、crash的原因以及堆栈backtrace;
一般有两种方式可用来分析native层crash的backtrace:
ndk-stack命令位于{NDK_root}/ndk-stack,使用时需要将crash的log复制到一个txt文件中,这个命令会从txt中的
A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
后面开始分析,使用方法如下:
# 用法:ndk-stack -sym ${SO_PARENT_DIR} -dump crash_log.txt
# -sys so所在目录,即${SO_PARENT_DIR},注意必须是目录名,不是so路径;
# -dump 发生crash的log信息,即crash_log.txt;
# 示例:
>ndk-stack -sym /Users/bc/demo/MyApplication2/app/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a -dump /Users/bc/crash.txt
# 结果如下:
********** Crash dump: **********
Build fingerprint: 'Meizu/meizu_16th_CN/16th:8.1.0/OPM1.171019.026/1554749207:user/release-keys'
pid: 8427, tid: 8427, name: com.bc.sample >>> com.bc.sample <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Stack frame #00 pc 000000000001d8a0 /system/lib64/libc.so (strlen+16)
Stack frame #01 pc 000000000000f7f0 /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/lib/arm64/libnative-lib.so (std::__ndk1::char_traits::length(char const*)+20): Routine std::__ndk1::char_traits::length(char const*) at /Users/bc/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include/c++/v1/__string:217
Stack frame #02 pc 000000000000fdf8 /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/lib/arm64/libnative-lib.so (std::__ndk1::basic_string, std::__ndk1::allocator>::assign(char const*)+44): Routine std::__ndk1::basic_string, std::__ndk1::allocator >::assign(char const*) at /Users/bc/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include/c++/v1/string:2385
Stack frame #03 pc 000000000000f2f4 /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/lib/arm64/libnative-lib.so: Routine std::__ndk1::basic_string, std::__ndk1::allocator >::operator=(char const*) at /Users/bc/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include/c++/v1/string:890
Stack frame #04 pc 000000000000f1f8 /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/lib/arm64/libnative-lib.so (Java_com_bc_sample_MainActivity_stringFromJNI+76): Routine Java_com_bc_sample_MainActivity_stringFromJNI at /Users/bc/Demo/app/src/main/cpp/native-lib.cpp:9
Stack frame #05 pc 000000000000909c /data/app/com.bc.sample-2gV8CATI0EfKhLhzjuP6sA==/oat/arm64/base.odex (offset 0x9000)
addr2line命令位于{NDK_root}/toolchains/${ABI}/prebuilt/darwin-x86_64/bin/x86_64-linux-android-addr2line,使用方法如下:
# 用法:arm-linux-androideabi-addr2line -C -f -e ${SOPATH} ${Address}
# -C -f 打印错误行数所在的函数名称
# -e 打印错误地址的对应路径及行数
# ${SOPATH} so路径
# ${Address} crash堆栈地址
# 示例如下:
>arm-linux-androideabi-addr2line -C -f -e libnative-lib.so 000000000000f1f8
# 结果如下,可以得到发生crash的行号:
>Java_com_bc_sample_MainActivity_stringFromJNI
/Users/bc/demo/MyApplication2/app/src/main/cpp/native-lib.cpp:9
欢迎关注我,一起解锁更多技能:BC的掘金主页~ BC的CSDN主页~
参考文档:
cmake指南:https://developer.android.com/ndk/guides/cmake
配置cmake:https://developer.android.com/studio/projects/configure-cmake
cmake命令文档:https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html
cmake-toolchain文档:https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html?highlight=ndk#cross-compiling-for-android-with-the-ndk
NDK native api简介:https://developer.android.com/ndk/guides/stable_apis
NDK native api:https://developer.android.com/ndk/reference
JNI 官方文档:https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html