當前位置:首頁 » 安卓系統 » androidjni實現

androidjni實現

發布時間: 2022-08-23 22:01:04

A. 如何在Android下使用JNI

Android中JNI是編譯so庫的源代碼,編譯成功後會生成SO庫,android中最終是使用SO庫的。
1.android的NDK開發需要在linux下進行: 因為需要把C/C++編寫的代碼生成能在arm上運行的.so文件,這就需要用到交叉編譯環境,而交叉編譯需要在linux系統下才能完成。
2.安裝android-ndk開發包,這個開發包可以在google android 官網下載: 通過這個開發包的工具才能將android jni 的C/C++的代碼編譯成庫
3.android應用程序開發環境: 包括eclipse、java、 android sdk、 adt等。
NDK編譯步驟:
1.選擇 ndk 自帶的例子 hello-jni ,我的位於E:\android-ndk-r5\samples\hello-jni( 根據具體的安裝位置而定 ) 。
2.運行 cygwin ,輸入命令 cd /cygdrive/e/android-ndk-r5/samples/hello-jni ,進入到 E:\android-ndk-r5\samples\hello-jni 目錄。
3.輸入 $NDK/ndk-build ,執行成功後,它會自動生成一個 libs 目錄,把編譯生成的 .so 文件放在裡面。 ($NDK是調用我們之前配置好的環境變數, ndk-build 是調用 ndk 的編譯程序 )
4.此時去 hello-jni 的 libs 目錄下看有沒有生成的 .so 文件,如果有,ndk 就運行正常啦。

B. 如何在Android下使用JNI

方法步驟:
第一步:
使用Java編寫HelloWorld 的Android應用程序:
代碼如下
package com.lucyfyr;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class HelloWorld extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.v("fresne", printJNI("I am HelloWorld Activity"));
}
static
{
//載入庫文件
System.loadLibrary("HelloWorldJni");
}
//聲明原生函數 參數為String類型 返回類型為String
private native String printJNI(String inputStr);
}
這一步可以使用eclipse來生成一個App;
因為eclipse會自動編譯此Java文件,後面要是用到。
第二步:
生成共享庫的頭文件:
進入到eclipse生成的Android Project中 :/HelloWorld/bin/classes/com/lucyfyr/ 下:

可以看到裡面後很多後綴為.class的文件,就是eclipse為自動編譯好了的java文件,其中就有:

HelloWorld.class文件。

退回到classes一級目錄:/HelloWorld/bin/classes/

執行如下命令:

javah com.lucyfyr.HelloWorld

生成文件:com_lucyfyr_HelloWorld.h

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>
/* Header for class com_lucyfyr_HelloWorld */
#ifndef _Included_com_lucyfyr_HelloWorld
#define _Included_com_lucyfyr_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_lucyfyr_HelloWorld
* Method: printJNI
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
可以看到自動生成對應的函數:Java_com_lucyfyr_HelloWorld_printJNI
Java_ + 包名(com.lucyfyr) + 類名(HelloWorld) + 介面名(printJNI):必須要按此JNI規范來操作;
java虛擬機就可以在com.simon.HelloWorld類調用printJNI介面的時候自動找到這個C實現的Native函數調用。
當然函數名太長,可以在.c文件中通過函數名映射表來實現簡化。
第三步:
實現JNI原生函數源文件:
新建com_lucyfyr_HelloWorld.c文件:
#include <jni.h>
#define LOG_TAG "HelloWorld"
#include <utils/Log.h>
/* Native interface, it will be call in java code */
JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI(JNIEnv *env, jobject obj,jstring inputStr)
{
LOGI("fresne Hello World From libhelloworld.so!");
// 從 instring 字元串取得指向字元串 UTF 編碼的指針
const char *str =
(const char *)(*env)->GetStringUTFChars( env,inputStr, JNI_FALSE );
LOGI("fresne--->%s",(const char *)str);
// 通知虛擬機本地代碼不再需要通過 str 訪問 Java 字元串。
(*env)->ReleaseStringUTFChars(env, inputStr, (const char *)str );
return (*env)->NewStringUTF(env, "Hello World! I am Native interface");
}

/* This function will be call when the library first be load.
* You can do some init in the libray. return which version jni it support.
*/
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
void *venv;
LOGI("fresne----->JNI_OnLoad!");
if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {
LOGE("fresne--->ERROR: GetEnv failed");
return -1;
}
return JNI_VERSION_1_4;
}

OnLoadJava_com_lucyfyr_HelloWorld_printJNI
函數裡面做一些log輸出 注意JNI中的log輸出的不同。
JNI_OnLoad函數JNI規范定義的,當共享庫第一次被載入的時候會被回調,
這個函數裡面可以進行一些初始化工作,比如注冊函數映射表,緩存一些變數等,
最後返回當前環境所支持的JNI環境。本例只是簡單的返回當前JNI環境。
第四步:
編譯生成so庫
編譯com_lucyfyr_HelloWorld.c成so庫可以和app一起編譯,也可以都單獨編譯。
在當前目錄下建立jni文件夾:HelloWorld/jni/
下建立Android.mk ,並將com_lucyfyr_HelloWorld.c和 com_lucyfyr_HelloWorld.h 拷貝到進去
編寫編譯生成so庫的Android.mk文件:
LOCAL_PATH:= $(call my-dir)
# 一個完整模塊編譯
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_lucyfyr_HelloWorld.c
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE := libHelloWorldJni
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_TAGS :=optional
include $(BUILD_SHARED_LIBRARY)
系統變數解析:
LOCAL_PATH - 編譯時的目錄
$(call 目錄,目錄….) 目錄引入操作符
如該目錄下有個文件夾名稱 src,則可以這樣寫 $(call src),那麼就會得到 src 目錄的完整路徑
include $(CLEAR_VARS) -清除之前的一些系統變數
LOCAL_MODULE - 編譯生成的目標對象
LOCAL_SRC_FILES - 編譯的源文件
LOCAL_C_INCLUDES - 需要包含的頭文件目錄
LOCAL_SHARED_LIBRARIES - 鏈接時需要的外部庫
LOCAL_PRELINK_MODULE - 是否需要prelink處理
include$(BUILD_SHARED_LIBRARY) - 指明要編譯成動態庫

C. android 怎麼調用jni裡面的方法

調用jni裡面的方法,過程如下:
第一步:
使用Java編寫HelloWorld 的Android應用程序:

package com.lucyfyr;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class HelloWorld extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.v("fresne", printJNI("I am HelloWorld Activity"));
}
static
{
//載入庫文件
System.loadLibrary("HelloWorldJni");
}
//聲明原生函數 參數為String類型 返回類型為String
private native String printJNI(String inputStr);
}

這一步我們可以使用eclipse來生成一個App;
因為eclipse會自動為我們編譯此Java文件,後面要是用到。

