Android studio NDK开发

xiaoxiao2021-02-28  105

  上一篇博客,因为第一次弄,上传的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下载 源码下载

转载请注明原文地址: https://www.6miu.com/read-31824.html

最新回复(0)