前段时间由于做项目紧,一直都没时间写博客,现在终于可以补上一篇了,一直想学习一点NDK开发的知识,但是迟迟没有动手,正好有一个NDK相关的项目机会,便查阅了一些资料,遂将学习的一些心得方法记录于此。
其实写这篇博客还有一个目的,在我搜寻NDK相关学习资料的过程中,大部分都是基于eclipse开发的,所以有些过时,而现在Google推荐使用AndroidStudio+CMake的方式进行NDK开发,所以想更新一下有些知识,便于大家学习参考。

首先说说这次的开发工具及版本

AndroidStudio 2.3.3
NDK 15.1.4
CMake 3.6.4
Genymotion  模拟器

一、相关概念介绍

1. 什么是NDK

NDK是一个让开发人员在Android应用中嵌入使用本地代码编写的组件的工具集。 android应用运行在Dalvik虚拟机中。NDK允许开发人员使用本地代码语言(例如C和C++)实现应用的部分功能。
上面是比较官方的介绍,通俗点来讲,就是帮助我们可以在Android应用中使用C/C++来完成特定功能的一套工具。

2. NDK的应用场景

不是说什么场景下我们都要使用NDK来开发Android的功能,由于NDK开发在一定程度上加大了项目的开发难度,我们应该综合考虑各种因素和条件,在特定场景下选用NDK来开发Android的特定功能,下面就是一些NDK适用的场景。

1. 重要核心代码保护。由于java层代码很容易反编译,而C/C++代码反汇编难度很大,所以对于重要的代码,可以使用C/C++来编写,Android去调用即可。2. Android中需要用到第三方的C/C++库。由于很多优秀的第三方库(比如FFmpeg)都是使用C/C++来编写的,我们想要使用它们,就必须通过NDK的方式来操作。3. 便于代码的移植。比如我们对于一些核心的公共组件(比如微信开源的的Mars),可能需要写一套代码在多个平台上运行(比如在Android和iOS上共用一个库),那么就需要选用NDK的方式。4. 对于音视频处理、图像处理这种计算量比较大追求性能的场景,也需要使用到NDK。

3. 什么是交叉编译

交叉编译通俗一点讲,就是在一个平台上生产在另一个平台上可执行的代码。比如我们在电脑上为一些硬件开发驱动,最终编译出的代码需要在硬件上使用。还有我们在电脑上将C/C++代码编译成相应的库,然后在ARM、x86、mips等平台上使用。NDK中就我们提供了交叉编译的工具,帮助我们可以将我们编写的C/C++代码生成各个平台需要的库。

4. 什么是jni

JNI的全称是Java Native Interface,它允许Java语言可以按照一定的规则去调用其他语言,与其进行交互。

jni的实现流程如下:编写Java代码(.java) —————> 编译生成字节码文件(.class) —————> 产生C头文件(.h) —————> 编写jni实现代码(.c) —————> * 编译成链接库(.so)**

5 . 什么是链接库

链接库可以简单理解为函数库,就是我们的C/C++代码编译生成的产物,供我们的java进行调用,同时,它又分为动态链接库和静态链接库。
动态链接库 : 在程序运行时才载入所需要的库,所以控制比较灵活,整个可执行文件的体积较小。

静态链接库 : 在程序的链接阶段,将其引用的代码也一并打包在了最终的可执行文件中,这样做的好处是可以不再依赖与环境,移植方便,但是这样做会使可执行文件体积较大。在Android中的静态链接库是.a文件。

6 . 什么是CMake

CMake是一款开源的跨平台自动化构建系统,它通过CMakeLists.txt来声明构建的行为,控制整个编译流程,我们在接下来的NDK开发中将会使用它配合Gradle来进行相关开发。

二、配置NDK开发环境

俗话说 工欲善其事必先利其器,接下来,我们先配置一下我们在开发NDK过程中要使用到的一些工具。

1 . 安装NDK

打开AndroidStudio,在如图所示的地方找到 SDK Tools, 勾选 NDK、LLDB、CMake,然后点击 Apply ,等待其下载安装完成,便配置好了基本的开发环境。

