Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> JNI(一) - Android Studio簡單開發流程

JNI(一) - Android Studio簡單開發流程

編輯:關於Android編程

簡介

JNI:Java Native Interface(Java 本地接口),它是為了方便Java調用C、C++等本地代碼所封裝的一層接口。

NDK:Native Development Kit(本地開發工具包),通過NDK可以在Android中更加方便的通過JNI來訪問本地代碼。

基於NDK的JNI開發流程

1.配置NDK開發環境

打開AS的SDK Manager,安裝NDK插件:

打開AS的SDK Manager,安裝NDK插件:

001.jpg

整個NDK比較大,解壓縮完2個G,自動安裝到配置的sdk目錄下:

002.jpg

安裝完畢後,點開structure,配置NDK的路徑:

003.jpg

配置NDK的環境變量:

004.jpg

\

驗證是否配置成功:

\

在命令行輸入ndk-build,如果顯示以上內容,表示成功。

2.創建Android項目

項目名稱:JNITest

包名:com.dgk.jnitest

實現功能:界面有兩個按鈕,點擊Get從本地方法中獲取一個字符串,並toast出來;點擊Set向本地方法傳遞一個字符串,打印到Logcat。

2.1 寫Android界面和基本邏輯,並聲明兩個本地方法。

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String tag = "【MainActivity】";

    private Button btn_get;
    private Button btn_set;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn_get = (Button) findViewById(R.id.btn_get);
        btn_set = (Button) findViewById(R.id.btn_set);

        btn_get.setOnClickListener(this);
        btn_set.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.btn_get:
                Log.i(tag, "點擊Get按鈕");
                Toast.makeText(this, getStringFromJNI(), Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn_set:
                Log.i(tag, "點擊Set按鈕");
                setStringToJNI("Hello C! 我是一只來自Java世界的Cat,喵~~~");
                break;
        }
    }

    /**
     * 聲明get方法
     *  - 作用是從本地方法返回一個String
     * @return 返回一個字符串
     */
    public native String getStringFromJNI();

    /**
     * 聲明set方法
     *  - 作用是向本地方法傳遞一個String
     */
    public native void setStringToJNI(String str);

    /**
     * 加載本地代碼庫
     * - 在應用啟動的時候加載名為"libjni-test.so"的代碼庫,該庫在安裝Apk的時候就已經
     * 被包管理器拆包放到了/data/data/包名/lib/目錄下了。
     */
    static {
        System.loadLibrary("jni-test");
    }
}

activity_main.xml


2.2 寫C代碼

選中main右鍵選擇New->Folder->JNI Folder,會在main路徑下創建一個jni的文件夾,用於存放本地源代碼,src\main\jni這個是AS默認的jni源代碼存放路徑,也可以自己手動創建。

創建Hello.c

#include 
#include 
#include 
#include 

#define  LOG_TAG    "【C_LOG】"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

JNIEXPORT jstring JNICALL Java_com_dgk_jnitest_MainActivity_getStringFromJNI (JNIEnv *env, jobject thiz)
{
    LOGI("調用 C getStringFromJNI() 方法\n");
    char* str = "Hello Java! 我是一只來自C世界的Dog,汪!!!";
    return (*env)->NewStringUTF(env, str);
}

JNIEXPORT void JNICALL Java_com_dgk_jnitest_MainActivity_setStringToJNI (JNIEnv* env, jobject thiz, jstring str){
    LOGI("調用 C setStringFromJNI() 方法\n");
    char* string = (char*)(*env)->GetStringUTFChars(env, str, NULL);
    LOGI("%s\n", string);
    (*env)->ReleaseStringUTFChars(env, str, string);
}

C代碼的我的理解,可以先大致看一下:

#include 
#include 
#include 
#include 

/*
    在C語言中標准輸出的方法是printf,但是打印出來的內容在logcat看不到,需要使用
    __android_log_print()方法打印log,才能在logcat看到,由於該方法名比較長,我們在
    這裡需要定義宏,使得在C語言中能夠向Android一樣打印log。
    注意:該方法還需要在gradle中聲明ldLibs "log",詳見build.gradle
*/
#define  LOG_TAG    "【C_LOG】"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

