jni开发初试

来源:互联网 发布:网络新闻报道的特点 编辑:程序博客网 时间:2024/04/27 16:02

简单jni流程初试

JNI java本地开发接口

JNI 是一个协议
这个协议用来沟通java代码和外部的本地代码(c/c++)。
通过这个协议,java代码就可以调用外部的c/c++ 代码,
外部的c/c++代码也可以调用java代码。

首先学习熟悉下简单的jni开发流程:

1.创建一个android工程

这个工程实现一个简单的功能:
使用jni这种技术打印一个由底层C语言返回的一个字符串

2.JAVA代码中声明native 方法如下;

//第一步,在java代码中定义一个c方法的接口//相当于在java代码中定义了一个接口,接口的实现是由c语言来实现的public native String helloWorldFromC();

3. 创建jni目录,编写c代码,方法名字要对应

  • 在jni目录下,新建一个hello.c文件
    首先引入两个头文件
#include <stdio.h>#include <jni.h>
  • 在java中声明的方法是下面这样的
public native String helloWorldFromC();
  • 实现该方法,方法名要对应

方法名:需要在方法名前面加上Java表示是java中的方法,另外,需要使用类的全名,将.替换为下划线

jstring Java_com_example_hellowordfromc_MainActivity_helloWorldFromC(JNIEnv * env,jobject obj){}

这里的方法名采用的是手动写的方式,由于这种书写方式容易出错,另一种比较简单的方式就是以cmd模式进入到工程的src目录,使用javah + native方法的全类名,就可以自动生成头文件,这样native方法的c语言实现就不用手动写了,避免了不必要的错误。
+ 现在需要返回一个java String类型的字符串

注意:要想实现jni这类的程序,必须下载安装ndk,因为将需要使用该工具将c语言编译生成动态库

在ndk的ndk\platforms\android-8\arch-arm\usr\include目录下,有个jni.h文件
我们在前面引入了这个这个头文件后,可以使用里面声明的方法来实现我们想要的功能。
查看jni.h文件,可以找到==jstring (NewStringUTF)(JNIEnv, const char*);==
这个方法能够以UTF-8格式返回一个字符串

那么如何获得这个方法呢?
注意到方法中第一个参数JNIEnv * env,通过这个参数就可以拿到上面的方法了。
在jni.h中,有下面个声明:

//表示JNIEnv其实是JNINativeInterface这个结构体的指针类型typedef const struct JNINativeInterface* JNIEnv;

在JNINativeInterface这个结构体中,有(NewStringUTF)(JNIEnv, const char*)这个方法,
当我们通过传过来的参数env就可以拿到JNINativeInterface,再通过JNINativeInterface去调用这个构造字符串的方法

/* * Table of interface function pointers. */struct JNINativeInterface {    ....·   jstring     (*NewStringUTF)(JNIEnv*, const char*);    ....}

代码的实现逻辑如下:

jstring Java_com_example_hellowordfromc_MainActivity_helloWorldFromC(JNIEnv * env,jobject obj) {    //(*env) 相当于JNINativeInterface* JNIEnv    //*(*env) 相当于JNINativeInterface    //return (**env).NewStringUTF(env,"helloworldfromc");    //另一种写法    return (*env)->NewStringUTF(env,"helloworldfromc");}

4.编写Android.mk文件

在完成了C语言的实现后,需要编写Android.mk文件,使得ndk能够编译C语言生成函数库
在这个路径的文档介绍了如何编写Android.mk文件:ndk目录/android-ndk-r7b/docs/ANDROID-MK.html,可以看这个文档,写的很详细。

下面是最简单的mk文件的写法:

 #将当前路径赋值给LOCAL_PATH这个变量 LOCAL_PATH := $(call my-dir) #清空之前定义的变量 include $(CLEAR_VARS) #对应的打包函数库的名字 LOCAL_MODULE    := hello #对应的C代码的文件 LOCAL_SRC_FILES := hello.c #表示编译成动态库,在linux下的动态库一般后缀名为.so include $(BUILD_SHARED_LIBRARY)

5.Ndk编译生成动态库

在完成上面的步骤后,就可以使用ndk将c程序打包成函数库了。
另外,编译之前需要配置下ndk的环境变量,这样就可以在任意目录使用ndk-build这个工具了。
进入到对应android工程下,以cmd模式输入ndk-build运行,这样就可以生成动态库了。

6.Java代码load 动态库.调用native代码

在java代码中引入库函数,然后就可以直接调用native方法名就可以了。

    //在java代码中引入库函数    static {        System.loadLibrary("hello");    }

这样就可以运行程序看效果了。

最后贴一下整个工程完整的程序:

MainActivity

package com.example.hellowordfromc;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.Toast;public class MainActivity extends Activity {    //第一步,在java代码中定义一个c方法的接口    //相当于在java代码中定义了一个接口,接口的实现是由c语言来实现的    public native String helloWorldFromC();    //第五步,在java代码中引入库函数    static {        System.loadLibrary("hello");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void click(View view) {        Toast.makeText(getApplicationContext(), helloWorldFromC(), 0).show();    }}

hello.c文件

#include <stdio.h>#include <jni.h>//  public native String helloWorldFromC();//jstring 方法的返回值类型//方法名:需要在方法名前面加上Java表示是java中的方法,另外,需要使用类的取名,将.替换为下划线/**    //Reference types, in C.    typedef void*           jobject;    typedef jobject         jclass;    typedef jobject         jstring; */jstring Java_com_example_hellowordfromc_MainActivity_helloWorldFromC(JNIEnv * env,jobject obj) {    //第二步,生成c代码    //第三步,编写Android.mk文件    //第四步,使用ndk将c程序打包成函数库    //现在需要返回一个java String类型的字符串    //在ndk的ndk\platforms\android-8\arch-arm\usr\include目录下,有个jni.h文件    //在引入了jni.h后,我们需要利用里面声明的下面这个方法来构造一个字符串    //jstring     (*NewStringUTF)(JNIEnv*, const char*);    //注意到这个方法jstring     (*NewStringUTF)(JNIEnv*, const char*)是    //定义在JNINativeInterface这个结构体中的    //当我们通过传过来的参数env就可以拿到JNINativeInterface,再通过JNINativeInterface去调用这个构造字符串的方法    //(*env) 相当于JNINativeInterface* JNIEnv    //*(*env) 相当于JNINativeInterface    //return (**env).NewStringUTF(env,"helloworldfromc");    //另一种写法    return (*env)->NewStringUTF(env,"helloworldfromc");}

java向c语言传递数据

接下来我们看java如何向c语言传递数据

首先创建一个类,主要用于声明java的native方法
现在主要实现以下三个方法:

第一个,使用jni技术实现两个数的相加

第二个,在字符串后面拼接字符串,应用场景为web url的拼接或者一些加密运算,由于java语言的特性,应用程序容易被反编译,对于有些敏感信息需要进行加密,使用c语言实现一般很难被反编译。

第三个,由java传递int数组,C语言对其进行操作后进行返回,这个应用的场景一般为图像的处理方面

package com.example.ndkpassdata;public class DataProvider {    /**     * 计算x和y的加法        * 315      * @param x     * @param y     * @return     */    public native int add(int x ,int y);  // char   String   short   kiss  keep it simple and stupid  String[]  "123:234"     /**     * 给字符串后面拼装字符  加密运算   web url       * @param s     * @return     */    public native String sayHelloInC(String s);    //      /**     * 给c代码传递int数组   让c代码给这个数组进行操作     * 图形 声音的处理     * @param iNum     * @return     */    public native int[] intMethod(int[] iNum); }

接着以windows命令行模式,进入到该android工程的src目录,
运行如下命令:javah com.example.ndkpassdata.DataProvider
这样生成如下的头文件:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_example_ndkpassdata_DataProvider */#ifndef _Included_com_example_ndkpassdata_DataProvider#define _Included_com_example_ndkpassdata_DataProvider#ifdef __cplusplusextern "C" {#endif/* * Class:     com_example_ndkpassdata_DataProvider * Method:    add * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_example_ndkpassdata_DataProvider_add  (JNIEnv *, jobject, jint, jint);/* * Class:     com_example_ndkpassdata_DataProvider * Method:    sayHelloInC * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_ndkpassdata_DataProvider_sayHelloInC  (JNIEnv *, jobject, jstring);/* * Class:     com_example_ndkpassdata_DataProvider * Method:    intMethod * Signature: ([I)[I */JNIEXPORT jintArray JNICALL Java_com_example_ndkpassdata_DataProvider_intMethod  (JNIEnv *, jobject, jintArray);#ifdef __cplusplus}#endif#endif

这样在工程目录创建jni目录,然后将生成的头文件剪切到jni目录下,新建hello.c文件,引入#include “com_example_ndkpassdata_DataProvider.h”头文件

#include <stdio.h>#include "com_example_ndkpassdata_DataProvider.h"//要实现在C语言中打印log,需要加入如下代码//并且需要在Android.mk文件中引入log相应的函数库#include <android/log.h>#include <string.h>#define LOG_TAG "clog"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)char*   Jstring2CStr(JNIEnv*   env,   jstring   jstr){     char*   rtn   =   NULL;     jclass   clsstring   =   (*env)->FindClass(env,"java/lang/String");     jstring   strencode   =   (*env)->NewStringUTF(env,"GB2312");     jmethodID   mid   =   (*env)->GetMethodID(env,clsstring,   "getBytes",   "(Ljava/lang/String;)[B");     jbyteArray   barr=   (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312");     jsize   alen   =   (*env)->GetArrayLength(env,barr);     jbyte*   ba   =   (*env)->GetByteArrayElements(env,barr,JNI_FALSE);     if(alen   >   0)     {      rtn   =   (char*)malloc(alen+1);         //"\0"      memcpy(rtn,ba,alen);      rtn[alen]=0;     }     (*env)->ReleaseByteArrayElements(env,barr,ba,0);  //     return rtn;}//注意JNIEXPORT和JNICALL都是由javah自动生成的,可以删掉,不影响JNIEXPORT jint JNICALL Java_com_example_ndkpassdata_DataProvider_add  (JNIEnv * env, jobject jobject, jint x, jint y){    // 想在logcat控制台上 打印日志    LOGD("x=%d",x);    LOGI("y=%d",y);    // log.i(TAG,"sss");    return x+y;}JNIEXPORT jstring JNICALL Java_com_example_ndkpassdata_DataProvider_sayHelloInC  (JNIEnv * env, jobject jobject, jstring str){    char* c="hello";    // 在C语言中不能直接操作java中的字符串    // 把java中的字符串转换成c语言中 char数组    char* cstr=Jstring2CStr(env,str);    strcat(cstr,c);    LOGD("%s",cstr);    return  (*env)->NewStringUTF(env,cstr);}JNIEXPORT jintArray JNICALL Java_com_example_ndkpassdata_DataProvider_intMethod  (JNIEnv * env, jobject jobject, jintArray jarray){    // jArray  遍历数组   jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);    // 数组的长度    jsize       (*GetArrayLength)(JNIEnv*, jarray);    // 对数组中每个元素 +5    int length=(*env)->GetArrayLength(env,jarray);    int* array=(*env)->GetIntArrayElements(env,jarray,0);    int i=0;    for(;i<length;i++){        *(array+i)+=5;    }    return jarray;}

最后在android工程中loadLibrary,就可以使用这些方法了。


c语言回调java方法

==接下来我们看如何在C语言回调java方法==

这种应用场景相对比较少,另外这种实现的方式也比较复杂点。
同样,前面的步骤都是一样的,声明native方法。首先在创建一个DataProvider,我们在这里面声明native方法。

这里不同的是,要想实现c语言回调java方法,需要通过声明native方法,通过实现该native方法,然后再回调会java代码中

package com.example.ndkcallback;public class DataProvider {    //C调用java空方法    public void helloFromJava(){        System.out.println("哈哈哈  我被调用了");    }    //C调用java中的带两个int参数的方法    public int Add(int x,int y){         int result=x+y;        System.out.println("result:"+result);        return result;    }    //C调用java中参数为string的方法    public void printString(String s){        System.out.println(s);    }    public static void demo(){        System.out.println("哈哈哈,我是静态方法");    }    public native void callMethod1();    public native void callMethod2();    public native void callMethod3();    public native void callMethod4();    public native void callMethod5();}

这里回调的机制类似于java的反射机制,通过jni.h中声明的FindClass方法获得java类的字节码,然后获得方法的ID,将字节码参数,方法名,以及方法签名传到CallVoidMethod这样的方法中,就可以实现在c语言中回调java方法。

注意,可以使用javap来获得对应类内部方法的签名,签名一般由方法的参数和赶回值组成。
javap -s 打印方法的签名 注意要cd到 C:\workspace\HelloWorldFromC\bin\classes 传全类名

#include "com_example_ndkcallback_DataProvider.h"JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod1  (JNIEnv * env, jobject jobject){    /*     *        Class<?> forName = Class.forName("com.example.ndkcallback.DataProvider");        Method declaredMethod = forName.getDeclaredMethod("helloFromJava", new Class[]{});        declaredMethod.invoke(forName.newInstance(), new Object[]{});     *     *     */    ///jclass      (*FindClass)(JNIEnv*, const char*);    jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/DataProvider");    //  jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);    // 方法签名  参数和返回值    jmethodID methodId=(*env)->GetMethodID(env,clazz,"helloFromJava","()V");    // void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);    (*env)->CallVoidMethod(env,jobject,methodId);}JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod2  (JNIEnv *  env, jobject jobject){    jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/DataProvider");    jmethodID methodId=(*env)->GetMethodID(env,clazz,"Add","(II)I");    // jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);    (*env)->CallIntMethod(env,jobject,methodId,3,5);}JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod3  (JNIEnv * env, jobject jobject){   // 参数 object  就是native方法所在的类    jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/DataProvider");        jmethodID methodId=(*env)->GetMethodID(env,clazz,"printString","(Ljava/lang/String;)V");        // jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);        jstring str=(*env)->NewStringUTF(env,"hello");        (*env)->CallVoidMethod(env,jobject,methodId,str);}JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod4  (JNIEnv * env, jobject j){    jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/MainActivity");    //  jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);    // 方法签名  参数和返回值    jmethodID methodId=(*env)->GetMethodID(env,clazz,"helloFromJava","()V");    // void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);    // 需要创建DataProvider的 对象    // jobject     (*AllocObject)(JNIEnv*, jclass);    jobject obj=(*env)->AllocObject(env,clazz);  // new MainActivity();    (*env)->CallVoidMethod(env,obj,methodId);}JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod5  (JNIEnv * env, jobject j){    jclass clazz=(*env)->FindClass(env,"com/example/ndkcallback/DataProvider");    //     jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);     jmethodID  methodid=(*env)->GetStaticMethodID(env,clazz,"demo","()V");    //void        (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);     (*env)->CallStaticVoidMethod(env,clazz,methodid);}

那么这个JNIEnv是干什么用的?
其实从这个参数的名称就可以看到,就是指JNI的运行环境,我觉得它就是对Java虚拟环境的一个引用,在Android中,就是指Dalvik VM。

0 0
原创粉丝点击