安装的工具中NDK和CMake上面已经介绍过了,LLDB是一款在开发NDK过程中的调试器,这篇博客中将不会介绍。

做完了上面的步骤我们就可以开始我们的第一个NDK程序了。

三、创建第一个NDK程序

下面我将以图示加序号的方式来说明新建步骤。
1 . 新建一个项目,填写基本信息,记得勾选Include C++ support,便于AndroidStudio为我们生成一些默认的配置。


2 . 接下来的几个步骤就选择默认设置

3 . 到最后一步如图,C++ Standard 选择 Toolchain Default,其它不变即可。

说明:

(a) C++ Standard是让我们选择C++标准,我们使用默认的CMake的设置

(b) Exceptions Support是添加C++中对于异常的处理,如果选中,Android Studio会
将 -fexceptions标志添加到模块级build.gradle文件的cppFlags中,Gradle会将其传递到CMake。

(c) Runtime Type Information Support是启用支持RTTI,请选中此复选框。如果选中,Android Studio会将-frtti标志添加到模块级build.gradle文件的cppFlags中,Gradle会将其传递到 CMake。

新建好的项目如图

下面我们看看这个默认的项目中AndroidStudio都为我们做了哪些事 :

(1) 在app 模块中新建了一个cpp文件夹用来放置我们的C/C++文件,此处默认的文件为native-lib.cpp

native-lib.cpp文件内容:

#include <jni.h>
#include <string>extern "C"
JNIEXPORT jstring JNICALL
Java_com_codekong_ndkdemo_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}

上面的代码中先是引入了固定的头文件jni.h,然后是引入了代码中需要用到的头文件,至于后面的返回字符串,我们在后面的时候将会讲到,现在只需要知道它就是返回了Hello from C++这个字符串即可。

上面的extern “C” 是告诉编译器按照C语言的规则来编译我们下面的代码

(2) 在app 模块下新建了一个CMakeLists.txt文件用于定义一些构建行为

CMakeLists.txt文件内容 :

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.4.1)# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).src/main/cpp/native-lib.cpp )# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.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 )# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library.native-lib# Links the target library to the log library# included in the NDK.${log-lib} )

上面的完成的有注释的内容,但其中最核心的也就几句,下面分别做介绍:

cmake_minimum_required(VERSION 3.4.1) 用来设置在编译本地库时我们需要的最小的cmake版本,AndroidStudio自动生成,我们几乎不需要自己管。

add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).src/main/cpp/native-lib.cpp )

add_library用来设置编译生成的本地库的名字为native-lib,SHARED表示编译生成的是动态链接库(这个概念前面已经提到过了),src/main/cpp/native-lib.cpp表示参与编译的文件的路径,这里面可以写多个文件的路径。

find_library 是用来添加一些我们在编译我们的本地库的时候需要依赖的一些库,由于cmake已经知道系统库的路径,所以我们这里只是指定使用log库,然后给log库起别名为log-lib便于我们后面引用,此处的log库是我们后面调试时需要用来打log日志的库,是NDK为我们提供的。

target_link_libraries 是为了关联我们自己的库和一些第三方库或者系统库,这里把我们把自己的库native-lib库和log库关联起来。

(3)在 app 模块对应的build.gradle文件中增加了一些配置,如下:

apply plugin: 'com.android.application'android {compileSdkVersion 25buildToolsVersion "25.0.3"defaultConfig {applicationId "com.codekong.ndkdemo"minSdkVersion 15targetSdkVersion 25versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"externalNativeBuild {cmake {cppFlags ""}}}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}externalNativeBuild {cmake {path "CMakeLists.txt"}}
}dependencies {compile fileTree(dir: 'libs', include: ['*.jar'])androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {exclude group: 'com.android.support', module: 'support-annotations'})compile 'com.android.support:appcompat-v7:25.3.1'compile 'com.android.support.constraint:constraint-layout:1.0.2'testCompile 'junit:junit:4.12'
}

主要的变化就两点:
(a) 在 android 的大括号内增加了 externalNativeBuild标签

