天天看點

Android NDK開發測試

Android NDK開發 測試

前言:Android開發小白,由于項目裡用到NDK開發,在看代碼的時候遇到了就稍微學習了一下NDK開發的一些簡單知識,以後會在這塊繼續做補充…

幾個概念:

JNI( Java Native Interface,Java本地接口 ),它提供了若幹的API實作了Java和其他語言的通信(主要是C&C++)。

.so(shared object,共享對象),Linux系統中的動态庫,類似于Windows系統中的DLL。.so有時被直接調用,有時會參與到編譯中。Android由Linux核心發展而來,是以在Android系統中也使用.so。

Android NDK(Android Native Development Kit),是Google提供的一系列的工具,簡化通過JNI将C/C++動态庫編譯為.so庫的過程。NDK內建了交叉編譯器,并提供了相應的.mk檔案隔離CPU、平台、ABI等差異,開發者隻需要簡單修改mk檔案,執行編譯腳本就可以建立.so。

安裝:

下載下傳位址:https://developer.android.google.cn/ndk/downloads/index.html

安卓過程可以從網上看,問題不大,主要是要配好環境變量,把NDK安裝的根目錄添加到系統變量上就可以;

實驗

由于項目需要在java層在一些操作後将資料傳輸到C層,在C層進行計算,之後将資料在傳輸到java層,主要是想看看資料的傳輸過程,是以進行如下測試。測試主要是為了觀察C層資料到java層的傳輸過程(C層資料傳輸到java層,其實就是在C層中調用java層代碼,我之前居然一直沒有想明白…-_-||);測試方法是在C層定義一些資料,将其傳入到java層的方法中計算;

編譯環境:Eclipse + jdk1.8.0_162 + android-ndk-r10c

建立項目:NdkCallback,在布局 activity_main 中添加兩個 Button,code如下:

<Button 
    android:id="@+id/callback_null"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="c回調java層空方法" />

<Button 
    android:id="@+id/callback_two_int"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="c回調java中帶連個int參數的方法" />
           

在活動 MainActivity.java 中添加兩個按鈕的監聽器,并添加 Log 資訊,用于驗證試驗是否成功,code如下:

public class MainActivity extends Activity implements OnClickListener{

    static {
        //加載動态庫 .so
        System.loadLibrary("Hello");
    }

    private Button buttonNull, buttonTwo;
    private DataProvider provider;
    private String Tag2 = "MainActivity";


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

        buttonNull = (Button)findViewById(R.id.callback_null);
        buttonTwo = (Button)findViewById(R.id.callback_two_int);

        buttonNull.setOnClickListener(this);
        buttonTwo.setOnClickListener(this);

        provider = new DataProvider();
    }

    @Override
    public void onClick(View v){

        switch(v.getId()){
        case R.id.callback_null:
            provider.callMethod1();
            //Log.d(Tag2, "test");
            break;
        case R.id.callback_two_int:
            provider.callMethod2();
            break;
        default:
            break;
        }
    }
}
           

其中,一開始調用了動态庫,這個過程後續說明;

由于要實作 C 層資料傳輸到 java,是以在java層定義方法,用于接收 C 層傳輸的資料并進行一些計算操作;

建立類 DataProvider,在該類中完成計算方法的定義,包括 nullMethod 方法和 add 方法,後面定義了兩個 native 函數,用于實作 C 層調用 java 層方法,code如下:

public class DataProvider {

    private String Tag = "Java";

    public void nullMethod(){

        Log.d(Tag, "hello from java");
    }

    public int add(int a, int b){

        int result = a + b;
        Log.d(Tag, "hello from java_2");
        return result;
    }

    //native method
    public native void callMethod1();
    public native void callMethod2();
}
           

因為在類 DataProvider 中添加的 native 方法,是以我們将這個類編譯成 C 的頭檔案,編譯方法為:在 Dos 視窗中進入 src 目錄,并執行 javah 指令;

Android NDK開發測試

在使用 javah 指令的時候需指定 -encoding utf-8 參數,防止編譯報亂碼錯誤,下面是編譯好的頭檔案:

/* Header for class com_example_ndkcallback_DataProvider */

