Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中關於JNI 的學習(四)簡單的例子,溫故而知新

Android中關於JNI 的學習(四)簡單的例子,溫故而知新

編輯:關於Android編程

在第零篇文章簡單地介紹了JNI編程的模式之後,後面兩三篇文章,我們又針對JNI中的一些概念做了一些簡單的介紹,也不知道我到底說的清楚沒有,但相信很多童鞋跟我一樣,在剛開始學習一個東西的時候,入門最好的方式就是一個現成的例子來參考,慢慢研究,再學習概念,再回過來研究代碼,加深印象,從而開始慢慢掌握。

今天我們就再來做一個小Demo,這個例子會比前面稍微復雜一點,但是如果閱讀過前面幾篇文章的話,理解起來也還是很簡單的。很多東西就是這樣,未知的時候很可怕,理解了就很簡單了。

1)我們首先定義一個Java類,裡面包含幾個native方法,如下:

public class ParamTransferTest {

	public static int testval = 1;
	
	public native void changeTestVal();
	
	public native int add(int x, int y);
	
	public native String addTail(String tail);
	
	public native int[] changeArray(int[] arr);
}

2)利用javah工具生成對應的頭文件,如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_lms_jni_ParamTransferTest */

#ifndef _Included_com_lms_jni_ParamTransferTest
#define _Included_com_lms_jni_ParamTransferTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_lms_jni_ParamTransferTest
 * Method:    changeTestVal
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal
  (JNIEnv *, jobject);