externalNativeBuild {cmake {cppFlags ""}
}

这里的cppFlags里面的内容为空,这里其实就是配置了我们在新建项目的时候的第(3)步中讲到的,如果我们勾选了异常支持和RTTI支持,这里就会有相关的配置信息。

(b) 使用 externalNativeBuild 来指定 CMakeLists.txt文件的路径,由于build.gradle文件和CMakeLists.txt文件在同一目录下,所以此处就直接写文件名啦。

externalNativeBuild {cmake {path "CMakeLists.txt"}
}

(4) 最终在MainActivity.java 文件中我们看到了函数的调用过程如下:

public class MainActivity extends AppCompatActivity {// Used to load the 'native-lib' library on application startup.static {System.loadLibrary("native-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// Example of a call to a native methodTextView tv = (TextView) findViewById(R.id.sample_text);tv.setText(stringFromJNI());}/*** A native method that is implemented by the 'native-lib' native library,* which is packaged with this application.*/public native String stringFromJNI();
}

我们看到其实这里就主要做了三步操作:
(a)使用 native 关键字声明了一个本地方法 stringFromJNI()

(b)使用loadLibrary()方法载入我们编译生成的动态链接库,这里要注意,虽然我们生成的动态链接库名称为libnative-lib.so,但是此处我们只需要写 native-lib,即就是我们在CMakeLists.txt文件中指定的名称,其中的lib前缀和.so后缀是系统为我们添加的。

(c)我们在布局文件中放了一个TextView,然后将函数返回的字符串放到了TextView中。

我们对比一下我们声明的native方法和最终我们的ndk帮我们生成的c++代码的函数名:

//我们声明的native方法名
public native String stringFromJNI();//ndk帮我们生成的c++方法名
JNIEXPORT jstring JNICALL
Java_com_codekong_ndkdemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */)

我们看到ndk生成的方法名是以 Java_包名类名方法名 的形式,其实这个方法名是javah帮助我们生成的。

注:我们对于新创建的项目可以点击菜单栏的Build——> Make Project来先编译项目,然后在 <项目目录>\app\build\intermediates\cmake\debug\obj\armeabi 下面就可以看到生成的动态链接库。由于我们没有指定我们需要生成什么平台的so库,所以系统帮我们生成了各个平台的库,分别放在对应的文件夹下面。

好了,以上就是我们使用AndroidStudio创建的第一个项目的分析,了解了上面这些,我们就基本了解了NDK开发的的一般步骤。

四、NDK开发中常用的函数

上面我们只是看了AndroidStudio为我们生成的代码,还没有自己动手写一行代码,下面我们就开始动手写代码啦。下面我们就自己新建一个项目,主要学习一下NDK里面的字符串操作和数组的操作。

1 . 新建项目,这个过程,我们在上一步的 三、创建第一个NDK程序 中已经讲到了,这里不再赘述。

2 . 删除项目为我们自动生成的native-lib.cpp文件,然后在cpp目录下新建一个hello-lib.c的文件,这时候AndroidStudio就会提醒我们这个文件没有在CMakeLists.txt文件中进行配置,所以我们去改动一下该文件,改动如下:

cmake_minimum_required(VERSION 3.4.1)add_library(hello-libSHAREDsrc/main/cpp/hello-lib.c )find_library(log-liblog )target_link_libraries(hello-lib${log-lib} )

这里我们把我们新建的hello-lib.c的路径加入到了CMakeLists.txt文件中,而且也将log库与我们的库关联了起来,其他的具体信息前面已经讲过了。

3 . 我们在MainActivity.java文件对应的布局文件中放入一个TextView,并且在MainActivity.java中获取它。

package com.codekong.ndkdemo;import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TextView tv = (TextView) findViewById(R.id.sample_text);}
}

4 . 接着我们在MainActivity.java文件中写一个native函数sayHelloWorld(),并将其返回的字符串设置给TextView,然后使用loadLibrary载入我们的自定义库。

