摘自:NBG(ndk.beginners.guide)第3章
本节将通过一个小实例,实现java与C,通过JNI来相互传递基本类型(int , String)。例子界面如下:
在界面输入“键”,‘‘值’’,再选择“类型”,“设置键值”即可将java端数据存入本地数组中。通过输入“键”,选择“类型”,再“获取值”,即可从java端将本地数据取回。
原书所述程序结构图如下:
看C与java交界处,可得知将要交互处理的类型为“int”,“String”。java端的本地方法声明在Store.java中,JNI接口实现模块为com_packtpub_Store。
下面以图上源码:
JAVA端:
1.界面StoreActivity.java
package com.packtpub;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
/**
* @author Administrator
* @version 1.0
*/
/*
* 界面,提供输入,取出途径
*/
public class StoreActivity extends Activity {
private EditText mUIKeyEdit, mUIValueEdit;
private Spinner mUITypeSpinner;
private Button mUIGetButton, mUISetButton;
private Store mStore;
private final static String TAG = "NDK";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initComponent();
mStore = new Store();
}
private void initComponent() {
mUIKeyEdit = (EditText) findViewById(R.id.etKey);
mUIValueEdit = (EditText) findViewById(R.id.etValue);
mUITypeSpinner = (Spinner) findViewById(R.id.spinnerType);
mUIGetButton = (Button) findViewById(R.id.btnGet);
mUISetButton = (Button) findViewById(R.id.btnSave);
mUIGetButton.setOnClickListener(getListener);
mUISetButton.setOnClickListener(setListener);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item);
adapter.add("Integer");
adapter.add("String");
mUITypeSpinner.setAdapter(adapter);
}
private OnClickListener getListener = new OnClickListener() {
public void onClick(View v) {
String lKey = mUIKeyEdit.getText().toString();
StoreType lType = StoreType.values()[mUITypeSpinner
.getSelectedItemPosition()];
switch (lType) {
case Integer:
Log.d(TAG, "case Integer");
mUIValueEdit.setText(Integer.toString(mStore
.getInteger(lKey)));
break;
case String:
Log.d(TAG, "case String");
mUIValueEdit.setText(mStore.getString(lKey));
break;
}
}
};
private OnClickListener setListener = new OnClickListener() {
public void onClick(View v) {
String lKey = mUIKeyEdit.getText().toString();
StoreType lType = StoreType.values()[mUITypeSpinner
.getSelectedItemPosition()];
switch (lType) {
case Integer:
mStore.setInteger(lKey,
Integer.valueOf(mUIValueEdit.getText().toString()));
break;
case String:
mStore.setString(lKey, mUIValueEdit.getText().toString());
break;
}
}
};
private void displayError(String pError) {
Toast.makeText(getApplicationContext(), pError, Toast.LENGTH_LONG)
.show();
}
}
2.布局文件: main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="1">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Save or retrieve a value from the store:" />
<LinearLayout
android:layout_height="wrap_content"
android:id="@+id/LinearLayout01"
android:layout_width="match_parent">
<TextView
android:text="Key: "
android:id="@+id/TextView01"
android:layout_height="wrap_content"
android:layout_width="50dp"></TextView>
<EditText
android:id="@+id/etKey"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"></EditText>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="Value: "
android:id="@+id/textView1"
android:layout_width="50dp"
android:layout_height="wrap_content"></TextView>
<EditText
android:id="@+id/etValue"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_weight="1">
<requestFocus></requestFocus>
</EditText>
</LinearLayout>
<LinearLayout
android:layout_height="wrap_content"
android:id="@+id/LinearLayout02"
android:layout_width="match_parent">
<TextView
android:text="Type: "
android:id="@+id/TextView02"
android:layout_height="wrap_content"
android:layout_width="50dp"></TextView>
<Spinner
android:layout_weight="1"
android:layout_height="wrap_content"
android:id="@+id/spinnerType"
android:layout_width="wrap_content"></Spinner>
</LinearLayout>
<RelativeLayout
android:id="@+id/linearLayout2"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<Button
android:text="Set Value"
android:id="@+id/btnSave"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginLeft="120dp"
android:layout_marginRight="20dp"></Button>
<Button
android:text="Get Value"
android:id="@+id/btnGet"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_toRightOf="@+id/btnSave"></Button>
</RelativeLayout>
</LinearLayout>
package com.packtpub;
/**
* @author Administrator
* @version 1.0
*/
/*
* 本类用于加载本地库,定义访问本地代码的接口
*/
public class Store {
static {
System.loadLibrary("store");
}
public native int getInteger(String pKey);
public native void setInteger(String pKey, int pInt);
public native String getString(String pKey);
public native void setString(String pKey, String pString);
}
4.用户可选类: StoreType.java
package com.packtpub;
/**
* @author Administrator
* @version 1.0
*/
/*
* 目前仅是基本类型
*/
public enum StoreType {
Integer, String
}
再C端:
5.本地存储工具类头文件 Store.h
#ifndef _STORE_H_
#define _STORE_H_
#include "jni.h"
#include <stdint.h>
#define STORE_MAX_CAPPCITY 3
//支持类型枚举
typedef enum {
StoreType_Integer, StoreType_String
} StoreType;
//值存储
typedef union {
int32_t mInteger;
char* mString;
} StoreValue;
//储存的实体结构
typedef struct {
char* mKey;
StoreType mType;
StoreValue mValue;
} StoreEntry;
//实体存储集合
typedef struct {
StoreEntry mEntries[STORE_MAX_CAPPCITY];
int32_t mLength;
} Store;
int32_t isEntryValid(JNIEnv *pEnv, StoreEntry *pEntry,
StoreType pType);
StoreEntry* allocateEntry(JNIEnv *pEnv, Store *pstore,
jstring pKey);
StoreEntry* findEntry(JNIEnv *pEnv, Store *store, jstring pKey,
int32_t* pError);
void releaseEntryValue(JNIEnv *pEvn, StoreEntry *pEntry);
#endif
6 .本地存储工具类头文件 Store.c
#include "Store.h"
#include <string.h>
/*
* 本地实现的诸工具方法
*/
//判断传入实例是否合法
int32_t isEntryValid(JNIEnv *pEnv, StoreEntry *pEntry,
StoreType pType)
{
if((pEntry != NULL) && (pEntry->mType == pType)){
return 1;
}
return 0;
}
//根据传入键名,在pStore里为其分配一个键名空间(分配一个实体,以在调用处写值)
StoreEntry* allocateEntry(JNIEnv *pEnv, Store *pStore, jstring pKey)
{
int32_t lError = 0;
StoreEntry *lEntry = findEntry(pEnv, pStore, pKey, &lError);
if(lEntry != NULL)
{
releaseEntryValue(pEnv, lEntry);
}
else if(!lError)
{
if(pStore->mLength >= STORE_MAX_CAPPCITY)
{
return NULL;
}
lEntry = pStore->mEntries + pStore->mLength;
const char *lKeyTmp = (*pEnv)->GetStringUTFChars(pEnv, pKey, NULL);
if(lKeyTmp == NULL)
{
return NULL;//add by me the 'NULL'
}
lEntry->mKey = (char*) malloc(strlen(lKeyTmp));
strcpy(lEntry->mKey, lKeyTmp);
(*pEnv)->ReleaseStringUTFChars(pEnv, pKey, lKeyTmp);
++pStore->mLength;
}
return lEntry;
}
//根据传入键名,若其在写入值时有分配空间,则在此释放空间
void releaseEntryValue(JNIEnv *pEnv, StoreEntry *pEntry)
{
int i;
switch(pEntry->mType)
{
case StoreType_String:
free(pEntry->mValue.mString);
break;
}
}
//查找是否存在此实体,存在则返加,否则返回空
StoreEntry* findEntry(JNIEnv *pEnv, Store *pStore, jstring pKey,
int32_t* pError)
{
StoreEntry *lEntry = pStore->mEntries;
StoreEntry *lEntryEnd = lEntry + pStore->mLength;
const char *lKeyTmp = (*pEnv)->GetStringUTFChars(pEnv, pKey, NULL);
if(lKeyTmp == NULL)
{
if(pError != NULL)
{
*pError = 1;
}
return NULL;//add by me the 'NULL'
}
while((lEntry < lEntryEnd)
&& (strcmp(lEntry->mKey, lKeyTmp) != 0) )
{
++lEntry;
}
(*pEnv)->ReleaseStringUTFChars(pEnv, pKey, lKeyTmp);
return (lEntry == lEntryEnd) ? NULL : lEntry;
}
7.JNI生成头文件: com_packtpub_Store.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_packtpub_Store */
#ifndef _Included_com_packtpub_Store
#define _Included_com_packtpub_Store
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_packtpub_Store
* Method: getInteger
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_packtpub_Store_getInteger
(JNIEnv *, jobject, jstring);
/*
* Class: com_packtpub_Store
* Method: setInteger
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_packtpub_Store_setInteger
(JNIEnv *, jobject, jstring, jint);
/*
* Class: com_packtpub_Store
* Method: getString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_packtpub_Store_getString
(JNIEnv *, jobject, jstring);
/*
* Class: com_packtpub_Store
* Method: setString
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_packtpub_Store_setString
(JNIEnv *, jobject, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
8.本地方法的实现文件: com_packtpub_Store.c
#include "com_packtpub_Store.h"
#include "Store.h"
#include <stdint.h>
#include <string.h>
static Store gStore = {{} , 0};
/*
* 提供java端本地方法的实现。
*/
/*
* Class: com_packtpub_Store
* Method: getInteger
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_packtpub_Store_getInteger
(JNIEnv *pEnv, jobject pThis, jstring pKey)
{
StoreEntry *lEntry = findEntry(pEnv, &gStore, pKey, NULL);
if(isEntryValid(pEnv, lEntry, StoreType_Integer)){
return lEntry->mValue.mInteger;
}
else
{
return 0;
}
}
/*
* Class: com_packtpub_Store
* Method: setInteger
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_packtpub_Store_setInteger
(JNIEnv *pEnv, jobject pThis, jstring pKey, jint pInteger)
{
StoreEntry *lEntry = allocateEntry(pEnv, &gStore, pKey);
if(lEntry != NULL)
{
lEntry->mType = StoreType_Integer;
lEntry->mValue.mInteger = pInteger;
}
}
/*
* Class: com_packtpub_Store
* Method: getString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_packtpub_Store_getString
(JNIEnv *pEnv, jobject pThis, jstring pKey)
{
StoreEntry *lEntry = findEntry(pEnv, &gStore, pKey, NULL);
if(isEntryValid(pEnv, lEntry, StoreType_String))
{
return (*pEnv)->NewStringUTF(pEnv, lEntry->mValue.mString);
}
else
{
return NULL;
}
}
/*
* Class: com_packtpub_Store
* Method: setString
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_packtpub_Store_setString
(JNIEnv *pEnv, jobject pThis, jstring pKey, jstring pValue)
{
const char *lStringTemp = (*pEnv)->GetStringUTFChars(pEnv, pValue, NULL);
if(lStringTemp == NULL)
{
return ;
}
StoreEntry *lEntry = allocateEntry(pEnv, &gStore, pKey);
if(lEntry != NULL)
{
lEntry->mType = StoreType_String;
jsize lStringLength = (*pEnv)->GetStringUTFLength(pEnv, pValue);
lEntry->mValue.mString =
(char*)malloc(sizeof(char) * (lStringLength + 1));
strcpy(lEntry->mValue.mString, lStringTemp);
}
(*pEnv)->ReleaseStringUTFChars(pEnv, pValue, lStringTemp);
}
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS := -DHAVE_INTTYPES_H
LOCAL_MODULE := store
LOCAL_SRC_FILES := com_packtpub_Store.c Store.c
include $(BUILD_SHARED_LIBRARY)
要点小结:
- 一个jstring不能直接在本地代码中进行操作。java与C中的字符串是不同的事物。在java中,String是一个带有成员方法的真实的对象,而在C中,字符串是原始的字符数组; 要把一个Java String转换为C String,可以使用GetStringUTFChars( ) 来进行。但同时,也必须使用ReleaseStringUTFChars( ) 来释放临时缓存(前者产生的),(详见8中示例)。
- 要将一个C string转化为Java String,可以使用NewStringUTF( ),(详见8中示例)。
- 从上例可以看出:在本地调用过程中,基本类型整型也经历了多次变化:首在在java中是int,在传入\传出java时是jint,最后在本地端是int/int_32。显然,可以在本地代码中使用JNI的表现方式jint,因为这两种类型是等价的。(int_32属于C99标准库,typedef int,为的是更好的移植性。在stdint.h中还有更多的数字类型,要在JNI中使用它们,需要在android.mk中申明 -DHAVE_INTTYPES_H宏)。
- 下表列出其它常见基本类型在C,Java,JNI中的类型对照:
- 更多C与Java字符串的转换,看P95;详细JNI规格说明://java.sun.com/docs/books/jni/html/jniTOC