/*
 * Class:     com_lms_jni_ParamTransferTest
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_lms_jni_ParamTransferTest_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_lms_jni_ParamTransferTest
 * Method:    addTail
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_lms_jni_ParamTransferTest_addTail
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_lms_jni_ParamTransferTest
 * Method:    changeArray
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_com_lms_jni_ParamTransferTest_changeArray
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

上面就生成了對應的方法,在上面我們可以看到前面文章所介紹過的方法名稱以Java開頭,方法簽名等信息,對吧。

3)編寫 C 文件,如下:

#include 
#include 
#include "com_lms_jni_ParamTransferTest.h"
#include 
#include 
#include 

#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

char* Jstring2CStr(JNIEnv * env, jstring str){
	char * rtn = NULL;

	jclass clsstring = (*env)->FindClass(env, "java/lang/String");//通過FindClass方法獲得Java的String類
	jstring strencode = (*env)->NewStringUTF(env, "UTF-8");//調用NewStringUTF方法,獲得"UTF-8"字符串,作為編碼
	jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");//獲取String類的getBytes方法
	jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, str, mid, strencode);//調用String類的getBytes方法

	jsize alen = (*env)->GetArrayLength(env, barr);//獲得數組長度
	jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);//獲得數組的首地址,C/C++中數組的首元素就是一個指針
	if(alen > 0){
		rtn = (char*) malloc(alen + 1);
		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}//上面這一步是將數組的值復制到一個char*的數組中,也就是C/C++的char數組,因為C/C++沒有字符串概念,最後以0結尾。
	(*env)->ReleaseByteArrayElements(env, barr, ba, 0);//釋放內存
	return rtn;
}

/*
 * Class:     com_lms_jni_ParamTransferTest
 * Method:    changeTestVal
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_lms_jni_ParamTransferTest_changeTestVal
  (JNIEnv * env, jobject obj){
	jclass clazz = (*env)->GetObjectClass(env,obj);//獲得obj對應的類,也就是ParamTransferTest
	jint val = (*env)->GetStaticIntField(env, clazz,
						(*env)->GetStaticFieldID(env, clazz,"testval","I"));//獲取字段testval的值
	LOGI("before change testval = %d", val);//添加Log信息
	val = val + 1;
	LOGI("after change testval = %d", val);
	(*env)->SetStaticIntField(env, clazz,(*env)->GetStaticFieldID(env, clazz,"testval","I"),val);//設置字段testval的值
}

/*
 * Class:     com_lms_jni_ParamTransferTest
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_lms_jni_ParamTransferTest_add
  (JNIEnv * env, jobject obj, jint x, jint y){
	LOGI("x = %d", x);
	LOGI("y = %d", y);
	return x + y;       //返回參數x和y的和
}

/*
 * Class:     com_lms_jni_ParamTransferTest
 * Method:    addTail
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_lms_jni_ParamTransferTest_addTail
  (JNIEnv * env, jobject obj, jstring str){

	char* p = Jstring2CStr(env,str);//將Java中的string轉化為C/C++中的char數組
	LOGI("str = %s", p);
	char* newStr = " Tail ";
	return (*env)->NewStringUTF(env, strcat(p, newStr));//調用strcat函數連接兩個char數組,將通過NewStringUTF返回。
}

/*
 * Class:     com_lms_jni_ParamTransferTest
 * Method:    changeArray
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_com_lms_jni_ParamTransferTest_changeArray
  (JNIEnv * env, jobject obj, jintArray ja){
	int len = (*env)->GetArrayLength(env, ja);/獲取數組長度
	LOGI("len = %d", len);
	LOGI("address = %#x", &ja);
	
	jint* arr = (*env)->GetIntArrayElements(env, ja, 0);//將數組中的所有元素存入以jint*為首地址的數組中(數組的首地址就是數組名)

	int i = 0;
	for(;i < len; i++){
		LOGI("arr[%d] = %d", i, *(arr + i));
		*(arr + i) += 10;
	}
    //數組中每個元素的值加上10return ja;由於GetIntArrayElements的最後一個參數是0,即表明獲取的數組是不復制的,即它們操作同一塊內存,所以返回哪個數組都是ok的
	return ja;
}


對應這四個方法,在上面都添加了一些注釋,大家看著注釋,自己研究一下邏輯,也就清楚了這幾個方法實現的功能是什麼,很簡單的。

4)編寫Android.mk文件,如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := com_lms_jni_HwDemo

LOCAL_SRC_FILES := \
HwDemo.c \
JniTest.c \
ParamTransferTest.c

LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

在這裡有一點的注意的是,編譯多個文件的時候,要利用反斜槓 "\"來進行換行,區分不同的C/C++文件。

而這裡LOCAL_LDLIBS是JNI中運用log所需要添加的動態包,下一篇文章會講。

5)編寫好Android.mk文件之後,就利用ndk-build文件進行編譯,文件結構如下:

\


在jni目錄下運行ndk-build,如下:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PGltZyBzcmM9"/uploadfile/Collfiles/20140523/20140523091221129.jpg" alt="\">

到這裡,關於JNI層的實現就結事了,下面就是Java端的事情。

6)Activity中調用,如下:

public class HwDemo extends Activity {

	static {
		System.loadLibrary("com_lms_jni_HwDemo");//加載動態包,名稱就是Android.mk中的Module名稱。
	}
	
	public native String printHello();	

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);		
		setContentView(R.layout.main);
		...
		
		TextView tvAdd = (TextView)findViewById(R.id.tvAdd);
		TextView tvString = (TextView)findViewById(R.id.tvString);
		TextView tvArray = (TextView)findViewById(R.id.tvArray);
		
		TextView tvChangeTestVal = (TextView)findViewById(R.id.tvChangeTestVal);		
		ParamTransferTest ptt = new ParamTransferTest();
		//調用changeTestVa()方法
		ptt.changeTestVal();		
		tvChangeTestVal.setText("" + ptt.testval);
		
		//調用add方法
		int sum = ptt.add(1, 2);
		tvAdd.setText(String.valueOf(sum));
		//調用addTail方法
		tvString.setText(ptt.addTail("lms"));
		//調用changeArray方法
		int[] newArr = ptt.changeArray(new int[]{1,2});
		
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		sb.append(newArr[0]).append(",").append(newArr[1]);
		sb.append("]");
		tvArray.setText(sb.toString());
				
	}

}

7)結果顯示:

系統核心服務,或者啟動Zygote虛擬機的時候,用到了大量的JNI層面的框架,大家如果對JNI熟悉了解了,再去了解這些框架的東西,會有很大的幫助的。

結束。

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