package com.codekong.ndkdemo;import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;public class MainActivity extends AppCompatActivity {// Used to load the 'native-lib' library on application startup.static {System.loadLibrary("hello-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TextView tv = (TextView) findViewById(R.id.sample_text);//将返回值设置给TextViewtv.setText(sayHelloWorld());}//自定义的native函数public native String sayHelloWorld();
}

5 . 见证AndroidStudio强大的地方到了,我们在我们声明的sayHelloWorld()函数上按住Alt+Enter,就会自动生成C++代码,但是,这里存在一个问题,初次生成,AndroidStudio会创建一个jni文件夹,然后在里面创建hello-lib.c文件,并且自动生成对应的C代码,但是,由于我们在CMakeLists.txt中指定的路径为src/main/cpp/hello-lib.c,所以我们这里直接将我们的src/main/jni/hello-lib.c中的代码拷贝到src/main/cpp/hello-lib.c中,并将jni目录删除即可。hello-lib.c中的内容如下:

#include <jni.h>JNIEXPORT jstring JNICALL
Java_com_codekong_ndkdemo_MainActivity_sayHelloWorld(JNIEnv *env, jobject instance) {return (*env)->NewStringUTF(env, "Hello World");
}

上面的代码中,我们拿到了jni环境指针,然后调用其NewStringUTF()方法,传入env指针和我们需要的字符串,便可以了。

运行程序,便可以看到界面上显示Hello World。

下面我们开始看看java中的类型和native类型的对应关系:

可以看出上面的类型对应关系还是十分清楚的,其实我们在jni.h文件中就可以看到上述的定义。

下面我们主要说说字符串的使用和数组的使用

(1)字符串的使用

其实上面新建的项目就已经演示了返回字符串的例子,使用(*env)->NewStringUTF(env, “Hello World”);即可返回字符串结果,下面在看看如何处理java传入的字符串。通过jni将Java传入的字符串写入文件。

(a) 在Mainactivity中添加如下代码

public native void writeFile(String filePath);

(b) 在hello-lib.c中生成如下代码

JNIEXPORT void JNICALL
Java_com_codekong_ndkdemo_MainActivity_writeFile(JNIEnv *env, jobject instance, jstring filePath_) {const char *filePath = (*env)->GetStringUTFChars(env, filePath_, 0);(*env)->ReleaseStringUTFChars(env, filePath_, filePath);
}

上面是AndroidStudio生成的代码,可以看出它主要用到了 (*env)->GetStringUTFChars(env, filePath_, 0); 来将java传入的字符串转化为c语言的char指针,最后又使用(*env)->ReleaseStringUTFChars(env, filePath_, filePath);将我们的指针指向的空间释放。

(c)我们可以在这个基础上写一个写入文件的小例子,代码如下:

JNIEXPORT void JNICALL
Java_com_codekong_ndkdemo_MainActivity_writeFile(JNIEnv *env, jobject instance, jstring filePath_) {const char *filePath = (*env)->GetStringUTFChars(env, filePath_, 0);FILE *file = fopen(filePath, "a+");char data[] = "I am a boy";int count = fwrite(data, strlen(data), 1, file);if (file != NULL) {fclose(file);}(*env)->ReleaseStringUTFChars(env, filePath_, filePath);
}

以上代码记得加头文件

#include <jni.h>
#include <stdio.h>
#include <string.h>

(d)还要记得在AndroidMainfest.xml文件中添加文件读写权限,然后在MainActivity.java中调用native方法

static {System.loadLibrary("hello-lib");
}@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);String filePath = "/mnt/sdcard/boys.txt";Toast.makeText(MainActivity.this, filePath, Toast.LENGTH_SHORT).show();updateFile(filePath);

注意:由于我这里使用的是Genymotion模拟器,所以那样写文件路径就表示文件管理器根目录。

运行上面的程序,就可以在文件管理器根目录下发现boys.txt,并在其中发现我们写入的字符串。

(2) 数组的使用

现在我们看看我们如何在jni中使用数组。
数组的操作主要有以下两种方式(我们这里仍然用我们刚才的hello-lib.c文件测试):

(a) 直接操作数组指针。

