Android JNI之Java和C互相调用

Posted 一口仨馍

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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编写完毕~

以上是关于Android JNI之Java和C互相调用的主要内容,如果未能解决你的问题,请参考以下文章

Android JNI之C/C++层调用JAVA

Android JNI之C/C++层调用JAVA

Android高级NDK/JNI编程技术基础介绍

Android JNI之JAVA调用C/C++层

JNI编程实现(Linux)

Android NDK开发之Jni调用Java对象