Android JNI之Java和C互相调用

来源:互联网 发布:如何删除mac桌面图标 编辑:程序博客网 时间:2024/05/22 01:15

概述

JNI是什么

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。

NDK是什么

NDK是Native Development Kit的缩写。是SDK(software development kit)软件开发工具包的一部分,不过通常需要单独下载。详见关于NDK。

JNI的优缺点

  • 优点:
    1. 和其他语言进行交互,各取所长。
    2. 增加反编译难度
  • 缺点:
    1. 失去Java跨平台的优势

Java调用C

  1. 配置module下的build.gradle

    android {   compileSdkVersion 25   buildToolsVersion "25.0.0"   defaultConfig {       ...       ndk {           moduleName "CallEachOther" //编译生成so库的名字,要和loadLibrary里面的参数一致           abiFilters "armeabi","armeabi-v7a","x86","x86_64","mips","arm64-v8a","mips64"//编译支持的平台       }   }}
  2. 新建Java

    public class JNITest_Java {   static {       System.loadLibrary("CallEachOther");   }   public native String getStringFromC();}

    这里System.loadLibrary中的参数就是build.gradlemoduleName的值,即CallEachOther。这里我们定义了一个native方法getStringFromC()。现在为止,Java端可以暂时告一段落。接下来,就该生成头文件。

  3. 生成.h头文件

    1. 打开AS自带的Terminalcd src/main/java命令进入到Java文件夹下。ps:使用cmd命令一样的效果

    2. 输入命令javah 完整包名.类名。例如:javah com.dongyk.jnitest.JNITest_Java。

      此时会在Java目录下生成包名_类名.hAS2.2.2打开会一片红,貌似是ASbug。不过不影响正常编译。

  4. main目录下新建jni文件夹,新建JniTestC.c

    #include <stdio.h>#include <stdlib.h>#include "com_dongyk_jnitest_JNITest_Java.h"JNIEXPORT jstring JNICALL Java_com_dongyk_jnitest_JNITest_1Java_getStringFromC(JNIEnv * env, jobject jobj){   char* str = "I come from C";   return (*env)->NewStringUTF(env,str);};

    这里方法的名字有一定的规则。格式:Java_包名_类名。方法名太长建议从刚生成的.h文件中复制过来。下面对这个方法简单解释下:

    1. JNIEnv * env env指针指向一个函数指针表。
    2. jobject jobj 指向在Java 代码中实例化的Java 对象 ,相当于this指针。
    3. (*env)->NewStringUTF 代表调用env#NewStringUTF()方法。
    4. 最后返回”I come from C”。
  5. MainActivity中调用。

    public class MainActivity extends AppCompatActivity {   private TextView tv;   @Override   protected void onCreate(Bundle savedInstanceState) {       super.onCreate(savedInstanceState);       setContentView(R.layout.activity_main);       tv = (TextView) findViewById(R.id.tv);       String result = new JNITest_Java().getStringFromC();       tv.setText(result);   }}

C调用Java

  1. Java类中添加方法

    public class JNITest_Java {    static {        System.loadLibrary("JavaCallC");    }    public native String getStringFromC();    public native int callAdd();    public int add(int a, int b) {        Log.i("TAG","add was called");        return a + b;    }}

    先搞明白流程。在Java层调用的肯定是Java代码,这里写了一个callAdd()方法,在调用这个方法的时候,通知C调用add()方法。这个过程中首先是C作为Java方法的具体实现,而且在C中调用了Java方法。之后调用javah命令生成.h头文件。

    /* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_dongyk_jnitest_JNITest_Java */#ifndef _Included_com_dongyk_jnitest_JNITest_Java#define _Included_com_dongyk_jnitest_JNITest_Java#ifdef __cplusplusextern "C" {#endif/* * Class:     com_dongyk_jnitest_JNITest_Java * Method:    getStringFromC * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL ava_com_dongyk_jnitest_JNITest_1Java_getStringFromC(JNIEnv*, jobject);/* * Class:     com_dongyk_jnitest_JNITest_Java * Method:    callAdd * Signature: ()V */JNIEXPORT jint JNICALL Java_com_dongyk_jnitest_JNITest_1Java_callAdd(JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif
  2. .c主函数的具体实现

    JNIEXPORT jint JNICALL Java_com_dongyk_jnitest_JNITest_1Java_callAdd(JNIEnv *env, jobject jobj){    // 得到字节码    jclass clazz = (*env)->FindClass(env,"com/dongyk/jnitest/JNITest_Java");    // 得到方法     jmethodID jmethodid = (*env)->GetMethodID(env,clazz,"add","(II)I");    // 实例化类    jobject jobject = (*env)->AllocObject(env,clazz);    // 调用方法    return (*env)->CallIntMethod(env,jobject,jmethodid,3,5);};

    (*env)->GetMethodID中最后一个参数是方法签名。因为Java支持方法重载,但是这些重载的方法在Jni中命名是一样的,为了区分函数重载才引入方法签名。得到方法签名:首先rebulid下工程,之后cd build\intermediates\classes\debug ,之后使用javap -s 包名/类名得到所有的方法签名。例如:javap -s com/dongyk/jnitest/JNITest_Java

    public int add(int, int);   descriptor: (II)I

    descriptor对应的就是方法签名。当然。里面还有FindClass、GetMethodID等方法,详见 XXX\sdk\ndk-bundle\platforms\android-xx\arch-arm\usr\include\jni.h。

  3. MainActivity中调用。

    public class MainActivity extends AppCompatActivity {    private TextView tv;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tv = (TextView) findViewById(R.id.tv);        int result = new JNITest_Java().callAdd();        tv.setText(result+"");    }}

    至此,一个Jni初入门的小Demo编写完毕~

3 0