我们现在看看在MainActivity.java 和 hello-lib.c文件中的代码

public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);int[] testData = new int[]{1, 2, 3, 4, 5};for (int i = 0; i < testData.length; i++) {Log.d(TAG, "testData: origin " + testData[i]);}//测试operationArray(testData);for (int i = 0; i < testData.length; i++) {Log.d(TAG, "testData: after " + testData[i]);}//声明方法public native void operationArray(int[] args);static {//载入库System.loadLibrary("hello-lib");}
}

上面的代码写完,我们仍然使用Alt+Enter快捷键生成我们c语言的代码,如下:

JNIEXPORT void JNICALL
Java_com_codekong_ndkdemo_MainActivity_operationArray(JNIEnv *env, jobject instance,jintArray args_) {//获得数组指针jint *args = (*env)->GetIntArrayElements(env, args_, NULL);//获得数组长度jint len = (*env)->GetArrayLength(env, args_);int i = 0;for (; i < len; ++i) {++args[i];}//释放(*env)->ReleaseIntArrayElements(env, args_, args, 0);
}

最终结果: 数组中的每个元素都被加1

上面其实还是很好理解的,大家可以查看注释。

(b) 将传入的数组先拷贝一份,操作完以后再将数据拷贝回原数组

这次还是像上面一样,只是我们在C++中换了一种操作数组的方式

//声明我们的本地方法,其余代码与上面一致
public native void operationArray2(int[] args);int[] testData2 = new int[]{1, 2, 3, 4, 5};
for (int i = 0; i < testData2.length; i++) {Log.d(TAG, "testData2: origin " + testData2[i]);
}operationArray2(testData2);
for (int i = 0; i < testData2.length; i++) {Log.d(TAG, "testData2: afetr " + testData2[i]);
}
JNIEXPORT void JNICALL
Java_com_codekong_ndkdemo_MainActivity_operationArray2(JNIEnv *env, jobject instance,jintArray args_) {//声明一个native层的数组,用于拷贝原数组jint nativeArray[5];//将传入的jintArray数组拷贝到nativeArray(*env)->GetIntArrayRegion(env, args_, 0, 5, nativeArray);int i = 0;for (; i < 5; ++i) {//给每个元素加5nativeArray[i] += 5;}//将操作完成的结果拷贝回jintArray(*env)->SetIntArrayRegion(env, args_, 0, 5, nativeArray);

最终结果:数组中每个元素都加5

注意: 我们上面的两种方式返回值都是void,也就是说我们对数组的改变都是最终改变了原来数组的值。

五、NDK自定义配置

下面我们说一下NDK里面最常见的几点配置方法,这里也是记录方便自己以后查阅

1 . 添加多个参与编译的C/C++文件

首先,我们发现我们上面的例子都是涉及到一个C++文件,那么我们实际的项目不可能只有一个C++文件,所以我们首先要改变CMakeLists.txt文件,如下 :

add_library( HelloNDKSHAREDsrc/main/cpp/HelloNDK.csrc/main/cpp/HelloJNI.c)

简单吧,简单明了,但是这里要注意的是,你在写路径的时候一定要注意当前的CMakeLists.txt在项目中的位置,上面的路径是相对于CMakeLists.txt 写的。

2 . 我们想编译出多个so库

大家会发现,我们上面这样写,由于只有一个CMakeLists.txt文件,所以我们会把所有的C/C++文件编译成一个so库,这是很不合适的,这里我们就试着学学怎么编译出多个so库。

先放上我的项目文件夹结构图:

然后看看我们每个CMakeLists.txt文件是怎么写的:

one文件夹内的CMakeLists.txt文件的内容:

ADD_LIBRARY(one-lib SHARED one-lib.c)target_link_libraries(one-lib log)

two文件夹内的CMakeLists.txt文件的内容:

ADD_LIBRARY(two-lib SHARED two-lib.c)target_link_libraries(two-lib log)

app目录下的CMakeLists.txt文件的内容

# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.4.1)add_library( HelloNDKSHAREDsrc/main/cpp/HelloNDK.csrc/main/cpp/HelloJNI.c)
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 )
target_link_libraries(HelloNDK log)
ADD_SUBDIRECTORY(src/main/cpp/one)
ADD_SUBDIRECTORY(src/main/cpp/two)