第二步:
生成共享庫的頭文件:
進入到eclipse生成的Android Project中 :/HelloWorld/bin/classes/com/lucyfyr/ 下:
可以看到裡面後很多後綴為.class的文件,就是eclipse為我們自動編譯好了的java文件,其中就有:
HelloWorld.class文件。
退回到classes一級目錄:/HelloWorld/bin/classes/
執行如下命令:
javah com.lucyfyr.HelloWorld
生成文件:com_lucyfyr_HelloWorld.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lucyfyr_HelloWorld */
#ifndef _Included_com_lucyfyr_HelloWorld
#define _Included_com_lucyfyr_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_lucyfyr_HelloWorld
* Method: printJNI
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif

可以看到自動生成對應的函數:Java_com_lucyfyr_HelloWorld_printJNI
Java_ + 包名(com.lucyfyr) + 類名(HelloWorld) + 介面名(printJNI):必須要按此JNI規范來操作;
java虛擬機就可以在com.simon.HelloWorld類調用printJNI介面的時候自動找到這個C實現的Native函數調用。
當然函數名太長,可以在.c文件中通過函數名映射表來實現簡化。

第三步:
實現JNI原生函數源文件:
新建com_lucyfyr_HelloWorld.c文件:

D. 如何在android的jni線程中實現回調

jni回調是指在c/c++代碼中調用java函數,當在c/c++的線程中執行回調函數時,會導致回調失敗。

其中一種在Android系統的解決方案是:

把c/c++中所有線程的創建,由pthread_create函數替換為由Java層的創建線程的函數AndroidRuntime::createJavaThread。

假設有c++函數:

[cpp] view plain
void *thread_entry(void *args)
{
while(1)
{
printf("thread running...\n");
sleep(1);
}

}

void init()
{
pthread_t thread;
pthread_create(&thread,NULL,thread_entry,(void *)NULL);
}

init()函數創建一個線程,需要在該線程中調用java類Test的回調函數Receive:

[cpp] view plain
public void Receive(char buffer[],int length){
String msg = new String(buffer);
msg = "received from jni callback:" + msg;
Log.d("Test", msg);
}

首先在c++中定義回調函數指針:

[cpp] view plain
//test.h
#include <pthread.h>
//function type for receiving data from native
typedef void (*ReceiveCallback)(unsigned char *buf, int len);

/** Callback for creating a thread that can call into the Java framework code.
* This must be used to create any threads that report events up to the framework.
*/
typedef pthread_t (* CreateThreadCallback)(const char* name, void (*start)(void *), void* arg);

typedef struct{
ReceiveCallback recv_cb;
CreateThreadCallback create_thread_cb;
}Callback;

再修改c++中的init和thread_entry函數:

[cpp] view plain
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/wait.h>
#include <unistd.h>
#include "test.h"

void *thread_entry(void *args)
{
char *str = "i'm happy now";
Callback cb = NULL;
int len;
if(args != NULL){
cb = (Callback *)args;
}

len = strlen(str);
while(1)
{
printf("thread running...\n");
//invoke callback method to java
if(cb != NULL && cb->recv_cb != NULL){
cb->recv_cb((unsigned char*)str, len);
}
sleep(1);
}

}

void init(Callback *cb)
{
pthread_t thread;
//pthread_create(&thread,NULL,thread_entry,(void *)NULL);
if(cb != NULL && cb->create_thread_cb != NULL)
{
cb->create_thread_cb("thread",thread_entry,(void *)cb);
}
}

然後在jni中實現回調函數,以及其他實現:

[cpp] view plain
//jni_test.c
#include <stdlib.h>
#include <malloc.h>
#include <jni.h>
#include <JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"

#include "test.h"
#define RADIO_PROVIDER_CLASS_NAME "com/tonny/Test"

using namespace android;

static jobject mCallbacksObj = NULL;
static jmethodID method_receive;

static void (JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
LOGE_EX(env);
env->ExceptionClear();
}
}

static void receive_callback(unsigned char *buf, int len)
{
int i;
JNIEnv* env = AndroidRuntime::getJNIEnv();
jcharArray array = env->NewCharArray(len);
jchar *pArray ;

if(array == NULL){
LOGE("receive_callback: NewCharArray error.");
return;
}

pArray = (jchar*)calloc(len, sizeof(jchar));
if(pArray == NULL){
LOGE("receive_callback: calloc error.");
return;
}

// buffer to jchar array
for(i = 0; i < len; i++)
{
*(pArray + i) = *(buf + i);
}
// buffer to jcharArray
env->SetCharArrayRegion(array,0,len,pArray);
//invoke java callback method
env->CallVoidMethod(mCallbacksObj, method_receive,array,len);
//release resource
env->DeleteLocalRef(array);
free(pArray);
pArray = NULL;

(env, __FUNCTION__);
}

static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)
{
return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
}

static Callback mCallbacks = {
receive_callback,
create_thread_callback
};

static void jni_class_init_native
(JNIEnv* env, jclass clazz)
{
method_receive = env->GetMethodID(clazz, "Receive", "([CI)V");
}

static int jni_init
(JNIEnv *env, jobject obj)
{

if (!mCallbacksObj)
mCallbacksObj = env->NewGlobalRef(obj);

return init(&mCallbacks);
}

static const JNINativeMethod gMethods[] = {
{ "class_init_native", "()V", (void *)jni_class_init_native },
{ "native_init", "()I", (void *)jni_init },
};

static int registerMethods(JNIEnv* env) {

const char* const kClassName = RADIO_PROVIDER_CLASS_NAME;
jclass clazz;
/* look up the class */
clazz = env->FindClass(kClassName);
if (clazz == NULL) {
LOGE("Can't find class %s/n", kClassName);
return -1;
}
/* register all the methods */
if (env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK)
{
LOGE("Failed registering methods for %s/n", kClassName);
return -1;
}
/* fill out the rest of the ID cache */
return 0;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
LOGI("Radio JNI_OnLoad");
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed/n");
goto fail;
}

if(env == NULL){
goto fail;
}
if (registerMethods(env) != 0) {
LOGE("ERROR: PlatformLibrary native registration failed/n");
goto fail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
fail:
return result;
}

jni的Android.mk文件中共享庫設置為:

[cpp] view plain
LOCAL_SHARED_LIBRARIES := liblog libcutils libandroid_runtime libnativehelper

最後再實現Java中的Test類:

[java] view plain
//com.tonny.Test.java

public class Test {

static{
try {
System.loadLibrary("test");
class_init_native();

} catch(UnsatisfiedLinkError ule){
System.err.println("WARNING: Could not load library libtest.so!");
}

}

public int initialize() {
return native_radio_init();
}

public void Receive(char buffer[],int length){
String msg = new String(buffer);
msg = "received from jni callback" + msg;
Log.d("Test", msg);
}

protected static native void class_init_native();

protected native int native_init();

}

E. 如何在android的jni線程中實現回調

JNI回調是指在c/c++代碼中調用java函數,當在c/c++的線程中執行回調函數時,會導致回調失敗。

其中一種在Android系統的解決方案是:

把c/c++中所有線程的創建,由pthread_create函數替換為由Java層的創建線程的函數AndroidRuntime::createJavaThread。

假設有c++函數:

[cpp] view plain
void *thread_entry(void *args)
{
while(1)
{
printf("thread running...\n");
sleep(1);
}

}

