Android JNI Java/C++互相調用

一,Java 調用 C

   1, 首先我們創建一個文件名字叫做,JNI。其實你不創建也行。看自己

public class JNI {
    //加載本地C語言文件庫。庫名字爲你寫的C語言文件名
    static {
        System.loadLibrary("Hello");
    }
    
    
    //todo: java 調用 C =======
    public native String stringFromJNI();
//相加
    public native  int numberTest(int a,int b);

//字符串拼接 java 和 C
    public native String strOrStr(String str);

 // 在C中數組中的每一個元素+10 然後返回到 java;
    public native int [] getArray(int array[]);

}

 2, 再次來到Cpp文件裏

#include <jni.h>
#include <string>
#include<android/log.h>
#define  LOG_TAG    "nativeprint"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__

// java String 轉成C char
char *jstringTostring(JNIEnv *env, jstring jstr);

#pragma clang diagnostic push
#pragma ide diagnostic ignored "err_typecheck_member_reference_arrow"
extern "C" JNIEXPORT jstring JNICALL
Java_com_cwj_ndkc_JNI_stringFromJNI(JNIEnv *env, jobject) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}


extern "C"
JNIEXPORT jint JNICALL
Java_com_cwj_ndkc_JNI_numberTest(JNIEnv *env, jobject thiz, jint a, jint b) {
    // TODO: implement numberTest()
    int result = a + b;
    return result;
}


extern "C"
JNIEXPORT jstring JNICALL
Java_com_cwj_ndkc_JNI_strOrStr(JNIEnv *env, jobject thiz, jstring str) {
    // TODO: implement strOrStr()
    char *javas = jstringTostring(env, str);
    char *cc = "我是C語言中的char*";
    strcat(javas, cc);
//    return (*env)->NewStringUTF(env,javas);
    return env->NewStringUTF((char *) javas);
}

//char* 轉成String
jstring stoJstring(JNIEnv *env, const char *pat) {
    jclass strClass = env->FindClass("java/lang/String");
    jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
    jbyteArray bytes = env->NewByteArray(strlen(pat));
    env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte *) pat);
    jstring encoding = env->NewStringUTF("utf-8");
    return (jstring) env->NewObject(strClass, ctorID, bytes, encoding);
}
// java String 轉成C char
char *jstringTostring(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("utf-8");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}

extern "C"
JNIEXPORT jintArray JNICALL
Java_com_cwj_ndkc_JNI_getArray(JNIEnv *env, jobject thiz, jintArray array) {
    // TODO: implement getArray()
    //1, 得到數組的長度
    int lenght = env->GetArrayLength(array);
    //2,得到數組的元素
    jint *intArray = env->GetIntArrayElements(array,0);
    //3,遍歷數組順便加10
    int i;
    for (i = 0; i < lenght; ++i) {
        *intArray+=10;
    }
    return array;
}

注意: 觀察,Java Native方法,再看C++裏面的實現。會發現,

C++的方法名字:是Java+JavaNative接口包名+接口名的方式生成的,對,這個不用自己寫。直接根據AS提示,自動創建。

3,在Activity中調用。先看看目錄

     case R.id.btn_java:
                //java調用C
                jni.strOrStr("參數");
                

二,C++調用Java

1,首先要有一個java 方法吧,然後C調用java了 ,最後,還是要回到Activity中去調用。 這裏只是把java方法和native接口寫在了一個類中。


    //todo: C 調用java=======================================================
    public int  addJava(int x,int y){
        Log.e(TAG, "addJava() x=" + x + " y=" + y);
        return x+y;
    }
    public native void callAdd();

    
    //todo:C  c調用靜態java方法
     public static void javaString(String jstr){
         Log.e(TAG, "javaString()  "  +jstr);

     }
     public native void javaStringCall();

2,Cpp代碼

//=============================================todo:===================== C調用java
extern "C"
JNIEXPORT void JNICALL
Java_com_cwj_ndkc_JNI_callAdd(JNIEnv *env, jobject thiz) {
    // TODO: implement callAdd()
    //要用到反射
    //1,得到字節碼
    jclass  jclass1=env->FindClass("com/cwj/ndkc/JNI");
    //2,得到方法
    jmethodID jmethodId=env->GetMethodID(jclass1,"addJava","(II)I");
    //3,實例化該類
    jobject  jobject=env->AllocObject(jclass1);
    //4,調用方法,得到結果
   jint jint1= env->CallIntMethod(jobject,jmethodId,99,1);
}
//調用靜態java方法
extern "C"
JNIEXPORT void JNICALL
Java_com_cwj_ndkc_JNI_javaStringCall(JNIEnv *env, jobject thiz) {
    // TODO: implement javaStringCall()
        //1,得到字節碼
            jclass jclass1=env->FindClass("com/cwj/ndkc/JNI");
        //2.得到方法 字節碼,方法名,方法簽名
            jmethodID jmethodId=env->GetStaticMethodID(jclass1,"javaString","(Ljava/lang/String;)V");
        //3,調用方法
            jstring jst=env->NewStringUTF("這個是java靜態方法參數");
            env -> CallStaticVoidMethod(jclass1,jmethodId,jst);
            printf("日誌:=======");
            __android_log_print(ANDROID_LOG_INFO,"日誌",  "aa");
}

3,最後調用方式是一樣的。

 case R.id.btn_c:
                //C調用java
                jni.callAdd();//C
                jni.javaStringCall();//C

注意:學這個之前先把C++ 看一遍。其實語法差不多,最大的區別是,C++的指針,和結構體,這倆要多看幾遍

三,C調用Activity中的方法,更新UI

    Activity ==================

    public  native void showToastCall();

    public void  showToast(){
        Toast.makeText(this, "C++調用我了", Toast.LENGTH_SHORT).show();
    }

    Cpp==================

    extern "C"
JNIEXPORT void JNICALL
/**
 * 
 * @param env  
 * @param thiz 這個代表的Activity  ,不需要實例化
 */
Java_com_cwj_ndkc_MainActivity_showToastCall(JNIEnv *env, jobject thiz) {
    // TODO: implement showToastCall()
    //要用到反射
    //1,得到字節碼
    jclass  jclass1=env->FindClass("com/cwj/ndkc/MainActivity");
    //2,得到方法
    jmethodID jmethodId=env->GetMethodID(jclass1,"showToast","()V");
    //3,實例化該類
//    jobject  jobject=env->AllocObject(jclass1);
    //4,調用方法,得到結果
    env->CallVoidMethod(thiz,jmethodId);
}


Activity調用=================================
                 MainActivity.this.showToastCall();

    

 

 

 

四,日誌的配置

這個放到你的Cpp文件中頂部。
#include<android/log.h>
#define  LOG_TAG    "nativeprint"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__


      build.gradle

  defaultConfig {
        applicationId "com.cwj.ndkc"
        minSdkVersion 24
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags " "
            }
            ndk {
                // Specifies the ABI configurations of your native
                // libraries Gradle should build and package with your APK.
//                abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
//                               'arm64-v8a'

                ldLibs "log"
            }

        }

使用:      __android_log_print(ANDROID_LOG_INFO,"日誌",  "aa");

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章