通过以上的配置我们可以看出CMakeLists.txt 文件的配置是支持继承的,所以我们在子配置文件中只是写了不同的特殊配置项的配置,最后在最上层的文件中配置子配置文件的路径即可,现在编译项目,我们会在 <项目目录>\app\build\intermediates\cmake\debug\obj\armeabi 下面就可以看到生成的动态链接库。而且是三个动态链接库

3 . 更改动态链接库生成的目录

我们是不是发现上面的so库的路径太深了,不好找,没事,可以配置,我们只需要在顶层的CMakeLists.txt文件中加入下面这句就可以了

#设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

然后我们就可以在app/src/main下看到jniLibs目录,在其中看到我们的动态链接库的文件夹和文件(这里直接配置到了系统默认的路径,如果配置到其他路径需要在gradle文件中使用jinLibs.srcDirs = [‘newDir’]进行指定)。

六、NDK错误调试

在开发的过程中,难免会遇到bug,那怎么办,打log啊,下面我们就谈谈打log和看log的姿势。

1. 在C/C++文件中打log

(1) 在C/C++文件中添加头文件

#include <android/log.h>

上面是打印日志的头文件,必须添加

(2) 添加打印日志的宏定义和TAG

//log定义
#define  LOG    "JNILOG" // 这个是自定义的LOG的TAG
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG,__VA_ARGS__) // 定义LOGD类型
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__) // 定义LOGI类型
#define  LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG,__VA_ARGS__) // 定义LOGF类型

上面的日志级别和Android中的log是对应的。

(3) 经过上面两步,我们就可以打印日志啦

int len = 5;
LOGE("我是log %d", len);

现在我们就可以在logcat中看到我们打印的日志啦。

2 . 查看报错信息

首先我们先手动写一个错误,我们在上面的C文件中找一个函数,里面写入如下代码:

int * p = NULL;
*p = 100;

上面是一个空指针异常,我们运行程序,发现崩溃了,然后查看控制台,只有下面一行信息:

libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 17481

完全看不懂上面的信息好吧,这个也太不明显了,下面我们就学习一下如何将上面的信息变得清楚明了

我们需要用到是ndk-stack工具,它在我们的ndk根目录下,它可以帮助我们把上面的信息转化为更为易懂更详细的报错信息,下面看看怎么做:

(1) 打开AndroidStudio中的命令行,输入adb logcat > log.txt

上面这句我们是使用adb命令捕获log日志并写入log.txt文件,然后我们就可以在项目根目录下看到log.txt文件

(2) 将log.txt打开看到报错信息,如下:

F/libc    (17481): Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 17481 (dekong.ndkdemo1)I/DEBUG   (   67): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***I/DEBUG   (   67): Build fingerprint: 'generic/vbox86p/vbox86p:5.0/LRX21M/genymotion08251046:userdebug/test-keys'I/DEBUG   (   67): Revision: '0'I/DEBUG   (   67): ABI: 'x86'I/DEBUG   (   67): pid: 17481, tid: 17481, name: dekong.ndkdemo1  >>> com.codekong.ndkdemo1 <<<I/DEBUG   (   67): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0I/DEBUG   (   67):     eax 00000000  ebx f3494fcc  ecx ffa881a0  edx 00000000I/DEBUG   (   67):     esi f434e2b0  edi 00000000I/DEBUG   (   67):     xcs 00000023  xds 0000002b  xes 0000002b  xfs 00000007  xss 0000002bI/DEBUG   (   67):     eip f3492a06  ebp ffa88318  esp ffa88280  flags 00210246I/DEBUG   (   67):I/DEBUG   (   67): backtrace:I/DEBUG   (   67):     #00 pc 00000a06  /data/app/com.codekong.ndkdemo1-2/lib/x86/libHelloNDK.so (Java_com_codekong_ndkdemo1_MainActivity_updateFile+150)I/DEBUG   (   67):     #01 pc 0026e27b  /data/dalvik-cache/x86/data@app@com.codekong.ndkdemo1-2@base.apk@classes.dexI/DEBUG   (   67):     #02 pc 9770ee7d  <unknown>I/DEBUG   (   67):     #03 pc a4016838  <unknown>I/DEBUG   (   67):I/DEBUG   (   67): Tombstone written to: /data/tombstones/tombstone_05