void init()
{
pthread_t thread;
pthread_create(&thread,NULL,thread_entry,(void *)NULL);
}

init()函數創建一個線程,需要在該線程中調用java類Test的回調函數Receive:

[cpp] view plain
public void Receive(char buffer[],int length){
String msg = new String(buffer);
msg = "received from jni callback:" + msg;
Log.d("Test", msg);
}

首先在c++中定義回調函數指針:

[cpp] view plain
//test.h
#include <pthread.h>
//function type for receiving data from native
typedef void (*ReceiveCallback)(unsigned char *buf, int len);

/** Callback for creating a thread that can call into the Java framework code.
* This must be used to create any threads that report events up to the framework.
*/
typedef pthread_t (* CreateThreadCallback)(const char* name, void (*start)(void *), void* arg);

typedef struct{
ReceiveCallback recv_cb;
CreateThreadCallback create_thread_cb;
}Callback;

再修改c++中的init和thread_entry函數:

[cpp] view plain
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/wait.h>
#include <unistd.h>
#include "test.h"

void *thread_entry(void *args)
{
char *str = "i'm happy now";
Callback cb = NULL;
int len;
if(args != NULL){
cb = (Callback *)args;
}

len = strlen(str);
while(1)
{
printf("thread running...\n");
//invoke callback method to java
if(cb != NULL && cb->recv_cb != NULL){
cb->recv_cb((unsigned char*)str, len);
}
sleep(1);
}

}

void init(Callback *cb)
{
pthread_t thread;
//pthread_create(&thread,NULL,thread_entry,(void *)NULL);
if(cb != NULL && cb->create_thread_cb != NULL)
{
cb->create_thread_cb("thread",thread_entry,(void *)cb);
}
}

然後在jni中實現回調函數,以及其他實現:

[cpp] view plain
//jni_test.c
#include <stdlib.h>
#include <malloc.h>
#include <jni.h>
#include <JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"

#include "test.h"
#define RADIO_PROVIDER_CLASS_NAME "com/tonny/Test"

using namespace android;

static jobject mCallbacksObj = NULL;
static jmethodID method_receive;

static void (JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
LOGE_EX(env);
env->ExceptionClear();
}
}

static void receive_callback(unsigned char *buf, int len)
{
int i;
JNIEnv* env = AndroidRuntime::getJNIEnv();
jcharArray array = env->NewCharArray(len);
jchar *pArray ;

if(array == NULL){
LOGE("receive_callback: NewCharArray error.");
return;
}

pArray = (jchar*)calloc(len, sizeof(jchar));
if(pArray == NULL){
LOGE("receive_callback: calloc error.");
return;
}

// buffer to jchar array
for(i = 0; i < len; i++)
{
*(pArray + i) = *(buf + i);
}
// buffer to jcharArray
env->SetCharArrayRegion(array,0,len,pArray);
//invoke java callback method
env->CallVoidMethod(mCallbacksObj, method_receive,array,len);
//release resource
env->DeleteLocalRef(array);
free(pArray);
pArray = NULL;

(env, __FUNCTION__);
}

static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)
{
return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
}

static Callback mCallbacks = {
receive_callback,
create_thread_callback
};

static void jni_class_init_native
(JNIEnv* env, jclass clazz)
{
method_receive = env->GetMethodID(clazz, "Receive", "([CI)V");
}

static int jni_init
(JNIEnv *env, jobject obj)
{

if (!mCallbacksObj)
mCallbacksObj = env->NewGlobalRef(obj);

return init(&mCallbacks);
}

static const JNINativeMethod gMethods[] = {
{ "class_init_native", "()V", (void *)jni_class_init_native },
{ "native_init", "()I", (void *)jni_init },
};

static int registerMethods(JNIEnv* env) {

const char* const kClassName = RADIO_PROVIDER_CLASS_NAME;
jclass clazz;
/* look up the class */
clazz = env->FindClass(kClassName);
if (clazz == NULL) {
LOGE("Can't find class %s/n", kClassName);
return -1;
}
/* register all the methods */
if (env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK)
{
LOGE("Failed registering methods for %s/n", kClassName);
return -1;
}
/* fill out the rest of the ID cache */
return 0;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
LOGI("Radio JNI_OnLoad");
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed/n");
goto fail;
}

if(env == NULL){
goto fail;
}
if (registerMethods(env) != 0) {
LOGE("ERROR: PlatformLibrary native registration failed/n");
goto fail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
fail:
return result;
}

jni的Android.mk文件中共享庫設置為:

[cpp] view plain
LOCAL_SHARED_LIBRARIES := liblog libcutils libandroid_runtime libnativehelper

最後再實現Java中的Test類:

[java] view plain
//com.tonny.Test.java

public class Test {

static{
try {
System.loadLibrary("test");
class_init_native();

} catch(UnsatisfiedLinkError ule){
System.err.println("WARNING: Could not load library libtest.so!");
}

}

public int initialize() {
return native_radio_init();
}

public void Receive(char buffer[],int length){
String msg = new String(buffer);
msg = "received from jni callback" + msg;
Log.d("Test", msg);
}

protected static native void class_init_native();

protected native int native_init();

}

F. 如何在android源碼中添加自己的jni方法

1,、項目實現了一個簡單的四則運算,項目的目錄層次如下:

AndroidManifest.xml Android.mk jni res src

資源文件簡簡單單,一個布局文件,稍後會有demo的下載地址

主要記錄備忘的內容如下:

MainActivity.Java

[html] view plain public native int add(int x, int y);

public native int substraction(int x, int y);

public native float multiplication(int x, int y);

public native float division(int x, int y);

static{

System.loadLibrary("arithmetic");

}

2、生成lib的名稱為libarithmetic.so.注意load的時候寫"arithmetic"

jni 目錄下有兩個文件,一個是Android.mk,一個是c++源文件long.cpp

jni/Android.mk如下:

[html] view plain LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE:= libarithmetic

LOCAL_SRC_FILES:= \

long.cpp

LOCAL_SHARED_LIBRARIES := \

libutils

LOCAL_STATIC_LIBRARIES :=

LOCAL_C_INCLUDES += \

$(JNI_H_INCLUDE)

LOCAL_CFLAGS +=

LOCAL_PRELINK_MODULE := false

include $(BUILD_SHARED_LIBRARY)

3、注釋:

LOCAL_PATH(必須定義,而且要第一個定義),宏函數『my-dir』, 由編譯系統提供,用於返回當前路徑(即包含Android.mk
file文件的目錄);

include $( CLEAR_VARS),

CLEAR_VARS由編譯系統提供,指定讓GNU MAKEFILE為你清除許多LOCAL_XXX變數(例如 LOCAL_MODULE,
LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...),除LOCAL_PATH
。這是必要的,因為所有的編譯控制文件都在同一個GNU MAKE執行環境中,所有的變數都是全局的;

LOCAL_MODULE_TAGS :=user eng tests optional

user: 指該模塊只在user版本下才編譯

eng: 指該模塊只在eng版本下才編譯

