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 指令;

在使用 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 檔案,如下所示:
最後,在 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;
}
}
程式主界面:
1、C 回調 Native 的空方法:
2、C 回調 Native 兩個 int 參數的方法:
可以看出,兩側調用都已經成功!
本文主要參考部落格:
http://blog.csdn.net/allen315410/article/details/41862479