现在的报错信息还是看不懂,所以我们需要使用ndk-stack转化一下:

(3) 继续在AndroidStudio中的命令行中输入如下命令(在这之前,我们必须要将ndk-stack的路径添加到环境变量,以便于我们在命令行中直接使用它)

ndk-stack -sym app/build/intermediates/cmake/debug/obj/x86 -dump ./log.txt

上面的-sym后面的参数为你的对应平台(我是Genymotion模拟器,x86平台)的路径,如果你按照上面的步骤改了路径,那就需要写改过的路径,-dump后面的参数就是我们上一步得出的log.txt文件,执行结果如下:

********** Crash dump: **********
Build fingerprint: 'generic/vbox86p/vbox86p:5.0/LRX21M/genymotion08251046:userdebug/test-keys'
pid: 17481, tid: 17481, name: dekong.ndkdemo1  >>> com.codekong.ndkdemo1 <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Stack frame I/DEBUG   (   67):     #00 pc 00000a06  /data/app/com.codekong.ndkdemo1-2/lib/x86/libHelloNDK.so (Java_com_codekon
g_ndkdemo1_MainActivity_updateFile+150): Routine Java_com_codekong_ndkdemo1_MainActivity_updateFile at F:\AndroidFirstCode\NDK
Demo1\app\src\main\cpp/HelloJNI.c:32
Stack frame I/DEBUG   (   67):     #01 pc 0026e27b  /data/dalvik-cache/x86/data@app@com.codekong.ndkdemo1-2@base.apk@classes.d
ex
Stack frame I/DEBUG   (   67):     #02 pc 9770ee7d  <unknown>: Unable to open symbol file app/build/intermediates/cmake/debug/
obj/x86/<unknown>. Error (22): Invalid argument
Stack frame I/DEBUG   (   67):     #03 pc a4016838  <unknown>: Unable to open symbol file app/build/intermediates/cmake/debug/
obj/x86/<unknown>. Error (22): Invalid argument
Crash dump is completed

尤其是上面的一句:

g_ndkdemo1_MainActivity_updateFile+150): Routine Java_com_codekong_ndkdemo1_MainActivity_updateFile at F:\AndroidFirstCode\NDK
Demo1\app\src\main\cpp/HelloJNI.c:32

准确指出了发生错误的行数,便于我们定位错误。

好了,上面就是简单介绍的调试技巧。

七、后记

终于写完了,这一次的内容有点多,但都是一些简单的入门的知识,我也是刚接触不久,希望通过总结加深理解,写出来帮助有需要的人,真心希望可以帮助到他人,大神勿喷,错误之处,多多指点。还有就是最近项目在做跑步功能,类似悦跑圈,功能开发完后会再分享一篇博文,敬请关注!