tests: 指該模塊只在tests版本下才編譯

optional:指該模塊在所有版本下都編譯

LOCAL_MODULE(必須定義),標識你在Android.mk文件中描述的每個模塊。名稱必須是唯一的,而且不包含任何空格。Note:編譯系統會自動產生合適的前綴和後綴,例如:arithmetic編譯成功後將生成libarithmetic.so庫文件

LOCAL_SRC_FILES 變數必須包含將要編譯打包進模塊中源代碼文件。不用在這里列出頭文件和包含文件。

LOCAL_SHARED_LIBRARIES中加入所需要鏈接的動態庫(*.so)的名稱

LOCAL_STATIC_LIBRARIES加入所需要鏈接的靜態庫(*.a)的名稱

LOCAL_CFLAG可選的編譯器選項,用法之一是定義宏,例如LOCAL_CFLAGS := -Werror作用是編譯警告也作為錯誤信息

LOCAL_PRELINK_MODULE:=false,不作prelink處理,默認是要prelink操作的,有可能造成地址空間沖突(這地方目前還不明白)
long.cpp源代碼如下:

[html] view plain #define LOG_TAG "LongTest2 long.cpp"

#include

#include

#include "jni.h"

jint add(JNIEnv *env, jobject thiz, jint x, jint y){

return x + y;

}

jint substraction(JNIEnv *env, jobject thiz, jint x, jint y){

return x - y;

}

jfloat multiplication(JNIEnv *env, jobject thiz, jint x, jint y){

return (float)x * (float)y;

}

jfloat division(JNIEnv *env, jobject thiz, jint x, jint y){

return (float)x/(float)y;

}

static const char *classPathName = "com/inspur/test2/MainActivity";

static JNINativeMethod methods[]= {

{"add", "(II)I", (void*)add},

{"substraction", "(II)I", (void*)substraction},

{"multiplication", "(II)F", (void*)multiplication},

{"division", "(II)F", (void*)division},

};

typedef union{

JNIEnv* env;

void* venv;

}UnionJNIEnvToVoid;

static int registerNativeMethods(JNIEnv* env, const char* className,

JNINativeMethod* gMethods, int numMethods){

jclass clazz;

clazz = env->FindClass(className);

if (clazz == NULL)

return JNI_FALSE;

if (env->RegisterNatives(clazz, gMethods, numMethods)<0)

return JNI_FALSE;

return JNI_TRUE;

}

static int registerNatives(JNIEnv *env){

if (!registerNativeMethods(env, classPathName,

methods, sizeof(methods)/sizeof(methods[0])))

{

return JNI_FALSE;

}

return JNI_TRUE;

}

jint JNI_OnLoad(JavaVM* vm, void* reserved){

UnionJNIEnvToVoid uenv;

uenv.venv = NULL;

jint result = -1;

JNIEnv *env = NULL;

if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK){

goto l;

}

env = uenv.env;

env = uenv.env;

if (registerNatives(env) != JNI_TRUE){

goto l;

}

result = JNI_VERSION_1_4;

l:

return result;

}

除了利用 編寫native JAVA類,通過javah生成.h文件,根據.h文件編寫.c/cpp文件
方法外(名字像老太太的裹腳步,又臭又長,而且不靈活),Android還可以通過引用JNI_Onload方式實現。jint JNI_onLoad(JavaVM*
vm, void* reverced),改方法在so文件被載入時調用。

JNI_OnLoad()有兩個重要的作用:

指定JNI版本:告訴VM該組件使用那一個JNI版本(若未提供JNI_OnLoad()函數,VM會默認該使用最老的JNI
1.1版),如果要使用新版本的JNI,例如JNI
1.4版,則必須由JNI_OnLoad()函數返回常量JNI_VERSION_1_4(該常量定義在jni.h中) 來告知VM。

初始化設定,當VM執行到System.loadLibrary()函數時,會立即先呼叫JNI_OnLoad()方法,因此在該方法中進行各種資源的初始化操作最為恰當。

JNI_OnUnload()的作用與JNI_OnLoad()對應,當VM釋放JNI組件時會呼叫它,因此在該方法中進行善後清理,資源釋放的動作最為合適。

4、項目根目錄下Android.mk文件:

[html] view plain LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_SRC_FILES := $(call all-java-files-under, src)

LOCAL_JNI_SHARED_LIBRARIES := libarithmetic

LOCAL_PACKAGE_NAME := LongTest

LOCAL_SHARED_LIBRARIES := \

libutils\

liblog

include $(BUILD_PACKAGE)

include $(LOCAL_PATH)/jni/Android.mk

# Also build all of the sub-targets under this one: the shared library.

include $(call all-makefiles-under,$(LOCAL_PATH))

LOCAL_PACKAGE_NAME:項目名稱,即最終生成apk的名字

LOCAL_JNI_SHARED_LIBRARIES := libxxx就是把so文件放到apk文件里的libs/armeabi里

執行BUILD_PACKAGE。它的定義也是在config.mk中定義如下:BUILD_PACKAGE:=
$(BUILD_SYSTEM)/package.mk

$(call all-java-files-under, src)編譯的源代碼文件列表添加src目錄下所有的java 源文件

$(call all-makefiles-under, $(LOCAL_PATH))編譯器會在編譯完當前目錄下的文件後再深入子目錄編譯

如果make過android源碼,可以在項目根目錄下執行mm命令進行編譯。前提是執行過source
androidSRC/build/envsetup.sh

或者直接把source androidSRC/build/envsetup.sh添加到~/.bashrc中,會更加方便

G. 如何在Android下使用JNI

1.引言
我們知道,Android系統的底層庫由c/c++編寫,上層Android應用程序通過Java虛擬機調用底層介面,銜接底層c/c++庫與Java應用程序間的介面正是JNI(JavaNative Interface)。本文描述了如何在ubuntu下配置AndroidJNI的開發環境,以及如何編寫一個簡單的c函數庫和JNI介面,並通過編寫Java程序調用這些介面,最終運行在模擬器上的過程。

2.環境配置

2.1.安裝jdk1.6
(1)從jdk官方網站下載jdk-6u29-linux-i586.bin文件。
(2)執行jdk安裝文件
[html] view plainprint?
01.$chmod a+x jdk-6u29-linux-i586.bin
02.$jdk-6u29-linux-i586.bin
$chmod a+x jdk-6u29-linux-i586.bin
$jdk-6u29-linux-i586.bin
(3)配置jdk環境變數

[html] view plainprint?
01.$sudo vim /etc/profile
02.#JAVAEVIRENMENT
03.exportJAVA_HOME=/usr/lib/java/jdk1.6.0_29
04.exportJRE_HOME=$JAVA_HOME/jre
05.exportCLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
06.exportPATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
$sudo vim /etc/profile
#JAVAEVIRENMENT
exportJAVA_HOME=/usr/lib/java/jdk1.6.0_29
exportJRE_HOME=$JAVA_HOME/jre
exportCLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
exportPATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
保存後退出編輯,並重啟系統。

(4)驗證安裝

