dc學習之基於Android Studio的jni開發總結

AS中jni技術 學習筆記

最近在學習AS下的jni開發技術,現將自己的學習筆記整理出來,望可以幫助剛學習jni的童鞋,大家有什麼不懂的地方,提出來,我們一起學習,一起進步。在本篇文章中,借鑑了一些大神們的例子,在此給出鏈接地址:借鑑大神的資料
我將從以下步驟來整理學習筆記:

  1. AS的環境配置,及簡單的程序實現。
  2. java調用c/c++代碼的實現。
  3. c/c++調用java代碼的實現。

AS jni開發的環境配置

在環境配置中,我們需要達到的目標:
1. 創建一個新工程,添加本地接口
2. 配置NDK中所需環境
3. 實現簡單的java調用c/c++,“dc,你好”程序。

好的,廢話不多說,新建工程

我新建了一個名叫JNISimpleDemo的工程,在工程的com.example.jnisimpledemo.view目錄中新建了一個名叫DCHello的類,我們在這個類中,添加native方法,代碼如下:

public class DCHello {
    private static final String libSoName = "DCHello";

    /**
     * 載入JNI生成的so庫文件
     */
    static {
        System.loadLibrary(libSoName);
    }

    /**
     * 該方法爲native方法.
     */
    public native String say();
}

接下來,我們要把這個本地的方法生成頭文件;首先,我們要通過AS的MakeProject生成class文件
生成classes文件,它對應的文件路徑在C:\Users\Administrator\AndroidStudioProjects\JNISimpleDemo\app\build\intermediates\classes\debug\com\example\jnisimpledemo中(這個是我的文件路徑);接着,進入關鍵步驟,利用javah -d jni命令生成c的頭文件。

第一步:進入AS的終端Terminal。

我們可以進入“C:\Users\Administrator\AndroidStudioProjects\JNISimpleDemo\app\src\main>”目錄,這樣再運用命令產生頭文件,可以確保jni文件夾正好在main文件夾下面。

第二步:生成“.h”頭文件

操作命令:”javah -d jni -classpath E:\zycSDK\platforms\android-23\android.jar;….\build\
intermediates\classes\debug com.example.jnisimpledemo.view.DCHello”當然,在生成頭文件的過程中,要根據自己的文件路徑設置。

第三步:看看我們的頭文件

ok,至此,我們的頭文件已經生成了,並且目錄結構爲:jni目錄
打開文件,我們可以看到:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_jnisimpledemo_view_DCHello */

#ifndef _Included_com_example_jnisimpledemo_view_DCHello
#define _Included_com_example_jnisimpledemo_view_DCHello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jnisimpledemo_view_DCHello
 * Method:    say
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnisimpledemo_view_DCHello_say
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

其中“Java_com_example_jnisimpledemo_view_DCHello_say”就是本地方法在頭文件中的表示了。
“java”代表此方法來源於java的標識頭。
“com_example_jnisimpledemo_view_DCHello_say”代表say方法在工程中的路徑。
我們在jni文件夾中新建一個sayHello.c文件用於測試:

#include <com_example_jnisimpledemo_view_DCHello.h>
#include <jni.h>

JNIEXPORT jstring JNICALL Java_com_example_jnisimpledemo_view_DCHello_say (JNIEnv * env, jobject jObj){
        jstring str = (*env)->NewStringUTF(env, "dc 你好!");
        return str;
}

第四步:配置NDK環境

現在,我們不妨運行一下,看看會發生什麼?

Error:Execution failed for task ':app:compileDebugNdk'.
> Error: NDK integration is deprecated in the current plugin.  Consider trying the new experimental plugin.  For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental.  Set "android.useDeprecatedNdk=true" in gradle.properties to continue using the current NDK integration.

這是什麼鬼?他讓我們設置“android.useDeprecatedNdk=true”在“gradle.properties”中。
設置好後,我們再來Run一下,又出錯了?

Error:Execution failed for task ':app:compileDebugNdk'.
> NDK not configured.
Download the NDK from http://developer.android.com/tools/sdk/ndk/.Then add ndk.dir=path/to/ndk in local.properties.
(On Windows, make sure you escape backslashes, e.g. C:\\ndk rather than C:\ndk)

這個是“NDK not configured“,NDK沒有配置,這時,如果我們還沒有下載NDK,我們可以到下列地址下載NDK:

Android NDK
http://dl.google.com/android/ndk/android-ndk32-r10-windows-x86.zip
http://dl.google.com/android/ndk/android-ndk32-r10-windows-x86_64.zip
http://dl.google.com/android/ndk/android-ndk32-r10-darwin-x86.tar.bz2
http://dl.google.com/android/ndk/android-ndk32-r10-darwin-x86_64.tar.bz2
http://dl.google.com/android/ndk/android-ndk32-r10-linux-x86.tar.bz2
http://dl.google.com/android/ndk/android-ndk32-r10-linux-x86_64.tar.bz2
http://dl.google.com/android/ndk/android-ndk64-r10-windows-x86.zip
http://dl.google.com/android/ndk/android-ndk64-r10-windows-x86_64.zip
http://dl.google.com/android/ndk/android-ndk64-r10-darwin-x86.tar.bz2
http://dl.google.com/android/ndk/android-ndk64-r10-darwin-x86_64.tar.bz2
http://dl.google.com/android/ndk/android-ndk64-r10-linux-x86.tar.bz2
http://dl.google.com/android/ndk/android-ndk64-r10-linux-x86_64.tar.bz2
http://dl.google.com/android/ndk/android-ndk-r10-cxx-stl-libs-with-debug-info.zip

在下載完對應我們機型的ndk後,雙擊運行會生成ndk文件文件夾,然後在local.properties文件中配置ndk的路徑爲:”ndk.dir=E\:\\zycSDK\\android-ndk-r10d”(我的ndk路徑爲:E:\zycSDK\android-ndk-r10d)。
接下來,我們需要在app\build.gradle文件夾中加入一些配置:

 defaultConfig {
            ndk {
                moduleName "DCHello"
                ldLibs "log"
                abiFilters "armeabi", "armeabi-v7a", "x86"
            }
            applicationId "com.example.jnisimpledemo"
            minSdkVersion 14
            targetSdkVersion 23
            versionCode 1
            versionName "1.0"
        }

其中,”DCHello“就是生成的”so“庫名稱;ldLibs 配置爲允許日誌打印;abiFilters 指定了三種abi的so庫。
我們再執行RebuildProject再次編譯,發現又出錯了?

make.exe: *** No rule to make target `C:\Users\Administrator\AndroidStudioProjects\JNISimpleDemo\app\build\intermediates\ndk\debug\obj/local/armeabi-v7a/objs/DCHello/C_\Users\Administrator\AndroidStudioProjects\JNISimpleDemo\app\src\main\jni', needed by `C:\Users\Administrator\AndroidStudioProjects\JNISimpleDemo\app\build\intermediates\ndk\debug\obj/local/armeabi-v7a/objs/DCHello/C_\Users\Administrator\AndroidStudioProjects\JNISimpleDemo\app\src\main\jni\sayHello.o'.  Stop.
Error:Execution failed for task ':app:compileDebugNdk'.
> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'E:\zycSDK\android-ndk-r10d\ndk-build.cmd'' finished with non-zero exit value 2

從網上查了資料發現在Sodino這裏有解釋,解決方法爲“在Windows下NDK一個bug,當僅僅編譯一個文件時會出現此問題,解決方法就是再往jni文件夾加入一個空util.c文件即可。”
那麼,我們再次編譯吧,發現生成了我們想要的so庫!
看這裏生成的so庫文件
終於看到了勝利的曙光!
ok,我們來運行,看效果:
來自c/c++的問候

java調用C/C++代碼實現

在代碼實現這一功能上,我們通過一個計算加運算的小例子來練習一下。
首先看一下效果:
加運算
很簡單的一個加法運算,我們先來看native方法:

/**
 * Copyright © 2015 Zyc. All rights reserved.
 *
 * @Project: DCJNIAdd
 * @Package: com.example.dcjniadd.view
 * @author: zyc
 * @date: 2015年12月22日10:42
 * @version: V1.0
 */

public class NativeAddM {
    private static final String libSoName = "NativeAddM";
    /**
     * 載入JNI生成的so庫文件
     */
    static {
        System.loadLibrary(libSoName);
    }
    /**
     * 該方法爲native方法.
     * <p/>
     * 實現加法功能
     *
     * @param x 加數
     * @param y 加數
     * @return x+y 的結果
     */
    public native int add(int x, int y);
}

然後在jni文件夾中建了main.c,Operate.c和Operate.h文件,代碼分別如下:

#include <string.h>
#include <android/log.h>
#include <jni.h>
#include "Operate.h"

/**
 *  Java 中 聲明的native add 方法的實現
 *
 *      jint x  參數X
 *      jint y  參數Y
 */
jint Java_com_example_dcjniadd_view_NativeAddM_add(JNIEnv *env, jobject thiz,
                                                   jint x, jint y) {
    return add(x,y);
}
#include "Operate.h"

/**
 * C 實現的 加法
 */
int add(int x, int y) {
    return x+y;
}

#include <string.h>
#include <jni.h>

int add(int x, int y);

這樣再加上java端的佈局就可以實現一個加運算的應用了。(在最後面提供源代碼下載)

c/c++調用java代碼的實現

先看效果:
c調java
首先拿出核心代碼實現(源碼後面有下載):

#include "Provider.h"
#include <android/log.h>

extern JNIEnv *jniEnv;

jclass TestProvider;
jobject mTestProvider;
jmethodID getTime;
jmethodID sayHello;