AndroidStudio中的NDK开发初探相关推荐

  1. 学海无涯!如何在Android-Studio下进行NDK开发,全网疯传

    前言 当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你会不会热修复,懂不懂性能优化,火箭造得让你猝及不防,结果就是凉凉:现如今市场,热修复.性能优化.NDK.APP开发.架构.源码等可以 ...

  2. 在android studio中配置ndk开发环境

    环境:android studio 1.5 android-ndk-r10b 1.下载android-ndk-r10b,解压. 2.android studio创建新项目, 配置ndk环境 打开fil ...

  3. android下载模块封装,AndroidStudio 3.0 NDK开发2-AAR模块封装

    完成了对JNI的编译,有时候是使用aar包提供给其他同学使用:或者说在共同开发的一个项目中,并不是每个人都需要开发C.C++功能.所以没必须每台电脑都配置好NDK的编译环境,所以大多数还是以AAR包的 ...

  4. 一篇博客带你熟悉Eclipse、AndroidStudio下搭建NDK环境(内有Demo)

    文章目录 一.NDK可以干什么 二.NDK开发环境搭建 三.一个简单的NDK小案例的编写 一.NDK可以干什么 NDK:(Native Development Kit),原生开发工具包是一组可以让您在 ...

  5. NDK开发_cwin配置+编程简单步骤

    最近在调试 车辆识别demo,说实话,虽然最初接触jni的时候,是在eclipse工程中进行开发的,不过后面遇到的项目都是直接放到了android 源码中编译的,所以对于在eclipse中的ndk开发 ...

  6. Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发

    Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...

  7. android ndk 界面开发教程,AndroidStudio NDK开发最佳入门实践

    AndroidStudio NDK开发最佳入门实践 网上一些介绍AndroidStudio NDK入门的教程,感觉都不是很完整和全面,也没有告诉初学AndroidStudio NDK的同学们一些需要注 ...

  8. android ndk怎样加载o文件_JNI初探之NDK 开发环境配置

    安装 CMake.LLDB与NDK 开发工具包 CMake 简介 CMake 是一款比make更强大的编译自动配置工具,它可以根据不同平台.不同的编译器,并通过CMakeLists.txt文件中简单的 ...

  9. 【Android NDK 开发】NDK 交叉编译 ( Ubuntu 中交叉编译动态库 | Android Studio 中配置使用第三方动态库 )

    文章目录 I . 动态库 与 静态库 II . 编译动态库 III. Android Studio 使用第三方动态库 IV . Android Studio 关键代码 V . 博客资源 I . 动态库 ...

  10. AndroidStudio下使用cmake开发ndk

    前言 之前,每次需要边写C++代码的时候,我的内心都是拒绝的.  1. 它没有代码提示!!!这意味着我们必须自己手动敲出所有的代码,对于一个新手来说,要一个字母都不错且大小写也要正确,甚至要记得住所有 ...

最新文章

  1. 自己觉得比较好的专业书籍
  2. Java中的Split方法不适用于一个句号
  3. C语言建立有向图的邻接表及其遍历操作
  4. 如何将eclipse设置为炫丽的全黑背景!
  5. 6月 Python 开源项目 Top10,还不收藏~
  6. m3u直播源_教你创建电视直播源
  7. docker为什么比虚拟机快
  8. Linux下端口占用解决方法
  9. 理解 Delphi 的类(十) - 深入方法[15] - 调用其他单元的函数
  10. libgmailer更新了,俺的下载空间又可以使用了(使用G-Share)
  11. 2016最新版App Store应用审核指南完整版
  12. 复合函数求导经典例题_复合函数求导解析及练习
  13. 新疆上半年工业品价格总水平创十七年新低
  14. 模电和数电复习资料//2021-2-18
  15. ‘github提交超时‘
  16. CentOS7和Ubuntu18.10下运行Qt Creator出现cannot find -lGL的问题的解决方案
  17. 【最短路】Graph practice T2 drive 题解
  18. javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection? HTTPS请求异常
  19. Android 注解处理器使用攻略
  20. windows10升级windows11后微信等软件无法连接网络

热门文章

  1. 分不清蓝牙适配器、蓝牙接收器和蓝牙发射器?伦茨科技为你讲解
  2. 如何使用 SEGGER Embedded Studio创建库文件?
  3. 旅游B2B2C系统解决方案
  4. 使用WireShark生成地理位置数据地图
  5. 维宏控制卡win7 驱动_雕刻机专用维宏5.55运动驱动控制卡
  6. 内核流浪猫流浪狗宠物领养平台H5源码
  7. 可替换MPS MP2451的高压DCDC芯片FS2451助力智能电表设计40V0.5A降压IC
  8. python cad模块_Pycad: Python Extension for AutoCad
  9. 制作ftl文件通过FreeMarke生成PDF文件(含图片处理)
  10. U盘加密软件测试自学,利用联想USB接口加密软件给你的U盘加密、设定访问权限...