[html] view plainprint?
01.$java -version
02.javaversion "1.6.0_29"
03.Java(TM)SE Runtime Environment (build 1.6.0_29-b11)
04.JavaHotSpot(TM) Server VM (build 20.4-b02, mixed mode)
05.$javah
06.用法:javah[選項]<類>
07.其中[選項]包括:
08.-help輸出此幫助消息並退出
09.-classpath<路徑>用於裝入類的路徑
10.-bootclasspath<路徑>用於裝入引導類的路徑
11.-d<目錄>輸出目錄
12.-o<文件>輸出文件(只能使用-d或-o中的一個)
13.-jni生成JNI樣式的頭文件(默認)
14.-version輸出版本信息
15.-verbose啟用詳細輸出
16.-force始終寫入輸出文件
17.使用全限定名稱指定<類>(例
18.如,java.lang.Object)。
$java -version
javaversion "1.6.0_29"
Java(TM)SE Runtime Environment (build 1.6.0_29-b11)
JavaHotSpot(TM) Server VM (build 20.4-b02, mixed mode)
$javah
用法:javah[選項]<類>
其中[選項]包括:
-help輸出此幫助消息並退出
-classpath<路徑>用於裝入類的路徑
-bootclasspath<路徑>用於裝入引導類的路徑
-d<目錄>輸出目錄
-o<文件>輸出文件(只能使用-d或-o中的一個)
-jni生成JNI樣式的頭文件(默認)
-version輸出版本信息
-verbose啟用詳細輸出
-force始終寫入輸出文件
使用全限定名稱指定<類>(例
如,java.lang.Object)。2.2.安裝android應用程序開發環境
ubuntu下安裝android應用程序開發環境與windows類似,依次安裝好以下軟體即可:
(1)Eclipse
(2)ADT
(3)AndroidSDK
與windows下安裝唯一不同的一點是,下載這些軟體的時候要下載Linux版本的安裝包。
安裝好以上android應用程序的開發環境後,還可以選擇是否需要配置emulator和adb工具的環境變數,以方便在進行JNI開發的時候使用。配置步驟如下:
把emulator所在目錄android-sdk-linux/tools以及adb所在目錄android-sdk-linux/platform-tools添加到環境變數中,android-sdk-linux指androidsdk安裝包android-sdk_rxx-linux的解壓目錄。
[plain] view plainprint?
01.$sudo vim /etc/profile
02.exportPATH=~/software/android/android-sdk-linux/tools:$PATH
03. exportPATH=~/software/android/android-sdk-linux/platform-tools:$PATH
$sudo vim /etc/profile
exportPATH=~/software/android/android-sdk-linux/tools:$PATH
exportPATH=~/software/android/android-sdk-linux/platform-tools:$PATH
編輯完畢後退出,並重啟生效。

2.3.安裝NDK
NDK是由android提供的編譯android本地代碼的一個工具。
(1)從androidndk官網http://developer.android.com/sdk/ndk/index.html下載ndk,目前最新版本為android-ndk-r6b-linux-x86.tar.bz2.
(2)解壓ndk到工作目錄:
[plain] view plainprint?
01.$tar -xvf android-ndk-r6b-linux-x86.tar.bz2
02.$sudo mv android-ndk-r6b /usr/local/ndk
$tar -xvf android-ndk-r6b-linux-x86.tar.bz2
$sudo mv android-ndk-r6b /usr/local/ndk
(3)設置ndk環境變數

[plain] view plainprint?
01.$sudo vim /etc/profile
02.exportPATH=/usr/local/ndk:$PATH
$sudo vim /etc/profile
exportPATH=/usr/local/ndk:$PATH

編輯完畢後保存退出,並重啟生效

(4)驗證安裝

[plain] view plainprint?
01.$ cd/usr/local/ndk/samples/hello-jni/
02.$ ndk-build
03.Gdbserver : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
04.Gdbsetup : libs/armeabi/gdb.setup
05.Install : libhello-jni.so => libs/armeabi/libhello-jni.so
$ cd/usr/local/ndk/samples/hello-jni/
$ ndk-build
Gdbserver : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
Gdbsetup : libs/armeabi/gdb.setup
Install : libhello-jni.so => libs/armeabi/libhello-jni.so

3.JNI實現
我們需要定義一個符合JNI介面規范的c/c++介面,這個介面不用太復雜,例如輸出一個字元串。接下來,則需要把c/c++介面的代碼文件編譯成共享庫(動態庫).so文件,並放到模擬器的相關目錄下。最後,啟動Java應用程序,就可以看到最終效果了。

3.1.編寫Java應用程序代碼
(1)啟動Eclipse,新建android工程

Project:JNITest

Package:org.tonny.jni

Activity:JNITest

(2)編輯資源文件

編輯res/values/strings.xml文件如下:編輯res/layout/main.xml文件
我們在主界面上添加了一個EditText控制項和一個Button控制項。

(3)編輯JNITest.java文件


static表示在系統第一次載入類的時候,先執行這一段代碼,在這里表示載入動態庫libJNITest.so文件。

再看這一段:

[java] view plainprint?
01.privatenativeString GetReply();
privatenativeString GetReply();
native表示這個方法由本地代碼定義,需要通過jni介面調用本地c/c++代碼。

[java] view plainprint?
01.publicvoidonClick(View arg0) {
02.edtName.setText(reply);
03.}
publicvoidonClick(View arg0) {
edtName.setText(reply);
}

這段代碼表示點擊按鈕後,把native方法的返回的字元串顯示到EditText控制項。

(4)編譯工程,生成.class文件。

3.2.用javah工具生成符合JNI規范的c語言頭文件

在終端中,進入android工程所在的bin目錄

[plain] view plainprint?
01.$cd ~/project/Android/JNITest/bin
$cd ~/project/Android/JNITest/bin
我們用ls命令查看,可以看到bin目錄下有個classes目錄,其目錄結構為classes/org/tonny/jni,即classes的子目錄結構是android工程的包名org.tonny.jni。請注意,下面我們准備執行javah命令的時候,必須進入到org/tonny/jni的上級目錄,即classes目錄,否則javah會提示找不到相關的java類。

下面繼續:

[plain] view plainprint?
01.$cd classes
02.$javah org.tonny.jni.JNITest
03.$ls
04.org org_tonny_jni_JNITest.h
$cd classes
$javah org.tonny.jni.JNITest
$ls
org org_tonny_jni_JNITest.h

執行javahorg.tonny.jni.JNITest命令,在classes目錄下會生成org_tonny_jni_JNITest.h頭文件。如果不進入到classes目錄下的話,也可以這樣:

[plain] view plainprint?
01.$javah -classpath ~/project/Android/JNITest/bin/classesorg.tonny.jni.JNITest
$javah -classpath ~/project/Android/JNITest/bin/classesorg.tonny.jni.JNITest
-classpath 參數表示裝載類的目錄。

