Android JNI之Java和C互相调用

概述

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 __cplusplus
    
    extern "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编写完毕~

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值