jniandroid的简单介绍

如何在android的jni线程中实现回调

jni回调是指在c/c++代码中调用java函数,当在c/c++的线程中执行回调函数时,会导致回调失败。

创新互联建站是一家专注于成都网站制作、成都网站建设与策划设计,光泽网站建设哪家好?创新互联建站做网站,专注于网站建设十余年,网设计领域的专业建站公司;建站业务涵盖:光泽等地区。光泽做网站价格咨询:18980820575

其中一种在Android系统的解决方案是:

把c/c++中所有线程的创建,由pthread_create函数替换为由Java层的创建线程的函数AndroidRuntime::createJavaThread。

假设有c++函数:

[cpp] view plaincopy

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 plaincopy

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 plaincopy

//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 plaincopy

//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 plaincopy

//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 checkAndClearExceptionFromCallback(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;

}

//copy buffer to jchar array

for(i = 0; i len; i++)

{

*(pArray + i) = *(buf + i);

}

//copy 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;

checkAndClearExceptionFromCallback(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 plaincopy

LOCAL_SHARED_LIBRARIES := liblog libcutils libandroid_runtime libnativehelper

最后再实现Java中的Test类:

[java] view plaincopy

//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();

}

android 为什么要使用jni

android的jni可以使用c/c++来开发,相比java而言,运行的效率提高了很多,特别是在做一些图像算法,或者游戏逻辑的时候,使用jni将大大的提高效率。比如某个游戏要采用opengl,同样加载一个由1000个多边形组成的3d模型,jni要比java运算快好几倍,这样就保证了游戏运行的fps不会太低。

另外一个好处就是内存管理上面,java的内存管理全部由虚拟机来管理,C++由程序员来管理,利用率上面就好多了。

等等其他优点。

既然这么多的优点,为什么一个android程序不采用纯c来开发呢?因为是android的 UI framework采用java,所以,在UI上面还是采用java来开发。

如何在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("dufresne", 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("dufresne Hello World From libhelloworld.so!");

// 从 instring 字符串取得指向字符串 UTF 编码的指针

const char *str =

(const char *)(*env)-GetStringUTFChars( env,inputStr, JNI_FALSE );

LOGI("dufresne---%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("dufresne-----JNI_OnLoad!");

if ((*vm)-GetEnv(vm, (void**)venv, JNI_VERSION_1_4) != JNI_OK) {

LOGE("dufresne---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) - 指明要编译成动态库


当前文章:jniandroid的简单介绍
网站URL:http://scyanting.com/article/dsdesgp.html