3.3.編寫c/c++代碼
生成org_tonny_jni_JNITest.h頭文件後,我們就可以編寫相應的函數代碼了。下面在android工程目錄下新建jni目錄,即~/project/Android/JNITest/jni,把org_tonny_jni_JNITest.h頭文件拷貝到jni目錄下,並在jni目錄下新建org_tonny_jni_JNITest.c文件,編輯代碼如下:

[cpp] view plainprint?
01.#include<jni.h>
02.#include<string.h>
03.#include"org_tonny_jni_JNITest.h"
04.
05.
06.JNIEXPORTjstring JNICALLJava_org_tonny_jni_JNITest_GetReply
07.(JNIEnv *env, jobject obj){
08.return(*env)->NewStringUTF(env,(char*)"Hello,JNITest");
09.}
#include<jni.h>
#include<string.h>
#include"org_tonny_jni_JNITest.h"

JNIEXPORTjstring JNICALLJava_org_tonny_jni_JNITest_GetReply
(JNIEnv *env, jobject obj){
return(*env)->NewStringUTF(env,(char*)"Hello,JNITest");
}

我們可以看到,該函數的實現相當簡單,返回一個字元串為:"Hello,JNITest"

3.4.編寫Android.mk文件
在~/project/Android/JNITest/jni目錄下新建Android.mk文件,android可以根據這個文件的編譯參數編譯模塊。編輯Android.mk文件如下:

[plain] view plainprint?
01.LOCAL_PATH:= $(call my-dir)
02.include$(CLEAR_VARS)
03.LOCAL_MODULE := libJNITest
04.LOCAL_SRC_FILES:= org_tonny_jni_JNITest.c
05.include$(BUILD_SHARED_LIBRARY)
LOCAL_PATH:= $(call my-dir)
include$(CLEAR_VARS)
LOCAL_MODULE := libJNITest
LOCAL_SRC_FILES:= org_tonny_jni_JNITest.c
include$(BUILD_SHARED_LIBRARY)

LOCAL_MODULE表示編譯的動態庫名稱

LOCAL_SRC_FILES 表示源代碼文件

3.5.用ndk工具編譯並生成.so文件
進入到JNITest的工程目錄,執行ndk-build命令即可生成libJNITest.so文件。

[plain] view plainprint?
01.$cd ~/project/Android/JNITest/
02.$ndk-build
03.Invalidattribute name:
04.package
05.Install : libJNITest.so => libs/armeabi/libJNITest.so
$cd ~/project/Android/JNITest/
$ndk-build
Invalidattribute name:
package
Install : libJNITest.so => libs/armeabi/libJNITest.so
可以看到,在工程目錄的libs/armeabi目錄下生成了libJNITest.so文件。

3.6.在模擬器上運行
(1)首先,我們把android模擬器啟動起來。進入到emulator所在目錄,執行emulator命令:

[plain] view plainprint?
01.$cd ~/software/android/android-sdk-linux/tools
02.$./emulator @AVD-2.3.3-V10 -partition-size 512
$cd ~/software/android/android-sdk-linux/tools
$./emulator @AVD-2.3.3-V10 -partition-size 512
AVD-2.3.3-V10表示你的模擬器名稱,與在Eclipse->AVDManager下的AVDName對應,-partition-size表示模擬器的存儲設備容量。

(2)接下來,我們需要把libJNITest.so文件拷貝到模擬器的/system/lib目錄下,執行以下命令:

[plain] view plainprint?
01.$cd ~/project/Android/JNITest/libs/armeabi/
02.$adb remount
03.$adb push libJNITest.so /system/lib
04.80 KB/s (10084 bytes in 0.121s)
$cd ~/project/Android/JNITest/libs/armeabi/
$adb remount
$adb push libJNITest.so /system/lib
80 KB/s (10084 bytes in 0.121s)

當在終端上看到有80 KB/s (10084 bytes in 0.121s)傳輸速度等信息的時候,說明拷貝成功。

(3)在終端上執行JNITest程序,這個我們可以在Eclipse下,右鍵點擊JNITest工程,RunAs->Android Application,即可在模擬器上啟動程序

H. 如何在android的jni線程中實現回調

JNI回調是指在c/c++代碼中調用java函數,當在c/c++的線程中執行回調函數時,會導致回調失敗。

其中一種在Android系統的解決方案是:

把c/c++中所有線程的創建,由pthread_create函數替換為由Java層的創建線程的函數AndroidRuntime::createJavaThread。

假設有c++函數:

[cpp] view plain
void *thread_entry(void *args)
{
while(1)
{
printf("thread running...\n");
sleep(1);
}

}

void init()
{
pthread_t thread;
pthread_create(&thread,NULL,thread_entry,(void *)NULL);
}

init()函數創建一個線程,需要在該線程中調用java類Test的回調函數Receive:

[cpp] view plain
public void Receive(char buffer[],int length){
String msg = new String(buffer);
msg = "received from jni callback:" + msg;
Log.d("Test", msg);
}

首先在c++中定義回調函數指針:

[cpp] view plain
//test.h
#include <pthread.h>
//function type for receiving data from native
typedef void (*ReceiveCallback)(unsigned char *buf, int len);

/** Callback for creating a thread that can call into the Java framework code.
* This must be used to create any threads that report events up to the framework.
*/
typedef pthread_t (* CreateThreadCallback)(const char* name, void (*start)(void *), void* arg);

typedef struct{
ReceiveCallback recv_cb;
CreateThreadCallback create_thread_cb;
}Callback;

再修改c++中的init和thread_entry函數:

[cpp] view plain
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/wait.h>
#include <unistd.h>
#include "test.h"

void *thread_entry(void *args)
{
char *str = "i'm happy now";
Callback cb = NULL;
int len;
if(args != NULL){
cb = (Callback *)args;
}

len = strlen(str);
while(1)
{
printf("thread running...\n");
//invoke callback method to java
if(cb != NULL && cb->recv_cb != NULL){
cb->recv_cb((unsigned char*)str, len);
}
sleep(1);
}

}

void init(Callback *cb)
{
pthread_t thread;
//pthread_create(&thread,NULL,thread_entry,(void *)NULL);
if(cb != NULL && cb->create_thread_cb != NULL)
{
cb->create_thread_cb("thread",thread_entry,(void *)cb);
}
}

然後在jni中實現回調函數,以及其他實現:

[cpp] view plain
//jni_test.c
#include <stdlib.h>
#include <malloc.h>
#include <jni.h>
#include <JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"

#include "test.h"
#define RADIO_PROVIDER_CLASS_NAME "com/tonny/Test"

using namespace android;

static jobject mCallbacksObj = NULL;
static jmethodID method_receive;

static void (JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
LOGE_EX(env);
env->ExceptionClear();
}
}

static void receive_callback(unsigned char *buf, int len)
{
int i;
JNIEnv* env = AndroidRuntime::getJNIEnv();
jcharArray array = env->NewCharArray(len);
jchar *pArray ;

if(array == NULL){
LOGE("receive_callback: NewCharArray error.");
return;
}

pArray = (jchar*)calloc(len, sizeof(jchar));
if(pArray == NULL){
LOGE("receive_callback: calloc error.");
return;
}

// buffer to jchar array
for(i = 0; i < len; i++)
{
*(pArray + i) = *(buf + i);
}
// buffer to jcharArray
env->SetCharArrayRegion(array,0,len,pArray);
//invoke java callback method
env->CallVoidMethod(mCallbacksObj, method_receive,array,len);
//release resource
env->DeleteLocalRef(array);
free(pArray);
pArray = NULL;

(env, __FUNCTION__);
}

