① 如何在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();
}
② 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
Android中JNI是编译so库的源代码,编译成功后会生成SO库,android中最终是使用SO库的。 1android的NDK开发需要在linux下进行: 因为需要把C/C++编写的代码如何在Android下使用JNI
④ 如何在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 就运行正常啦。
⑤ android java 和 jni 调用代码的区别
关于Android studio中使用NDK/JNI环境和入门:
1. C代码回调Java方法的流程
(1) 找到java对应的Class
创建一个char*数组, 然后使用jni.h中提供的FindClass方法获取jclass返回值;
[cpp] view plain print?
char* classname = "wjy/geridge/com/testndk/jni/JniUtils";
jclass dpclazz = (*env)->FindClass(env, classname);
(2) 找到要调用的方法的methodID
使用jni.h中提供的GetMethodID方法, 获取jmethodID, 传入参数 ①JNIEnv指针 ②Class对象 ③ 方法名 ④方法签名, 在这里方法名和方法签名确定一个方法, 方法签名就是方法的返回值 与 参数的唯一标示;
⑥ 如何在Android下使用JNI
关于如何在Android使用JNI调用C/C++代码库,网上已经有很多优秀的文章了,这里说一个大概过程吧:
首先需要懂C,其次要明白JNI的开发流程,然后还要知道NDK如何使用
1、在java代码中声明了一个native本地方法
Public native String helloFromc();
2、在项目目录中创建JNI文件夹
3、在JNI文件夹里面创建C文件,按照规范写代码
Jstring
Java_com_cheng_jnitest_MainActivity_helloFromc(JNIEnv* env,jobject obj)
4、用ndk-build指令编译
编译前需要配置Android.mk文件
//指定编译的文件夹,指定当前的目录
LOCAL_PATH := $(call my-dir)
//编译器在编译的时候会产生很多临时变量,中间变量最好在编译前清空所有的临时变量
include $(CLEAR_VARS)
//编译完成后的模块名
LOCAL_MOUDLE := hello
//编译的源文件
LOCAL_SRC_FILES:=hello.c
//编译一个动态库
//动态库.so 只包含运行的函数,不包含依赖,所以体积小,运行的时候回去系统寻找依赖
//静态库.a 包含所有的函数和运行的依赖,所以体积大,包含所有的api
include $(BUILD_SHARED_LIBRARY)
5、生成了一个so动态库,放到了libs里面
6、项目中引入依赖库
Static{
System.loadLibrary("hello");
}
⑦ 如何在Android下使用JNI
我们知道,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官网下载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文件如下:
[html] view plainprint?
01.<?xmlversionxmlversion="1.0"encoding="utf-8"?>
02.<resources>
03.<stringnamestringname="hello">HelloWorld, JNITestActivity!</string>
04.<stringnamestringname="app_name">JNITest</string>
05.<stringnamestringname="btn_show">Show</string>
06.</resources>
<?xmlversion="1.0"encoding="utf-8"?>
<resources>
<stringname="hello">HelloWorld, JNITestActivity!</string>
<stringname="app_name">JNITest</string>
<stringname="btn_show">Show</string>
</resources>
编辑res/layout/main.xml文件如下:
[html] view plainprint?
01.<?xmlversionxmlversion="1.0"encoding="utf-8"?>
02.<LinearLayoutxmlns:androidLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
03.android:layout_width="fill_parent"
04.android:layout_height="fill_parent"
05.android:orientation="vertical">
06.<TextView
07.android:layout_width="fill_parent"
08.android:layout_height="wrap_content"
09.android:text="@string/hello"/>
10.<EditText
11.android:id="@+id/ed_name"
12.android:layout_width="match_parent"
13.android:layout_height="wrap_content"
14.android:layout_gravity="center_horizontal"
15.android:layout_marginLeft="5dp"
16.android:layout_marginRight="5dp"/>
17.<Button
18.android:id="@+id/btn_show"
19.android:layout_width="109dp"
20.android:layout_height="wrap_content"
21.android:layout_gravity="center_horizontal"
22.android:text="@string/btn_show"/>
23.</LinearLayout>
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"/>
<EditText
android:id="@+id/ed_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"/>
<Button
android:id="@+id/btn_show"
android:layout_width="109dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/btn_show"/>
</LinearLayout>
我们在主界面上添加了一个EditText控件和一个Button控件。
(3)编辑JNITest.java文件
[java] view plainprint?
01.packageorg.tonny.jni;
02.
03.importandroid.app.Activity;
04.importandroid.os.Bundle;
05.importandroid.view.View;
06.importandroid.widget.EditText;
07.importandroid.widget.Button;
08.
09.
10. {
11.static{
12.System.loadLibrary("JNITest");
13.}
14.privatenativeString GetReply();
15.privateEditTextedtName;
16.privateButtonbtnShow;
17.Stringreply;
18./**Called when the activity is first created. */
19.@Override
20.publicvoidonCreate(Bundle savedInstanceState) {
21.super.onCreate(savedInstanceState);
22.setContentView(R.layout.main);
23.reply= GetReply();
24.edtName= (EditText)this.findViewById(R.id.ed_name);
25.btnShow= (Button)this.findViewById(R.id.btn_show);
26.btnShow.setOnClickListener(newButton.OnClickListener() {
27.publicvoidonClick(View arg0) {
28.edtName.setText(reply);
29.}
30.});
31.}
32.}
packageorg.tonny.jni;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.EditText;
importandroid.widget.Button;
{
static{
System.loadLibrary("JNITest");
}
privatenativeString GetReply();
privateEditTextedtName;
privateButtonbtnShow;
Stringreply;
/**Called when the activity is first created. */
@Override
publicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
reply= GetReply();
edtName= (EditText)this.findViewById(R.id.ed_name);
btnShow= (Button)this.findViewById(R.id.btn_show);
btnShow.setOnClickListener(newButton.OnClickListener() {
publicvoidonClick(View arg0) {
edtName.setText(reply);
}
});
}
}
我们看这一段代码:
[java] view plainprint?
01.static{
02.System.loadLibrary("JNITest");
03.}
static{
System.loadLibrary("JNITest");
}
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文件。