网上已经有太多的有关如何配置eclipse+NDK了,本人就不再重复这些了,只是想记录下自己开始写第一个NDK程序的整个流程(保证可执行),共自己和大家分享。
首先安装一个能够支持Native代码的eclipse插件Sequoyah,然后在eclipse中的“窗口-首选项-Android”中多出来了一个“本机开发”选项,在“NDK Location”选择你的android-ndk的路径。有了Sequoyah插件进行Android Native开发就简单多了。下面根据自己的第一个Android Native程序开发过程,做一个记录。
1. 创建Android应用程序MyFirstNativeStore,过程省去。
2. 创建用来保存int和String类型的
public enum StoreType {
Integer, String
}和Store类,该类使用到Native函数,利用本地语言进行保存和读取操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package
com.yemeishu.myfirstnativestore.store;
public
class
Store {
static
{
System.loadLibrary(
"MyFirstNativeStore"
);
}
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);
}
|
其中在eclipse中会提示“此项目没有本机库支持”
这时候可以点开左边的提示,选择“加上本机支持”:
之后会在出现添加本机支持有关的参数名称(如NDK所在路径、生成的lib**.so文件名称等等)
添加完成之后,在项目中多了一个“jni”文件夹,该文件夹这时候多出了两个文件,如本程序中多了“MyFirstNativeStore.cpp”和“Android.mk”,有了这两个文件,我们不用自己手动编写麻烦的“Android.mk”文件了。这两个文件的含义也不用多说了。
2. 利用javah编译生成头文件,首先在eclipse中“运行-外部工具-外部工具配置”选项中配置javah工具:
3. 执行外部工具javah,执行完之后,刷新本工程,会在jni文件夹下自动生成头文件“com_yemeishu_myfirstnativestore_store_Store.h”(包名+java类名)。
4. 完成一些基本类配置之后,我们来设计android显示层,显示需要导入的store数据和读取store数据,设计界面
xml代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".StoreActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:text="@string/sor" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView1"
android:layout_marginTop="25dp"
android:text="@string/key" />
<EditText
android:id="@+id/uiKeyEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView2"
android:layout_alignBottom="@+id/textView2"
android:layout_marginLeft="30dp"
android:layout_toRightOf="@+id/textView2"
android:ems="10" >
<requestFocus />
</EditText>
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/uiKeyEdit"
android:layout_marginTop="44dp"
android:text="@string/value" />
<EditText
android:id="@+id/uiValueEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textView3"
android:layout_centerHorizontal="true"
android:ems="10" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView3"
android:layout_marginTop="40dp"
android:text="@string/Spinner" />
<Spinner
android:id="@+id/uiTypeSpinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/uiKeyEdit"
android:layout_below="@+id/uiValueEdit"
android:layout_marginTop="25dp"
android:entries="@array/sor_labels" />
<Button
android:id="@+id/setValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/getValue"
android:layout_alignBottom="@+id/getValue"
android:layout_toRightOf="@+id/getValue"
android:text="Set Value" />
<Button
android:id="@+id/getValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/uiValueEdit"
android:layout_alignParentBottom="true"
android:layout_marginBottom="35dp"
android:text="Get Value" />
</RelativeLayout>
在StoreActivity类中 直接获取控件资源,设置两个按钮的按键事件函数onSetValue()和onGetValue。
private EditText mUIKeyEdit, mUIValueEdit;
private Spinner mUITypeSpinner;
private Button mUIGetButton, mUISetButton;
private Store mStore;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mUIKeyEdit = (EditText) findViewById(R.id.uiKeyEdit);
mUIValueEdit = (EditText) findViewById(R.id.uiValueEdit);
mUITypeSpinner = (Spinner) findViewById(R.id.uiTypeSpinner);
mUIGetButton = (Button) findViewById(R.id.getValue);
mUISetButton = (Button) findViewById(R.id.setValue);
mStore = new Store();
mUIGetButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO 自动生成的方法存根
onGetValue();
}
});
mUISetButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO 自动生成的方法存根
onSetValue();
}
});
两个函数代码如下:
private void onGetValue() {
String lKey = mUIKeyEdit.getText().toString();
String lType1 = (String) mUITypeSpinner.getSelectedItem();
StoreType lType = StoreType.Integer;
if("String".equals(lType1))
lType = StoreType.String;
System.out.println(lType.toString());
switch (lType) {
case Integer:
mUIValueEdit.setText(Integer.toString(mStore.getInteger(lKey)));
break;
case String:
mUIValueEdit.setText(mStore.getString(lKey));
break;
}
}
private void onSetValue() {
String lKey = mUIKeyEdit.getText().toString();
String lValue = mUIValueEdit.getText().toString();
System.out.println(lKey.toString() + " " + lValue.toString());
Log.i("1", lKey.toString() + " " + lValue.toString());
System.out.println(mUITypeSpinner.getSelectedItem().toString());
String lType1 = (String) mUITypeSpinner.getSelectedItem();
StoreType lType = StoreType.Integer;
if("String".equals(lType1))
lType = StoreType.String;
System.out.println(mUITypeSpinner.getSelectedItem().toString());
try {
switch (lType) {
case Integer:
mStore.setInteger(lKey, Integer.parseInt(lValue));
break;
case String:
mStore.setString(lKey, lValue);
break;
}
} catch (NumberFormatException eNumberFormatException) {
displayError("Incorrect value.");
}
}
private void displayError(String pError) {
Toast.makeText(getApplicationContext(), pError, Toast.LENGTH_LONG)
.show();
}
5. 完成了界面设计了,java层基本完成,现在把注意力放在jni文件夹下,读取和保存Store类型的数据的Native层实现了。
5.1 在文件夹下jni由于没配置好c++编译器和NDK命令等,所以自动生成的.cpp和.h等文件出现错误,所以先进行配置,首先在工程属性中,在“C/C++ Build”下的“Tool Chain Editor”选择GCC等工具。
"C/C++ Build的Builder Settings"中配置"Build command:"直接输入ndk-build。
在“Behaviour”中设置
5.2 首先在jni文件夹下创建Store.h文件,主要创建一些保存Store类型、Store数组等枚举、结构体等信息,直接上代码(你肯定能看得懂):
#include "jni.h"
#include <stdint.h>
#define STORE_MAX_CAPACITY 16
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_CAPACITY];
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* pStore, jstring pKey,
int32_t* pError);
void releaseEntryValue(JNIEnv* pEnv, StoreEntry* pEntry);
这里还定义了一些操作Store类型信息的操作函数,如findEntry()查找保存在pStore链表中的Store数据,并返回StoreEntry结构数据,即数据的key、数据类型(int和string类型)和数据值。具体实现函数在jni文件夹下创建Store.c类中实现,代码如下:
#include "Store.h"
#include <string.h>
#include <stdlib.h>
#include <jni.h>
int32_t isEntryValid(JNIEnv* pEnv, StoreEntry* pEntry, StoreType pType) {
if ((pEntry != NULL) && (pEntry->mType == pType)) {
return 1;
}
return 0;
}
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;
}
while ((lEntry < lEntryEnd) && (strcmp(lEntry->mKey, lKeyTmp) != 0)) {
++lEntry;
}
(*pEnv)->ReleaseStringUTFChars(pEnv, pKey, lKeyTmp);
return (lEntry == lEntryEnd) ? NULL : lEntry;
}
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_CAPACITY) {
return NULL;
}
lEntry = pStore->mEntries + pStore->mLength;
const char* lKeyTmp = (*pEnv)->GetStringUTFChars(pEnv, pKey, NULL);
if (lKeyTmp == NULL) {
return 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;
}
}
5.3 完成了基本Store操作之后,现在开始完成连接java和Native桥梁的“MyFirstNativeStore.cpp”(还记得吧?),在该类中直接实现由javah外部工具生成的“com_yemeishu_myfirstnativestore_store_Store.h”的头文件定义的四个函数,“com_yemeishu_myfirstnativestore_store_Store.h”中的四个函数定义如下显示
/*
* Class: com_yemeishu_myfirstnativestore_store_Store
* Method: getInteger
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_yemeishu_myfirstnativestore_store_Store_getInteger
(JNIEnv *, jobject, jstring);
/*
* Class: com_yemeishu_myfirstnativestore_store_Store
* Method: setInteger
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_yemeishu_myfirstnativestore_store_Store_setInteger
(JNIEnv *, jobject, jstring, jint);
/*
* Class: com_yemeishu_myfirstnativestore_store_Store
* Method: getString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_yemeishu_myfirstnativestore_store_Store_getString
(JNIEnv *, jobject, jstring);
/*
* Class: com_yemeishu_myfirstnativestore_store_Store
* Method: setString
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_yemeishu_myfirstnativestore_store_Store_setString
(JNIEnv *, jobject, jstring, jstring);
具体实现函数代码如下
#include <string.h>
#include <jni.h>
#include "Store.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "com_yemeishu_myfirstnativestore_store_Store.h"
static Store gStore = { {}, 0 };
/*
* Class: com_yemeishu_myfirstnativestore_store_Store
* Method: getInteger
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_yemeishu_myfirstnativestore_store_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.0f;
}
}
/*
* Class: com_yemeishu_myfirstnativestore_store_Store
* Method: setInteger
* Signature: (Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_yemeishu_myfirstnativestore_store_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_yemeishu_myfirstnativestore_store_Store
* Method: getString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_yemeishu_myfirstnativestore_store_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_yemeishu_myfirstnativestore_store_Store
* Method: setString
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_yemeishu_myfirstnativestore_store_Store_setString(
JNIEnv* pEnv, jobject pThis, jstring pKey, jstring pString) {
const char* lStringTmp = (*pEnv)->GetStringUTFChars(pEnv, pString, NULL );
if (lStringTmp == NULL ) {
return;
}
StoreEntry* lEntry = allocateEntry(pEnv, &gStore, pKey);
if (lEntry != NULL ) {
lEntry->mType = StoreType_String;
jsize lStringLength = (*pEnv)->GetStringUTFLength(pEnv, pString);
lEntry->mValue.mString = (char*) malloc(
sizeof(char) * (lStringLength + 1));
strcpy(lEntry->mValue.mString, lStringTmp);
}
(*pEnv)->ReleaseStringUTFChars(pEnv, pString, lStringTmp);
}
5.4 最后修改下自动生成的Android.mk文件,将Store.c也包含进去
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := MyFirstNativeStore
### Add all source file names to be included in lib separated by a whitespace
LOCAL_SRC_FILES := MyFirstNativeStore.c Store.c
include $(BUILD_SHARED_LIBRARY)
6. 编译、执行。结果省去。
利用NDK进行Android编程,主要流程为:
一、编写Android程序,创建需要利用Native编写的实现函数类(包含native关键字的函数,和static{System.loadLibrary("xxx")})。
二、利用javah外部工具生成包含Native函数的头文件(头文件名为:包名+函数名称)。
三、通过“本机开发”(eclipse插件Sequoyah)自动生成Android.mk文件和实现Native函数的.c或者.cpp文件,实现函数功能。
四、利用Android提供的“Android GCC”或者“Cygwin GCC”或者“MinGW GCC”等工具和NDK-build编译Native函数,生成lib***.so(windows下)库文件。
五、完成实现java与Native的函数调用和回调、结果返回等。
7. 最后提供源码程序:MyFirstNativeStore 和插件sequoyah(org.eclipse.sequoyah.feature.2.0.0.I20110609-0753)