static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)
{
return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
}

static Callback mCallbacks = {
receive_callback,
create_thread_callback
};

static void jni_class_init_native
(JNIEnv* env, jclass clazz)
{
method_receive = env->GetMethodID(clazz, "Receive", "([CI)V");
}

static int jni_init
(JNIEnv *env, jobject obj)
{

if (!mCallbacksObj)
mCallbacksObj = env->NewGlobalRef(obj);

return init(&mCallbacks);
}

static const JNINativeMethod gMethods[] = {
{ "class_init_native", "()V", (void *)jni_class_init_native },
{ "native_init", "()I", (void *)jni_init },
};

static int registerMethods(JNIEnv* env) {

const char* const kClassName = RADIO_PROVIDER_CLASS_NAME;
jclass clazz;
/* look up the class */
clazz = env->FindClass(kClassName);
if (clazz == NULL) {
LOGE("Can't find class %s/n", kClassName);
return -1;
}
/* register all the methods */
if (env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK)
{
LOGE("Failed registering methods for %s/n", kClassName);
return -1;
}
/* fill out the rest of the ID cache */
return 0;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
LOGI("Radio JNI_OnLoad");
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed/n");
goto fail;
}

if(env == NULL){
goto fail;
}
if (registerMethods(env) != 0) {
LOGE("ERROR: PlatformLibrary native registration failed/n");
goto fail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
fail:
return result;
}

jni的Android.mk文件中共享庫設置為:

[cpp] view plain
LOCAL_SHARED_LIBRARIES := liblog libcutils libandroid_runtime libnativehelper

最後再實現Java中的Test類:

[java] view plain
//com.tonny.Test.java

public class Test {

static{
try {
System.loadLibrary("test");
class_init_native();

} catch(UnsatisfiedLinkError ule){
System.err.println("WARNING: Could not load library libtest.so!");
}

}

public int initialize() {
return native_radio_init();
}

public void Receive(char buffer[],int length){
String msg = new String(buffer);
msg = "received from jni callback" + msg;
Log.d("Test", msg);
}

protected static native void class_init_native();

protected native int native_init();

}

I. 如何在android的jni線程中實現回調

把c/c++中所有線程的創建,由pthread_create函數替換為由Java層的創建線程的函數AndroidRuntime::createJavaThread。

假設有c++函數:

[cpp] view plain
void *thread_entry(void *args)
{
while(1)
{
printf("thread running...\n");
sleep(1);
}

}

void init()
{
pthread_t thread;
pthread_create(&thread,NULL,thread_entry,(void *)NULL);
}

init()函數創建一個線程,需要在該線程中調用java類Test的回調函數Receive:

[cpp] view plain
public void Receive(char buffer[],int length){
String msg = new String(buffer);
msg = "received from jni callback:" + msg;
Log.d("Test", msg);
}

首先在c++中定義回調函數指針:

[cpp] view plain
//test.h
#include <pthread.h>
//function type for receiving data from native
typedef void (*ReceiveCallback)(unsigned char *buf, int len);

/** Callback for creating a thread that can call into the Java framework code.
* This must be used to create any threads that report events up to the framework.
*/
typedef pthread_t (* CreateThreadCallback)(const char* name, void (*start)(void *), void* arg);

typedef struct{
ReceiveCallback recv_cb;
CreateThreadCallback create_thread_cb;
}Callback;

再修改c++中的init和thread_entry函數:

[cpp] view plain
//test.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/wait.h>
#include <unistd.h>
#include "test.h"

void *thread_entry(void *args)
{
char *str = "i'm happy now";
Callback cb = NULL;
int len;
if(args != NULL){
cb = (Callback *)args;
}

len = strlen(str);
while(1)
{
printf("thread running...\n");
//invoke callback method to java
if(cb != NULL && cb->recv_cb != NULL){
cb->recv_cb((unsigned char*)str, len);
}
sleep(1);
}

}

void init(Callback *cb)
{
pthread_t thread;
//pthread_create(&thread,NULL,thread_entry,(void *)NULL);
if(cb != NULL && cb->create_thread_cb != NULL)
{
cb->create_thread_cb("thread",thread_entry,(void *)cb);
}
}

然後在jni中實現回調函數,以及其他實現:

[cpp] view plain
//jni_test.c
#include <stdlib.h>
#include <malloc.h>
#include <jni.h>
#include <JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"

#include "test.h"
#define RADIO_PROVIDER_CLASS_NAME "com/tonny/Test"

using namespace android;

static jobject mCallbacksObj = NULL;
static jmethodID method_receive;

static void (JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
LOGE_EX(env);
env->ExceptionClear();
}
}

static void receive_callback(unsigned char *buf, int len)
{
int i;
JNIEnv* env = AndroidRuntime::getJNIEnv();
jcharArray array = env->NewCharArray(len);
jchar *pArray ;

if(array == NULL){
LOGE("receive_callback: NewCharArray error.");
return;
}

pArray = (jchar*)calloc(len, sizeof(jchar));
if(pArray == NULL){
LOGE("receive_callback: calloc error.");
return;
}

// buffer to jchar array
for(i = 0; i < len; i++)
{
*(pArray + i) = *(buf + i);
}
// buffer to jcharArray
env->SetCharArrayRegion(array,0,len,pArray);
//invoke java callback method
env->CallVoidMethod(mCallbacksObj, method_receive,array,len);
//release resource
env->DeleteLocalRef(array);
free(pArray);
pArray = NULL;

(env, __FUNCTION__);
}

static pthread_t create_thread_callback(const char* name, void (*start)(void *), void* arg)
{
return (pthread_t)AndroidRuntime::createJavaThread(name, start, arg);
}

static Callback mCallbacks = {
receive_callback,
create_thread_callback
};

static void jni_class_init_native
(JNIEnv* env, jclass clazz)
{
method_receive = env->GetMethodID(clazz, "Receive", "([CI)V");
}

static int jni_init
(JNIEnv *env, jobject obj)
{

if (!mCallbacksObj)
mCallbacksObj = env->NewGlobalRef(obj);

return init(&mCallbacks);
}

static const JNINativeMethod gMethods[] = {
{ "class_init_native", "()V", (void *)jni_class_init_native },
{ "native_init", "()I", (void *)jni_init },
};

static int registerMethods(JNIEnv* env) {

const char* const kClassName = RADIO_PROVIDER_CLASS_NAME;
jclass clazz;
/* look up the class */
clazz = env->FindClass(kClassName);
if (clazz == NULL) {
LOGE("Can't find class %s/n", kClassName);
return -1;
}
/* register all the methods */
if (env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/sizeof(gMethods[0])) != JNI_OK)
{
LOGE("Failed registering methods for %s/n", kClassName);
return -1;
}
/* fill out the rest of the ID cache */
return 0;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
LOGI("Radio JNI_OnLoad");
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed/n");
goto fail;
}

