上一篇博客,因为第一次弄,上传的Demo代码忘记了加博客地址,然后下载的次数有几十次,但是博客浏览却是个位数,还是自己检查有没有编辑错误点的(>_ <)
知错就要改, 这次一定不能忘了!内心里狂烈得嘶吼…_,不说笑了,写博客如果可以起到帮助作用是一个方面,对自己知道的东西的梳理,归纳是另一个方面。技术分享,本身就是一种乐趣。
闲话不多说,在项目开发中,有时候总免不了NDK方面的开发,那使用NDK有哪些方面的好处?
1.可以提高代码的安全性。因为so反编译相对比较困难,所以也就间接得提升了代码的安全性,提高了程序的安全性。
2.可以很方便得使用目前已知的C或C++开源库
3.便于平台间的移植。通过C/C++实现的动态库,可以很方便得在其他平台上使用。
4.可以提高程序,在某些特定情形下的执行效率,不过并不能明显提升Android程序的性能。
这是一些大神总结的几点,也相对容易理解,使用NDK有这些方面的优点,那NDK是什么,是一个什么具体的概念?
NDK是Android提供的一个工具集合,通过NDK可以在Android中更加方便得通过JNI来访问本地代码,比如C或C++。简单得理解,就是方便访问C/C++等方面的本地代码。
怎么使用NDK,NDK怎么开发?记得以前用Eclipse进行NDK开发的时候,还比较麻烦,记得当时好像是下的Cygwin弄的,时间有点长,有些忘了,就是记得好像有些麻烦。通过Android studio进行NDK开发,相对方便很多,首先我们要下载NDK(如果自己电脑上没有)。下载可以选择官网下载(需要翻墙),也可以选择直接在as内部下载,(我使用的As是2.3正式版,在SDK Tools选项里没有找到NDK的选项,也不知道为什么),也可以百度NDK下载,自行找资源下载。我在博客的最后提供一个了android-ndk-r11b版本的NDK下载链接,有32位和64位,朋友们如果需要,可以根据需要进行下载。
NDK下载好,我们需要进行一些配置,首先打开AS的Project Structure选项,指定NDK的位置,比如我电脑上NDK的目录位置是:F:\NDK\ndk-r11b\android-ndk-r11b,指定:
指定好NDK位置,项目的local.properties里,应该会自动添加上NDK目录信息,
要使用ndk的命令,还要配置一下电脑的环境变量,新建变量,指向NDK目录
然后将新建的NDK环境变量,添加到Path的末尾(ps:Path最后添加的不要加”;”哦)
配置好NDK环境变量,打开cmd窗口,输入ndk-build命令,如果
说明NDK配置成功。配置好NDK环境,然后我们终于可以开始进行NDK开发,新建一个安卓moudle,创建一个用来供调用的Java类,定义native方法,
public class MyJni {
static {
System.loadLibrary("TestJNI");//加载(JNI)so库 完整规范:libTestJNI.so
}
//定义native方法
//静态native方法
public static native String get();//JNI调C/C++
public static native void invokeJavaMethod();//JNI调Java
}
然后选择Make Project,build当前moudle,生成.class文件(这里定义的native方法可能报错,没关系,直接build编译)。之后,打开As的终端,
通过javah命令来生成.h头文件。使用javah命令,需要定义到要编译的类的上一级,比如我这里MyJni.java文件所在的完整目录是:F:\MyBlog\app\jnindkdemo\src\main\java\com\example\jnindkdemo\MyJni.java
定义到上一级
然后使用javah命令,来生成.h头文件,
javah -jni com.example.jnindkdemo.MyJni
如果直接这样调用javah来生成.h头文件,可能会报下面这个编码错误,
这里是因为如果没有指定编码,javah会默认调用操作系统的默认编码,我的电脑是Windows8操作系统,默认编码是GBK,所以会出现上面的错误。既然是没有指定编码那就指定编码,调用javah命令时指定编码
编译完成,在moudle的java目录下,
如果生成了.h文件,说明.h头文件生成成功。然后,右键moudle,新建jni目录
因为我这里使用的是C++进行的实现,所以选择jni目录右键,创建一个myjni.cpp的文件(ps:我已经创建过,这里主要做演示)
然后将生成的.h文件里的内容,全部复制到myjni.cpp中。到这一步,就可以开始编写定义的native方法的实现。
.h文件中的全部代码:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnindkdemo_MyJni */
#ifndef _Included_com_example_jnindkdemo_MyJni
#define _Included_com_example_jnindkdemo_MyJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_jnindkdemo_MyJni
* Method: get
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnindkdemo_MyJni_get
(JNIEnv *, jclass);
/*
* Class: com_example_jnindkdemo_MyJni
* Method: invokeJavaMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_jnindkdemo_MyJni_invokeJavaMethod
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
myjni.cpp实现后的本地代码:
//
// Created by Tangshuiming99 on 2017/5/4.
//
#include <jni.h>
#include <stdio.h>
/* Header for class com_example_jnindkdemo_MyJni */
#ifndef _Included_com_example_jnindkdemo_MyJni
#define _Included_com_example_jnindkdemo_MyJni
#ifdef __cplusplus
extern "C" {
#endif
//调用Java代码方法
void callJavaMethod(JNIEnv *env, jclass thiz){
//找到Java类
jclass clazz = env->FindClass("com/example/jnindkdemo/MainActivity");
if(clazz == NULL){
printf("It's error for find MainActivity class");
return;
}
//找到要调用的方法 (Ljava/lang/String;)V方法签名 Ljava/lang/String:方法参数类型 V:void 返回值类型
jmethodID id = env->GetStaticMethodID(clazz, "calledByJNI", "(Ljava/lang/String;)V");
if(id == NULL){
printf("It's error for find calledByJni method");
return;
}
jstring data = env->NewStringUTF("data send to calledByJNI in myjni.cpp");
env->CallStaticVoidMethod(clazz, id, data);
}
/*
* Class: com_example_jnindkdemo_MyJni
* Method: get
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_jnindkdemo_MyJni_get
(JNIEnv *env, jclass thiz){
return env->NewStringUTF("Hello, I am from JNI!(我来自JNI(impl with c++)-Java调JNI");
};
/*
* Class: com_example_jnindkdemo_MyJni
* Method: invokeJavaMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_jnindkdemo_MyJni_invokeJavaMethod
(JNIEnv *env, jclass thiz){
//JNI调Java方法
callJavaMethod(env, thiz);
};
#ifdef __cplusplus
}
#endif
#endif
完成了JNI本地代码的编写,NDK的开发大概完成了一大半,接下来我们要配置ndk的gradle参数,来生成对应的so。打开moudle的build.grade,在defaultConfig下新添ndk配置
然后打开项目下的gradle.properties,添加android.useDeprecatedNdk=true,表示允许NDK版本差异。做完这些,选择Make Project,编译so文件,如果
说明so编译成功。到这里,NDK的主要开发步骤已经完成,接下来就剩最简单的调用。右键main目录,创建jniLibs目录,将生成的so文件全部复制进去
然后在MainActivity中,直接调用native方法,MainActivity代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "TAG MainActivity";
private Button JNIInvokeCppSta, JNIInvokeJava, JNIInvokeCpp;
private TextView textView;
private static Context context;
private void setupView(){
JNIInvokeCppSta = (Button)findViewById(R.id.JNIInvokeCpp_static_button);
JNIInvokeJava = (Button)findViewById(R.id.JNIInvokeJava_button);
JNIInvokeCpp = (Button)findViewById(R.id.JNIInvokeCpp_button);
textView = (TextView)findViewById(R.id.textView);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupView();
addListener();
context = getApplicationContext();
}
private void addListener() {
JNIInvokeCppSta.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Java调JNI(静态native方法)
String str = MyJni.get();
textView.setText(str);
}
});
JNIInvokeJava.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//JNI调Java方法
MyJni.invokeJavaMethod();
}
});
}
//开放给Jni调用的方法
public static void calledByJNI(String dataFromJni){
Log.w(TAG, "方法被Jni调用,接收到Jni传递过来的数据:" + dataFromJni);
if(context != null){
Toast.makeText(context, "JNI调用Java方法,接收到数据:" + dataFromJni, Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
context = null;
super.onDestroy();
}
}
运行程序,点击Java调用JNI按钮,
点击JNI调Java按钮,查看log:
05-05 15:23:37.288 28171-28171/com.example.jnindkdemo W/TAG MainActivity: 方法被Jni调用,接收到Jni传递过来的数据:data send to calledByJNI in myjni.cpp
证明Java调JNI(C++),JNI调Java,全部调用成功。这样,一次相对简单的相互调用完成。我们来总结一下,NDK大概的开发过程:
1.下载NDK,配置NDK环境(如果没有配置过)
2.定义用来调用的Java类,定义native方法
3.编译生成class文件
4.生成.h文件
5.编写本地代码(C/C++)
6.配置ndk grade脚本参数,编译生成so
7.使用生成的so,来供Java、JNI相互调用
看到这里,可能有的朋友会奇怪:目前定义的natvie方法都是静态的,那如果是非静态方法?
如果是非静态方法,那我们就多加一步对象构建,然后再通过对象调用方法就行了。比如,我们现在新定义一个非静态的native方法
//非静态native方法
public native String cycleReturn(String str);
重新编译class文件,重新生成.h头文件,编写新增的cycleReturn本地方法实现(ps:本来想传入一个字符串到JNI,然后给字符串添加一个JNI或C++的后辍传出来,所以取名cycleReturn。但是C++的语法记得不多,最后只能返回一条新字符串出来>_<):
JNIEXPORT jstring JNICALL Java_com_example_jnindkdemo_MyJni_cycleReturn
(JNIEnv *env, jobject thiz, jstring string){
return env->NewStringUTF("I from JNI, cycle return(c++)!");
};
编写完本地方法实现,重新编译生成新so,然后将新so替换掉jniLIB中的so,在MainActivty添加新JNI的调用:
JNIInvokeCpp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Java调JNI(非静态native方法)
MyJni myJni = new MyJni();
String s = myJni.cycleReturn("sss");
textView.setText(s);
}
});
运动程序:
证明,非静态native方法也调用成功。
博客中,难怪有不正确错误的地方,欢迎大家不吝指出。如果需要转载,请注明出处,谢谢。
NDK下载
源码下载