簡單實現Android NDK編譯jni呼叫動態庫開發

郵筒守護者發表於2016-05-20

首先我們知道再次熟悉一下NDK的基本知識,Android平臺從誕生起,就已經支援C、C++開發。眾所周知,Android的SDK基於Java實現,這意味著基於Android SDK進行發的第三方應用都必須使用Java語言。但這並不等同於“第三方應用只能使用Java”,谷歌改良了ndk的開發流程,對於Windows環境下NDK的開發,Google在釋出之初就提出過,開發支援jni程式設計方式,也就意味著我們在開發的過程中可以呼叫自己的第三方動態庫了,不管是用c寫的還是c++寫的都行,那就有人要問的,為什麼要多此一舉,原因很簡單,越接近底層的程式碼,其耦合性機越強,執行效率也就更高,方便我們處理一下相對比較繁瑣的邏輯功能,
本部落格將的是NDK版本是人r10e,目前最新的NDK版本貌似是r11了,至於r7之前的版本需要安裝Cvgwin才能使用,既然有更高階的,那就何必講情懷呢不是,因為高版本的NDK提供了一個ndk-build.cmd的指令碼,可以直接利用這個指令碼編譯,而不需要使用Cygwin了。只需要為Eclipse Android工程新增一個Builders,而為Eclipse配置的builder,

其實就是在執行Cygwin,然後傳遞ndk-build作為引數,這樣就能讓Eclipse自動編譯NDK了,廢話不多說開始吧!

工欲善其事必先利其器,首先得配置好NDK開發環境:

1.我自己下載安裝NDK-r10e版本,現在可以下載更高階的r11了,下面提供下載地址:http://developer.android.com/sdk/ndk/index.html

2開啟我們的Eclipse(本人對Studio開發不是很熟悉,目前還在堅持著),新建一個Android工程,在工程目錄testNdk下新建jni資料夾,該資料夾就用來儲存NDK需要編譯的檔案程式碼比如.mk檔案

1.1 點選新建專案右鍵Properties->Builders->New或者Project->Properties->Builders->New,新建一個Builder。

1.2 彈出視窗如下,在彈出的【Choose configuration type】對話方塊,選擇【Program】,點選【OK】:

1.3 在彈出的【Edit Configuration】對話方塊中,配置選項卡【Main】。 在“Name“中輸入新builders的名稱,名字可以隨意取,任性點就好。接下來就是在“Location”中輸入nkd-build.cmd的路徑,是不是對這個檔案很陌生,這個檔案的來源是之前下載的NDKr10解壓後的中的檔案可以自己設定路徑也可以點選Browser File System 找到解壓後的檔案路徑,這個建議放在根目錄下面,路徑不能有空格和中文,否則會編譯不通過,同樣的選在Working Diretcoty時可以選擇Worksapce 的專案位置,也可以點選Browse Workspace來選擇專案TestNDK的絕對路徑,如下圖:

1.4繼續在這個【Edit Configuration】對話方塊中,配置選項卡【Refresh】。

     如下圖勾選“Refresh resources upon completion”, 

    勾選“The entire workspace”, 

    勾選“Recuresively include sub-folders”。

1.5 繼續在【Edit Configuration】對話方塊中,配置選項卡【Build options】。 
勾選“After a “Clean””,(勾選這個操作後,如果你想編譯ndk的時候,只需要clean一下專案 就開始交叉編譯) 
勾選“During manual builds”, 
勾選“During auto builds”, 
勾選“Specify working set of relevant resources”。如圖下圖

注意這個時候上面會提示有錯誤,那是因為你沒有選擇要關聯的工程,此時點選點選“Specify Resources…”勾選testNdk工程中新建的“jni“目錄,點選”finish“。 點選“OK“,完成配置。 如圖

到此點選finish、回去點選OK就算是配置完成了,接下來就開始試試行還是不行咯。

1.第一步就是testNdk工程中新建一個JniTest.java的class類(為了呼叫C/C++程式碼),其內容如下:
package com.ndk.test;
public class JniTest{
    static public native String AddStr(String strA, String strB);
    static public native int AddInt(int a, int b);
}
2.生成 .h 的c++標頭檔案
(1)用cmd命令定位到Jni
Test.java所在目錄,輸入“javac JniTest.java“後回車,生成JniTest.class檔案
(如果是用的Eclipse建的工程,在
testNdk\bin\classes\com\ndk\test目錄下就已經有JniTest.class檔案了)。


(2)將Jni
Test.class拷貝到testNdk\bin\classes\com\ndk\test目錄,將cmd命令定位到testNdk\bin\classes目錄,
輸入”javah com.ndk.test.Jni
Test.class“後回車,在testNdk\bin\classes目錄下就生成了C++標頭檔案com_ndk_test_JniTest.h
com_ndk_test_Jni
Test.h的檔案內容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ndk_test_Jni
Test */
#ifndef _Included_com_ndk_test_JniTest
#define _Included_com_ndk_test_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_ndk_test_JniTest
 * Method:    AddStr
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_ndk_test_JniTest_AddStr
  (JNIEnv *, jclass, jstring, jstring);
/*
 * Class:     com_ndk_test_JniTest
 * Method:    AddInt
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_ndk_test_JniTest_AddInt
  (JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
3.在jni目錄下新建一個Android.mk檔案,其內容如下
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE :=
TestNdk
LOCAL_SRC_FILES := com_
ndk_test_JniTest.c
include $(BUILD_SHARED_LIBRARY)
4. 將剛剛手動生成的com_ndk_test_JniTest.h拷貝到TestNdk工程的jni目錄下,
然後新建一個com_ndk_test_JniTest.c檔案完成標頭檔案中函式的實現,其內容如下
(本來想寫兩個方法的,現在只講解第一個方法,返回一個字串“HelloWorld from JNI ”,
另一個方法是一個a+b的運算,方法寫到這裡,感興趣的可以自己去研究):
com_ndk_test_JniClient.c
#include "com_ndk_test_JniTest.h"
#include <stdlib.h>
#include <stdio.h>


#ifdef __cplusplus   
extern "C"  
{   
#endif  
/*
 * Class:     com_ndk_test_JniTest
 * Method:    AddStr
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_ndk_test_JniTest_AddStr
  (JNIEnv *env, jclass arg, jstring instringA, jstring instringB)
{
    jstring str = (*env)->NewStringUTF(env, "HelloWorld from JNI !");
    return str;       
}


/*
* Class:     com_ndk_test_JniTest
* Method:    AddInt
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_ndk_test_JniTest_AddInt
  (JNIEnv *env, jclass arg, jint a, jint b)
{
    return a + b;
}


#ifdef __cplusplus   
}   
#endif
此刻,當編輯com_ndk_test_JniTest.c並儲存後,project下的—clean  一下工程,
就可以看到TestNkd工程下的obj/local/armeabi目錄下將自動生成libTestNdk.so庫。
5.在TestNdkActivity.java中完成對JniClient.java中函式的呼叫(首先靜態載入動態連結so庫)
package com.ndk.test;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;


public class TestNdkActivity extends Activity {
    static {
        System.loadLibrary("TestNdk");
    }
    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.main);
        //第一個方法傳入的兩個引數沒有做操作,直接返回hello jni,不用管
        String str = JniClient.AddStr("test", "test");
        //第二個方法暫時不實現
        //  int iSum = JniClient.AddInt(5, 2);        
       // String strSum = "5 + 7 = " + iSum;
        
        TextView tv1 = new TextView(this);
        tv1.setText(str);
        setContentView(tv1);
    }
}


相關文章