if(env == NULL){
goto fail;
}
if (registerMethods(env) != 0) {
LOGE("ERROR: PlatformLibrary native registration failed/n");
goto fail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
fail:
return result;
}

jni的Android.mk文件中共享庫設置為:

[cpp] view plain
LOCAL_SHARED_LIBRARIES := liblog libcutils libandroid_runtime libnativehelper

最後再實現Java中的Test類:

[java] view plain
//com.tonny.Test.java

public class Test {

static{
try {
System.loadLibrary("test");
class_init_native();

} catch(UnsatisfiedLinkError ule){
System.err.println("WARNING: Could not load library libtest.so!");
}

}

public int initialize() {
return native_radio_init();
}

public void Receive(char buffer[],int length){
String msg = new String(buffer);
msg = "received from jni callback" + msg;
Log.d("Test", msg);
}

protected static native void class_init_native();

protected native int native_init();

}

J. 如何在Android下使用JNI

第一步: 使用Java編寫HelloWorld 的Android應用程序: 復制代碼 package com.lucyfyr; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class HelloWorld extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.v("fresne", printJNI("I am HelloWorld Activity")); } static { //載入庫文件 System.loadLibrary("HelloWorldJni"); } //聲明原生函數 參數為String類型 返回類型為String private native String printJNI(String inputStr); } 復制代碼 這一步我們可以使用eclipse來生成一個App; 因為eclipse會自動為我們編譯此Java文件,後面要是用到。 第二步: 生成共享庫的頭文件: 進入到eclipse生成的Android Project中 :/HelloWorld/bin/classes/com/lucyfyr/ 下: 可以看到裡面後很多後綴為.class的文件,就是eclipse為我們自動編譯好了的java文件,其中就有: HelloWorld.class文件。 退回到classes一級目錄:/HelloWorld/bin/classes/ 執行如下命令: javah com.lucyfyr.HelloWorld 生成文件:com_lucyfyr_HelloWorld.h 復制代碼 /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_lucyfyr_HelloWorld */ #ifndef _Included_com_lucyfyr_HelloWorld #define _Included_com_lucyfyr_HelloWorld #ifdef __cplusplus extern "C" { #endif /* * Class: com_lucyfyr_HelloWorld * Method: printJNI * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif 復制代碼 可以看到自動生成對應的函數:Java_com_lucyfyr_HelloWorld_printJNI Java_ + 包名(com.lucyfyr) + 類名(HelloWorld) + 介面名(printJNI):必須要按此JNI規范來操作; java虛擬機就可以在com.simon.HelloWorld類調用printJNI介面的時候自動找到這個C實現的Native函數調用。 當然函數名太長,可以在.c文件中通過函數名映射表來實現簡化。 第三步: 實現JNI原生函數源文件: 新建com_lucyfyr_HelloWorld.c文件: 復制代碼 #include <jni.h> #define LOG_TAG "HelloWorld" #include <utils/Log.h> /* Native interface, it will be call in java code */ JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI(JNIEnv *env, jobject obj,jstring inputStr) { LOGI("fresne Hello World From libhelloworld.so!"); // 從 instring 字元串取得指向字元串 UTF 編碼的指針 const char *str = (const char *)(*env)->GetStringUTFChars( env,inputStr, JNI_FALSE ); LOGI("fresne--->%s",(const char *)str); // 通知虛擬機本地代碼不再需要通過 str 訪問 Java 字元串。 (*env)->ReleaseStringUTFChars(env, inputStr, (const char *)str ); return (*env)->NewStringUTF(env, "Hello World! I am Native interface"); } /* This function will be call when the library first be load. * You can do some init in the libray. return which version jni it support. */ jint JNI_OnLoad(JavaVM* vm, void* reserved) { void *venv; LOGI("fresne----->JNI_OnLoad!"); if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) { LOGE("fresne--->ERROR: GetEnv failed"); return -1; } return JNI_VERSION_1_4; } 復制代碼 OnLoadJava_com_lucyfyr_HelloWorld_printJNI 函數裡面做一些log輸出 注意JNI中的log輸出的不同。 JNI_OnLoad函數JNI規范定義的,當共享庫第一次被載入的時候會被回調, 這個函數裡面可以進行一些初始化工作,比如注冊函數映射表,緩存一些變數等, 最後返回當前環境所支持的JNI環境。本例只是簡單的返回當前JNI環境。 第四步: 編譯生成so庫 編譯com_lucyfyr_HelloWorld.c成so庫可以和app一起編譯,也可以都單獨編譯。 在當前目錄下建立jni文件夾:HelloWorld/jni/ 下建立Android.mk ,並將com_lucyfyr_HelloWorld.c和 com_lucyfyr_HelloWorld.h 拷貝到進去 編寫編譯生成so庫的Android.mk文件: 復制代碼 LOCAL_PATH:= $(call my-dir) # 一個完整模塊編譯 include $(CLEAR_VARS) LOCAL_SRC_FILES:=com_lucyfyr_HelloWorld.c LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) LOCAL_MODULE := libHelloWorldJni LOCAL_SHARED_LIBRARIES := libutils LOCAL_PRELINK_MODULE := false LOCAL_MODULE_TAGS :=optional include $(BUILD_SHARED_LIBRARY) 復制代碼 系統變數解析: LOCAL_PATH - 編譯時的目錄 $(call 目錄,目錄….) 目錄引入操作符 如該目錄下有個文件夾名稱 src,則可以這樣寫 $(call src),那麼就會得到 src 目錄的完整路徑 include $(CLEAR_VARS) -清除之前的一些系統變數 LOCAL_MODULE - 編譯生成的目標對象 LOCAL_SRC_FILES - 編譯的源文件 LOCAL_C_INCLUDES - 需要包含的頭文件目錄 LOCAL_SHARED_LIBRARIES - 鏈接時需要的外部庫 LOCAL_PRELINK_MODULE - 是否需要prelink處理 include$(BUILD_SHARED_LIBRARY) - 指明要編譯成動態庫

熱點內容
我的世界伺服器等級如何升 發布:2025-01-15 12:45:55 瀏覽:687
c語言程序填空題 發布:2025-01-15 12:45:53 瀏覽:543
怎麼配置氯化鈉濃度 發布:2025-01-15 12:34:06 瀏覽:205
4000除以125簡便演算法 發布:2025-01-15 12:27:41 瀏覽:463
源碼商用 發布:2025-01-15 12:26:54 瀏覽:74
價錢演算法 發布:2025-01-15 12:26:03 瀏覽:400
蘋果手機安卓功能在哪裡 發布:2025-01-15 12:21:34 瀏覽:786
伺服器地址埠名稱怎麼找 發布:2025-01-15 12:15:32 瀏覽:705
怎麼把電腦程序改安卓 發布:2025-01-15 12:13:52 瀏覽:576
安卓如何設置格子 發布:2025-01-15 12:02:11 瀏覽:936