Android studio NDK开发

来源:互联网 发布:linux mint美化 编辑:程序博客网 时间:2024/06/06 07:27

  上一篇博客,因为第一次弄,上传的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位置
指定好NDK位置,项目的local.properties里,应该会自动添加上NDK目录信息,

要使用ndk的命令,还要配置一下电脑的环境变量,新建变量,指向NDK目录
环境变量
然后将新建的NDK环境变量,添加到Path的末尾(ps:Path最后添加的不要加”;”哦)
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 __cplusplusextern "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 __cplusplusextern "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下载
源码下载

1 0