/*
    返回類型 Java_包名_類名_方法名
    - 返回類型:jstring,即java格式的String
    - 參數:
        可以在jni.h頭文件中查找到各個自定義變量的原類型。
        必帶的兩個參數:
            - JNIEnv:是結構體JNINativeInterface的一級指針,即JNINativeInterface*,
                    結構體JNINativeInterface:接口函數指針表,該表就是用來Java和C語言之間進行交互的,
                    包含著Java變量和C變量之間的對應關系,可以用於變量之間的轉換。
              JNIEnv* env:是JNIEnv的一級指針,
                    是結構體JNINativeInterface的二級指針,即JNINativeInterface**
            - jobject thiz:誰調用了這個本地函數,那麼這個thiz就是指的哪個對象,本項目中是MainActicity

*/
JNIEXPORT jstring JNICALL Java_com_dgk_jnitest_MainActivity_getStringFromJNI (JNIEnv *env, jobject thiz)
{

    LOGI("調用 C getStringFromJNI() 方法\n");

    /*
        char* 相當與C語言中的字符串
        char* 指的是字符串str的指針,指向的是該str的內存區域,
            但是在C語言中操作字符串可以直接使用一級指針,來獲取字符串的各個元素。
    */
    char* str = "Hello Java! 我是一只來自C世界的Dog,汪!!!";

    /*
        通過在jni.h的結構體JNINativeInterface中查找jstring,可以找到將C語言的字符串轉換成
        Java字符串的代碼:
                const struct JNINativeInterface* functions;
                jstring NewStringUTF(const char* bytes){
                    return functions->NewStringUTF(this, bytes);
                }
            即:JNINativeInterface*->NewStringUTF(*env, str)
            即:(env*)->NewStringUTF(*env, str)
            將str轉換並保存在結構體中,然後使用間接引用運算符->來獲得這個jstring成員。

        在這裡,結構體是JNINativeInterface,他的一級指針是JNIEnv,即*env(因為env又是JNIEnv的一級指針)。
        所以(*env)->NewStringUTF(env, str)就相當於JNINativeInterface.jstring,表示該結構體內的
        jstring格式的變量。
    */
    return (*env)->NewStringUTF(env, str);
}

JNIEXPORT void JNICALL Java_com_dgk_jnitest_MainActivity_setStringToJNI (JNIEnv* env, jobject thiz, jstring str){

    LOGI("調用 C setStringFromJNI() 方法\n");

    // 將收到的jstring轉換成UTF-8格式的C字符串
    char* string = (char*)(*env)->GetStringUTFChars(env, str, NULL);
    LOGI("%s\n", string);

    // 顯示釋放轉換成UTf-8的string空間,如果不顯示調用,JVM會一直保存該對象,不回收,容易導致內存溢出
    (*env)->ReleaseStringUTFChars(env, str, string);
}

2.3 配置gradle

\

配置完gradle後如果直接同步,會提示:

\

如果點擊第一行,會轉到google開發網站,顯示一個實驗性的gradle,可以用來集成NDK,在本篇不予介紹。點擊第二行,或者直接在gradle.properties中貼上代碼,表示使用當前過時的插件:

android.useDeprecatedNdk=true

3.編譯生成.so庫

直接Build->Make Project,會自動生成.so庫,保存路徑為\app\build\intermediates\ndk\debug\lib

\

將生成的lib包下的需要的.so庫copy到main/jniLibs目錄中即可:

\

該文件夾為AS默認的jni庫的目錄,可以在gradle中修改。

4.運行程序查看結果

運行手機:魅族pro5 Android 5.1

點擊GET:

\

點擊SET:

\

到現在為止,已經完成了最簡單的JNI開發流程。

遇到問題

1.自動生成JNI.h頭文件

在這裡我們的C代碼的方法名和參數是自己寫的,而實際上完全可以讓jdk來幫我們生成,流程是:

編譯java代碼生成.class文件—>使用javah命令生成C語言的頭文件—>將.h文件中的JNI方法聲明復制到C文件中,並完善方法的方法體。這樣的話,可以加速我們的開發流程,同時避免寫錯方法名,但是在這個過程中由於對自己的開發環境還有命令了解不夠,經常會出問題,所以小編在簡潔流程中並未寫出。

如果對C語言不了解,就需要自動生成頭文件了,在這裡給出小編自己的流程,一行代碼搞定:

在Make Project之後,在Terminal中輸入命令(當前目錄為應用的根目錄)

\

javah -d . -cp android.jar的地址;編譯生成的.class目錄  全類名

格式:
-d . 表示生成文件存放在當前目錄
-cp class文件的加載根路徑,在這裡需要加載兩個類,一個是android的基礎類庫,要不然jdk本身識別不了android的東西,比如Activity\Log等;第二個是MainActivity編譯生成的路徑 + 全類名,注意中間有個空格,而且不能寫全地址,不識別。
運行完該命令後會默認在當前目錄(即程序的根目錄)下生成一個文件:com_dgk_jnitest_MainActivity.h,該文件就是JNI的頭文件,裡面定義好了JNI的本地方法聲明,可以直接復制來用。

\

對於這一步,網上的命令七花八門,感覺好多復制過來直接用然後就發表了,很多坑,令人很頭疼,搞了一天,煩躁!其實主要的問題就是,Android Studio對JNI的兼容性剛開始不是很好,後來又更新的版本比較多,再加上如果對gradle不熟悉的話,就會造成寫起來很難受的感覺。

還好,當真正的把整個流程給串起來以後就會感覺還是很清晰的,不過還是希望成熟的插件能夠將整個流程串起來~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved