在一些老的项目或老的Android教程中,有可能还使用着本篇文章所描述的方式来创建一个Android NDK项目,所以,就写了此篇文章以供参考。
下面以AS中一个简单的例子来按步骤说明:
第一步:创建一个新的Android项目(此处不勾选“include C++ support”,只按普通项目创建)。
第二步:在“src/main/java/你的包名”中,新建一个调用NDK函数的Java类,并且在Java类中定义(与NDK函数名称相同的)native方法。
package com.lonly.example.ndkdemo2;
public class MyNDK {
public static native int sequare(int num);
static {
//参数是.so库的名称:libJniDemo.so 的名称为JniDemo
System.loadLibrary("JniDemo");
}
}
第三步:准备头文件(.h)。
在AS的Terminal面板,输入命令定位到上一步所创建Java类的根目录,使用下面的命令自动生成头文件javah -jni -encoding UTF-8 com.lonly.example.ndkdemo2.MyNDK(全类名),按回车键,系统会自动在“src/main/java/”目录下生成一个.h文件。
打开后是这样子的:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lonly_example_ndkdemo2_MyNDK */
#ifndef _Included_com_lonly_example_ndkdemo2_MyNDK
#define _Included_com_lonly_example_ndkdemo2_MyNDK
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_lonly_example_ndkdemo2_MyNDK
* Method: sequare
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_lonly_example_ndkdemo2_MyNDK_sequare
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
里面声明了我们定义的native方法。
第四步:创建jni目录,将生成的.h头文件移动到这个目录。
第五步:在jni目录下创建cpp文件,实现.h文件中的函数。
代码:
#include "com_lonly_example_ndkdemo2_MyNDK.h"
//计算一个整数的平方
JNIEXPORT jint JNICALL Java_com_lonly_example_ndkdemo2_MyNDK_sequare
(JNIEnv *env, jclass clz, jint num){
return num * num;
}
第六步:设置local.properties文件,指定Androidndk的根目录位置 (ndk.dir=E\:\\AndroidSDK\\ndk-bundle)。
## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Wed Oct 25 16:32:30 CST 2017
ndk.dir=E\:\\AndroidSDK\\ndk-bundle
sdk.dir=E\:\\AndroidSDK
第七步:设置build.gradle文件,编译NDK程序,生成libJniDemo.so文件。
ndk{
moduleName "JniDemo"
}
第八步:gradle.properties文件中加入android.useDeprecatedNdk=true。
第九步:调用NDK函数。
public class MainActivity extends AppCompatActivity {
// JAVA_packagename_classname_functionname
// jstring String jint int
//1.建立一个调用NDK函数的Java类,并且在Java类中定义与NDK函数名称相同的native方法
//2. 准备头文件(.h)
// 使用下面的命令自动生成头文件javah -jni -encoding UTF-8 com.lonly.example.ndkdemo2.MyNDK
//3.创建jni目录,将头文件移动到这个目录
//4.在jni目录下创建cpp文件,实现.h文件中的函数
//5.设置local.properties文件,指定Androidndk的根目录位置 ndk.dir=E\:\\AndroidSDK\\ndk-bundle
//6. 设置build.gradle文件,编译NDK程序,生成libJniDemo.so文件
//7.调用NDK函数
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.textview);
tv.setText(String.valueOf(MyNDK.sequare(21)));
}
}
知识拓展:
1..h文件中声明的函数参数JNIEnv与JavaVM、jclass与jobject。参考http://blog.youkuaiyun.com/freechao/article/details/7692239
- JNIEnv是一个与线程相关的变量,不同线程的JNIEnv彼此独立。JavaVM是虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,因此该进程的所有线程都可以使用这个JavaVM。当后台线程需要调用JNI native时,在native库中使用全局变量保存JavaVM尤为重要,这样使得后台线程能通过JavaVM获得JNIEnv。native程序中频繁使用JNIEnv*和JavaVM*。
- 当NDK方法声明为static静态方法时,参数使用jclass,非静态时,使用jobject。
2.javah命令生成.h头文件时报错误“错误: 编码GBK的不可映射字符”。
解决方法:应该使用-encoding参数指明编码方式,如:
javah -jni -encoding UTF-8 com.example.XXXX.XXXX.MainActivity
3.编译运行时,出现以下异常:
Error:Execution failed for task ':app:compileDebugNdk'. > Error: Your project contains C++ files but it is not using a supported native build system. Consider using CMake or ndk-build integration with the stable Android Gradle plugin: https://developer.android.com/studio/projects/add-native-code.html or use the experimental plugin: http://tools.android.com/tech-docs/new-build-system/gradle-experimental. Information:BUILD FAILED Information:Total time: 1.303 secs Information:1 error Information:0 warnings Information:See complete output in console
这是因为项目中包含了jni文件夹和jni文件夹中需要ndk-build的c文件,AS会调用ndk-build去编译这些jni代码,但项目中却没有将“useDeprecatedNdk”设为true。解决方法:在gradle.properites中添加“android.useDeprecatedNdk=true”。
4.生成的.so文件的位置,如何使用.so文件?
源码传送门: https://github.com/legallonly/NDKDemo2此时AS每次编译build时会将jni中的代码编译成静态库并放到app/build/intermediates/ndk/debug/lib/下,APP运行时所调用的so库就是该目录下的so库,如果想只留下so库,而不再需要jni代码,需要做的是:①删除jni文件夹;②将app/build/intermediates/ndk/debug/lib/下已经编译好的so库拷贝至app/libs中,指定so存放目录build.gradle->sourceSets;③注释掉gradle.properites中android.useDeprecatedNdk=true。