Android Sutdio开发NDK工程

时间:2021-02-06 17:23:24
使用Android Sutdio创建一个新的工程后,接下来记录创建NDK工程的基本步骤。
本文将达到: 1. 创建NDK工程 2. 在JNI中输出Log语句 3. 指定编译的so库的abi版本 4. 解决在创建NDK工程中的问题
Step: 1. 添加native接口 注意写好native接口和System.loadLibrary()即可了,并无特别之处。 P.S:onCreate()中对R.id.txt执行setText(),所以这里需要对xml布局文件按正常的开发步骤进行修改即可。
新建Math.java,代码如下:
public class Math {
public native String getStringFromNative();
}

MainActivity 中调用此方法:
public class MainActivity extends Activity{
    static {
        System.loadLibrary("JniTest");//调用前需要load这个库
    }

   

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView txtView = (TextView) findViewById(R.id.txt);
        txtView.setText(getStringFromNative());
    }
}


Step: 2.执行Build->Make Project

Android Sutdio开发NDK工程

这一步骤执行一下,验证工程中并无其它错误,并对工程进行了编译,生成了.class文件. .class文件的生成路径是在 app_path/build/intermediates/classes/debug下的 如果javah不是基于class文件生成c头文件,此步骤可不做,基于class文件生成c头文件和基于.java文件生成头文件是一样的


Step: 3.javah生成c头文件

进入app_path/app/build/intermediates/classes/debug下,或 app_path/app/src/main/java下执行:javah com.zy.test.ndktest2.math 生成.h文件

两个生成的.h文件是一样的,生成任意一个即可,注意路径,不能到具体的包目录下,只能到debug或java路径下,然后指定的文件带包名

在:app_path/app/src/main下新建一个jni文件夹,把.h拷贝到里面,.h文件只是为了得到c函数名,可以不拷贝到这里


Step: 4.编辑c文件

在jni文件夹中新建math.c文件(可以是任意名字,只要函数名和.h中生成的函数名一样即可,也可以是.cpp文件),实现头文件中的方法,具体功能为直接return回一个String,并且使用android_log打印出相关日志。 代码如下: [cpp] view plaincopyAndroid Sutdio开发NDK工程Android Sutdio开发NDK工程
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. #include <android/log.h>  
  4.   
  5. #ifndef LOG_TAG  
  6. #define LOG_TAG "ANDROID_LAB"  
  7. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)  
  8. #endif  
  9.   
  10. /* Header for class lab_sodino_jnitest_MainActivity */  
  11.   
  12. #ifndef _Included_lab_sodino_jnitest_MainActivity  
  13. #define _Included_lab_sodino_jnitest_MainActivity  
  14. #ifdef __cplusplus  
  15. extern "C" {  //如果在c++中,就是cpp文件中,声明extern c为了让c++兼容c语言的全局函数
  16. #endif  
  17. /* 
  18.  * Class: lab_sodino_jnitest_MainActivity 
  19.  * Method: getStringFromNative 
  20.  * Signature: ()Ljava/lang/String; 
  21.  */  
  22. JNIEXPORT jstring JNICALL Java_com_zy_test_ndktest2_Math_getStringFromNative//此函数名跟.h中生成的函数名要一致
  23.   (JNIEnv * env, jobject jObj){  
  24.       LOGE("log string from ndk.");  
  25.       return (*env)->NewStringUTF(env,"Hello From JNI!");  
  26.   }  
  27.   
  28. #ifdef __cplusplus  
  29. }  
  30. #endif  
  31. #endif  

到这里后,我们再执行一个"Build->Make Project",发现"Messages Gradle Build"会给出提示如下: [java] view plaincopyAndroid Sutdio开发NDK工程Android Sutdio开发NDK工程
  1. Error:Execution failed for task ':app:compileDebugNdk'.   
  2. > NDK not configured.   
  3. Download the NDK from http://developer.android.com/tools/sdk/ndk/.Then add ndk.dir=path/to/ndk in local.properties.   
  4. (On Windows, make sure you escape backslashes, e.g. C:\\ndk rather than C:\ndk)  
这里提示了NDK未配置,并且需要在工程中的local.properties文件中配置NDK路径。好了,提示很清楚了,那我们就进入下一步吧。
注意:也可以不用javah生成.h文件,直接在native方法上按alt+enter

在上面按alt+enter生成c层的方法

Android Sutdio开发NDK工程

这样如果没有jni文件夹和.c文件的情况下,会自动生成jni文件夹和跟.java类名一样的.c类,对应的方法会自动生成,如果已经存在jni和.c文件,在对应.java文件中添加一个新的natvie方法,自动生成时会自动找到对应的.c文件并生成新加native方法对应的c方法,如果要修改为.cpp,记得在全局方法上加extern c声明

Step: 5.获取NDK
as中在线获取:

使用Android Studio内置的SDK管理器下载NDK。

Android Sutdio开发NDK工程

或者在项目上右键打开Project Structure,切到的SDK Location页进行安装

Android Sutdio开发NDK工程

这个NDK安装好后其目录在SDK目录下的ndk-bundle目录下

单独下载并配置: 下载地址:http://developer.android.com/ndk/downloads/index.html#download 下载完成后(mac版),获取.bin文件

Mac解压 . bin文件

1.获取文件权限

chmod a+x android-ndk-r10c-darwin-x86_64.bin

2. 解压出文件
./android-ndk-r10c-darwin-x86_64.bin
3.在这里配置路径:

Android Sutdio开发NDK工程
或配置local.properties: Android Sutdio开发NDK工程

Step: 6.配置NDK 这一步包括两个动作: 1.指明ndk路径 Android Sutdio开发NDK工程
2. 修改build.gradle配置    工程*有两个build.gradle配置文件,我们要修改的是在<Project>\app\build.gradle这个文件。为其在defaultConfig分支中增加上[java] view plaincopyAndroid Sutdio开发NDK工程Android Sutdio开发NDK工程
  1. ndk {  
  2.     moduleName "JniTest"  
  3.     ldLibs "log""z""m"  
  4.     abiFilters "armeabi""armeabi-v7a""x86"  
  5. }  
    以上配置代码指定的so库名称为JniTest,链接时使用到的库,对应android.mk文件中的LOCAL_LDLIBS,及最终输出指定三种abi体系结构下的so库。添加后如下图:Android Sutdio开发NDK工程
3.在gradle.properties下加入
android.useDeprecatedNdk=true

这时,再执行"Build->Rebuild Project",就可以编译出so文件了。 但在Window平台上会出现一个问题: [java] view plaincopyAndroid Sutdio开发NDK工程Android Sutdio开发NDK工程
  1. Error:Execution failed for task ':app:compileDebugNdk'.  
  2. > com.android.ide.common.internal.LoggedErrorException: Failed to run command:  
  3.  D:\Mission\adt-bundle-windows\ndk-r10b\ndk-build.cmd NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\Android.mk APP_PLATFORM=android-21 NDK_OUT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj NDK_LIBS_OUT=C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\lib APP_ABI=armeabi,armeabi-v7a,x86  
  4. Error Code:  
  5.  2  
  6. Output:  
  7.  make.exe: *** No rule to make target `C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj/local/armeabi/objs/JniTest/C_\Users\sodinochen\AndroidstudioProjects\JniTest2\app\src\main\jni', needed by `C:\Users\sodinochen\AndroidstudioProjects\JniTest2\app\build\intermediates\ndk\debug\obj/local/armeabi/objs/JniTest/C_\Users\sodinochen\AndroidstudioProjects\JniTest2\app\src\main\jni\main.o'. Stop.  
出现这个错误很莫名其妙..几番折腾下,找到一个视频出来了大概原因及解决方式:出处见Youtube视频 02:50分开始:https://www.youtube.com/watch?v=okLKfxfbz40#t=362在Windows下NDK一个bug,当仅仅编译一个文件时出现会出现此问题,解决方法就是再往jni文件夹加入一个空util.c文件即可。

编译出来的库文件被Studio输出到了下图的路径中

Android Sutdio开发NDK工程



Step: 7.安装运行

界面:

Android Sutdio开发NDK工程

查看Log打印:

Android Sutdio开发NDK工程


Step: 8.调试

在运行模块中打开jni调试开关

Android Sutdio开发NDK工程

然后选择自动生成好的native模块进行断点调试

Android Sutdio开发NDK工程

调试日志打印(打印到logcat):

首先要加入日志库的支持,在gradle中配置ldlibs加上log:

ndk {
moduleName "JniTest"
ldLibs "log", "z", "m"
abiFilters "armeabi", "armeabi-v7a", "x86"
}

然后再需要打印日志的c文件中加入头文件

#include <android/log.h>
调用其中的日志打印:

int __android_log_write(int prio, const char *tag, const char *text);
第一个参数为打印级别,为以下枚举之一:

typedef enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;
后面两个参数对应安卓中日志打印的tag和打印内容