Android NDK模拟native crash

2020-07-23  本文已影响0人  12313凯皇

最近需要模拟出一个native crash,简单来说就是声明一个native方法,然后在c/c++层实现这个方法并触发一个异常即可。由于之前没有接触过这些,所以实现起来还是花费了挺多的时间的,这期间也涉及到了很多知识点或概念,如NDKJNIabi以及so等。

JNI的全称为Java Native Interface,即Java本地接口,类似于AIDL,提供了若干的API实现了Java和其他语言的通信(主要是CC++),目的是使Java方法能够调用C实现的一些函数

NDK全称为Native Development Kit,它是一个工具集,NDK允许用户使用类似C/C++之类的原生原生代码执行部分程序,其内部还是采用JNI机制实现的。

在对上述概念有了一个基础了解后,才好理解后面要做的事情都是在干什么,下面就以楼主现在需要实现的这个小功能为例阐述一个如何通过NDK实现一个native方法:

一、配置安装NDK

楼主的运行环境是Ubuntu,开发工具选用的Android Studio。所以NDK的安装比较简单:

  1. 打开Android StudioFile->Settings->Appearance & Behavior->System Settings->Android SDK,然后选中安装SDK Tools中的NDK即可。

  2. 安装好NDK后还需要给项目设置NDK版本,点击File->Project Structure->SDK Location,然后设置NDK的目录,我这里下来了多个NDK版本,所以我选择了其中一个版本。

  3. 安装好NDK后可以先尝试在Android StudioTerminal终端中输入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环境配置好了之后就可以开始编写代码了。

  1. 创建TestJNI.Java声明一个native方法
    public class TestJNI {
    
        static {
            System.loadLibrary("TestJNI");
        }
    
        public native void crashTest();
    }
    
  2. 使用javac生成.class文件
    javac TestJNI.java
    
  3. 使用javah生成.h文件,注意此时命令所处的位置应该是项目的java目录下
    以我的项目为例此时命令应到是在~/AndroidStudioProjects/NativeCrashTest/app/src/main/java目录下执行的,否则会报找不到类文件的错误
    javah  -jni com.example.nativecrashtest.jni.TestJNI
    
  4. 编写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
    }
    
  5. 创建Android.mk文件,添加如下代码:
    记得将LOCAL_MODULELOCAL_SRC_FILES替换成你自己的文件名。
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE        := TestJNI         
    LOCAL_SRC_FILES     := TestJni.cpp      
    include $(BUILD_SHARED_LIBRARY)
    
  6. 配置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文件并调用实现方法

  1. 相关准备工作完成之后,进入到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文件所在目录然后执行命令即可。

  1. 添加一个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();
    }
}
  1. 启动demo点击按钮触发崩溃,然后执行adb shell ls -l /data/system/dropbox命令,可以看到多出来了一个data_app_native_crash@1595486083697.txt.gz文件,取出解压就可以看到完整的crash调用栈信息了。

参考文章

上一篇下一篇

猜你喜欢

热点阅读