#ifndef _Included_com_example_ndkcallback_DataProvider
#define _Included_com_example_ndkcallback_DataProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_ndkcallback_DataProvider
 * Method:    callMethod1
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod1
(JNIEnv *, jobject);

/*
 * Class:     com_example_ndkcallback_DataProvider
 * Method:    callMethod2
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod2
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
           

之後開始編寫 C 代碼:

#include<stdio.h>
#include<jni.h>
#include"com_example_ndkcallback_DataProvider.h"
#include<android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod1
  (JNIEnv* env, jobject obj){

    //1、找到java代碼native方法所在的位元組碼檔案
    // jcalss (*FindClass)(JNIEnv*, const char*)
    jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");

    if(clazz == 0){
        LOGD("find class1 erroer!");
        return;
    }

    LOGD("find class1!");

    //2、找到class裡面對應的方法
    jmethodID method1 = (*env)->GetMethodID(env, clazz, "nullMethod", "()V");
    if(method1 == 0){
        LOGD("find method1 errored");
        return;
    }
    LOGD("find method1");

    //3、調用方法
    (*env)->CallVoidMethod(env, obj, method1);
    LOGD("method1 called");
}

JNIEXPORT void JNICALL Java_com_example_ndkcallback_DataProvider_callMethod2
(JNIEnv* env, jobject obj) {

//1、找到java代碼native方法所在的位元組碼檔案
// jcalss (*FindClass)(JNIEnv*, const char*)
    jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");

    if(clazz == 0) {
        LOGD("find class2 erroer!");
        return;
    }

    LOGD("find class2!");

    //2、找到class裡面對應的方法
    jmethodID method2 = (*env)->GetMethodID(env, clazz, "add", "(II)I");
    if(method2 == 0) {
        LOGD("find method2 errored");
        return;
    }
    LOGD("find method2");

    //3、調用方法
    int result = (*env)->CallIntMethod(env, obj, method2, 3, 5);
    LOGD("result in C = %d", result);
}
           

C 代碼的編寫主要可以分為三步:

1、找到 java 代碼 native 方法所在的位元組碼檔案,在 jni.h 中的 JNINativeInterface 中可以找到,即函數:

jclass clazz = (*env)->FindClass(env, "com/example/ndkcallback/DataProvider");
           

2、找到class裡面對應的方法,在jni.h中的JNINativeInterface中可以找到,進而擷取方法id,即函數:

jmethodID method1 = (*env)->GetMethodID(env, clazz, "nullMethod", "()V");
           

3、調用方法,即函數:

int result = (*env)->CallIntMethod(env, obj, method2, 3, 5);
           

之後,需要配置 Android.mk 和 Application.mk

Android.mk

LOCAL_PATH := $(call my-dir)  

include $(CLEAR_VARS)  

LOCAL_MODULE    := Hello  

LOCAL_SRC_FILES := Hello.c  

LOCAL_LDLIBS += -llog  

include $(BUILD_SHARED_LIBRARY) 
           

Application.mk

APP_PLATFORM := android-14

APP_ABI := all  

APP_ABI := armeabi-v7a  
           

接着,編譯 C 代碼

以前,編譯C代碼需要用 Cygwin 這個工具,自從Google更新 ndk7 以後的版本就可以直接用ndk來編譯C代碼,省去了許多麻煩;首先在Dos視窗下進入工程檔案目錄下,進入src目錄,然後輸入編譯指令 ndk-build ,便能夠得到 .so 檔案,如下所示:

Android NDK開發測試

最後,在 Java 層調用 Native 方法來測試結果,即第二段代碼中的片段:

@Override
public void onClick(View v){

    switch(v.getId()){
    case R.id.callback_null:
        provider.callMethod1();
        //Log.d(Tag2, "test");
        break;
    case R.id.callback_two_int:
        provider.callMethod2();
        break;
    default:
        break;
    }
}
           

程式主界面:

Android NDK開發測試

1、C 回調 Native 的空方法:

Android NDK開發測試

2、C 回調 Native 兩個 int 參數的方法:

Android NDK開發測試

可以看出,兩側調用都已經成功!

本文主要參考部落格:

http://blog.csdn.net/allen315410/article/details/41862479

繼續閱讀