int GetProviderInstance(jclass obj_class);

/**
 * 初始化 類、對象、方法
 */
int InitProvider() {
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "InitProvider Begin  1");
    if (jniEnv == NULL) {
        return 0;
    }

    TestProvider = (*jniEnv)->FindClass(jniEnv, "com/example/ccalljavademo/view/TestProvider");
    if (TestProvider == NULL) {
        return -1;
    }
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "InitProvider Begin  2 ok");

    if (GetProviderInstance(TestProvider) != 1) {
        (*jniEnv)->DeleteLocalRef(jniEnv, TestProvider);
        return -1;
    }
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "InitProvider Begin  3 ok");

    getTime = (*jniEnv)->GetStaticMethodID(jniEnv, TestProvider, "getTime", "()Ljava/lang/String;");
    if (getTime == NULL) {
        (*jniEnv)->DeleteLocalRef(jniEnv, TestProvider);
        (*jniEnv)->DeleteLocalRef(jniEnv, mTestProvider);
        return -2;
    }
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "InitProvider Begin  4 ok");

    sayHello = (*jniEnv)->GetMethodID(jniEnv, TestProvider, "sayHello", "(Ljava/lang/String;)V");
    if (sayHello == NULL) {
        (*jniEnv)->DeleteLocalRef(jniEnv, TestProvider);
        (*jniEnv)->DeleteLocalRef(jniEnv, mTestProvider);
        (*jniEnv)->DeleteLocalRef(jniEnv, getTime);
        return -3;
    }
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "InitProvider Begin  5 ok");
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "InitProvider Begin  6");
    return 1;
}

int GetProviderInstance(jclass obj_class) {

    if (obj_class == NULL) {
        return 0;
    }

    jmethodID construction_id = (*jniEnv)->GetMethodID(jniEnv, obj_class,
                                                       "<init>", "()V");

    if (construction_id == 0) {
        return -1;
    }

    mTestProvider = (*jniEnv)->NewObject(jniEnv, obj_class,
                                         construction_id);

    if (mTestProvider == NULL) {
        return -2;
    }

    return 1;
}

/**
 * 獲取時間 ---- 調用 Java 方法
 */
void GetTime() {
    int result = InitProvider();
    if (result != 1) {
        return;
    }
    jstring jstr = NULL;
    char *cstr = NULL;
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "GetTime Begin");
    jstr = (*jniEnv)->CallStaticObjectMethod(jniEnv, TestProvider, getTime);
    cstr = (char *) (*jniEnv)->GetStringUTFChars(jniEnv, jstr, 0);
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "Success Get Time from Java , Value = %s",
                        cstr);
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "GetTime End");

    (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
    (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
}

/**
 * SayHello ---- 調用 Java 方法
 */
void SayHello() {
        int result = InitProvider();
        if (result != 1) {
            return;
        }
    jstring jstrMSG = NULL;
    jstrMSG = (*jniEnv)->NewStringUTF(jniEnv, "Hi,I'm From C");
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "SayHello Begin");
    (*jniEnv)->CallVoidMethod(jniEnv, mTestProvider, sayHello, jstrMSG);
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "SayHello End");

    (*jniEnv)->DeleteLocalRef(jniEnv, jstrMSG);
}

來看需要學習的知識:

1.TestProvider = (*jniEnv)->FindClass(jniEnv, “com/example/ccalljavademo/view/TestProvider”); 找java中位於“com.example.ccalljavademo.view”下的TestProvider 類;
2.
得到靜態方法id:
getTime = (*jniEnv)->GetStaticMethodID(jniEnv, TestProvider, “getTime”, “()Ljava/lang/String;”);
得到非靜態方法id:
sayHello = (*jniEnv)->GetMethodID(jniEnv, TestProvider, “sayHello”, “(Ljava/lang/String;)V”);
3.創建新的對象:
jmethodID construction_id = (*jniEnv)->GetMethodID(jniEnv, obj_class, “”, “()V”);
mTestProvider = (*jniEnv)->NewObject(jniEnv, obj_class, construction_id);
4.C調用java中的方法:
jstring jstr = NULL;
char *cstr = NULL;
調用靜態的方法:
jstr = (*jniEnv)->CallStaticObjectMethod(jniEnv, TestProvider, getTime);
cstr = (char *) (*jniEnv)->GetStringUTFChars(jniEnv, jstr, 0);
調用非靜態的方法:
jstring jstrMSG = NULL;
jstrMSG = (*jniEnv)->NewStringUTF(jniEnv, “Hi,I’m From C”);
(*jniEnv)->CallVoidMethod(jniEnv, mTestProvider, sayHello, jstrMSG);


ok,可以看出c/c++調用java寫的不是很好,並且在學習中也借鑑了很多大神的資料。我正在努力中,會一直更新的,最後附上源碼。
點擊下載

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