最近需要模拟出一个native crash
,简单来说就是声明一个native
方法,然后在c/c++
层实现这个方法并触发一个异常即可。由于之前没有接触过这些,所以实现起来还是花费了挺多的时间的,这期间也涉及到了很多知识点或概念,如NDK、JNI,abi以及so等。
JNI的全称为Java Native Interface
,即Java
本地接口,类似于AIDL
,提供了若干的API
实现了Java
和其他语言的通信(主要是C
和C++
),目的是使Java
方法能够调用C
实现的一些函数。
NDK全称为Native Development Kit
,它是一个工具集,NDK允许用户使用类似C
/C++
之类的原生原生代码执行部分程序,其内部还是采用JNI机制实现的。
在对上述概念有了一个基础了解后,才好理解后面要做的事情都是在干什么,下面就以楼主现在需要实现的这个小功能为例阐述一个如何通过NDK实现一个native
方法:
一、配置安装NDK
楼主的运行环境是Ubuntu
,开发工具选用的Android Studio
。所以NDK的安装比较简单:
-
打开
Android Studio
,File
->Settings
->Appearance & Behavior
->System Settings
->Android SDK
,然后选中安装SDK Tools
中的NDK
即可。 -
安装好
NDK
后还需要给项目设置NDK
版本,点击File
->Project Structure
->SDK Location
,然后设置NDK
的目录,我这里下来了多个NDK
版本,所以我选择了其中一个版本。
-
安装好
NDK
后可以先尝试在Android Studio
的Terminal
终端中输入ndk-build
命令看是否有反应。如果报错未找到命令的话就还需要配置一下环境变量,命令如下(详细内容参考Android JNI和NDK学习(01)--搭建NDK开发环境):
NDK
路径就用上面自己配置的那个路径就可以了。# 替换成自己的ndk路径 export NDK_HOME=/home/hy/Android/Sdk/ndk/21.3.6528147 export PATH=$PATH:$NDK_HOME source ~/.bashrc
配置完成后在终端输入
ndk-build
命令应该就会有输出了,如果Studio中的Terminal还是未找到命令可以重启或者在系统终端中cd到项目目录再输入命令。
二、编写so库准备工作
NDK环境配置好了之后就可以开始编写代码了。
- 创建
TestJNI.Java
并声明一个native
方法:public class TestJNI { static { System.loadLibrary("TestJNI"); } public native void crashTest(); }
- 使用
javac
生成.class
文件:javac TestJNI.java
- 使用
javah
生成.h
文件,注意此时命令所处的位置应该是项目的java
目录下:
以我的项目为例此时命令应到是在~/AndroidStudioProjects/NativeCrashTest/app/src/main/java
目录下执行的,否则会报找不到类文件的错误。javah -jni com.example.nativecrashtest.jni.TestJNI
- 编写
C
/C++
实现native
方法:#include "jni.h" #include "com_example_nativecrashtest_jni_TestJNI.h" #include <cstdio> JNIEXPORT void JNICALL Java_com_example_nativecrashtest_jni_TestJNI_crashTest(JNIEnv *, jobject) { printf("make a crash"); //TODO 制造一个native carash int *p = 0; //空指针 *p = 1; //写空指针指向的内存,产生SIGSEGV信号,造成crash }
- 创建
Android.mk
文件,添加如下代码:
记得将LOCAL_MODULE
和LOCAL_SRC_FILES
替换成你自己的文件名。LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := TestJNI LOCAL_SRC_FILES := TestJni.cpp include $(BUILD_SHARED_LIBRARY)
- 配置app下的
build.gradle
文件android { ... defaultConfig { ... ndk { moduleName "TestJNI" abiFilters "armeabi-v7a" } } externalNativeBuild { ndkBuild { path "src/main/java/com/example/nativecrashtest/jni/Android.mk" } } sourceSets { main() { jniLibs.srcDirs = ['libs'] } } ... }
同样这里需要根据自己的情况修改相应的文件名或者路径,其中abiFilters
相关知识可以参考Android ABI,如果仅是测试的话可以通过adb shell cat /proc/cpuinfo
命令查看自己的测试机类型(参考如何查看Android设备的ABI)。
最后的项目目录结构图如下:
项目结构图
三、生成.so文件并调用实现方法
- 相关准备工作完成之后,进入到
Android.mk
所在目录执行ndk-build
命令即可:
hy@hy-OptiPlex-7070:~/AndroidStudioProjects/NativeCrashTest/app/src/main/java/com/example/nativecrashtest/jni$ ndk-build
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.
[arm64-v8a] Compile++ : TestJNI <= TestJni.cpp
[arm64-v8a] SharedLibrary : libTestJNI.so
[arm64-v8a] Install : libTestJNI.so => libs/arm64-v8a/libTestJNI.so
[armeabi-v7a] Compile++ thumb: TestJNI <= TestJni.cpp
[armeabi-v7a] SharedLibrary : libTestJNI.so
[armeabi-v7a] Install : libTestJNI.so => libs/armeabi-v7a/libTestJNI.so
[x86] Compile++ : TestJNI <= TestJni.cpp
[x86] SharedLibrary : libTestJNI.so
[x86] Install : libTestJNI.so => libs/x86/libTestJNI.so
[x86_64] Compile++ : TestJNI <= TestJni.cpp
[x86_64] SharedLibrary : libTestJNI.so
[x86_64] Install : libTestJNI.so => libs/x86_64/libTestJNI.so
命令执行完成后可以看到项目目录下多出来一个libs
文件夹,里面有生成的.so
文件。
ps:如果Studio的终端命令行还是提示未找到命令就通过系统终端跳转到.mk
文件所在目录然后执行命令即可。
- 添加一个
button
按钮点击事件,在点击按钮时触发crash
:
//MainActivity.java
public class MainActivity extends WearableActivity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
com.example.nativecrashtest.jni.TestJNI testJNI = new TestJNI();
testJNI.crashTest();
}
});
// Enables Always-on
setAmbientEnabled();
}
}
- 启动
demo
点击按钮触发崩溃,然后执行adb shell ls -l /data/system/dropbox
命令,可以看到多出来了一个data_app_native_crash@1595486083697.txt.gz
文件,取出解压就可以看到完整的crash
调用栈信息了。
网友评论