记录一下自己学习JNI的过程,目标是做一个小demo,实现apk简单调用C或C++语言,从而熟悉JNI过程。并记录错误。可以实现java调用C或C++语言,并且从C或C++回调java。
以下为个人观点,未必正确,可不参考。
JNI就是java调用本地方法的意思,具体来说就是android apk里java调用.so为后缀的库文件里的C或C++函数。
我个人认为java调用C有两种,一种是java函数映射成C语言函数,类似于注册。另一种是直接通过apk包名实现定义。
一开始选择在网上找到最多的方法,就是直接包名绑定定义。
首先,参考网上的一个编译独立so库来调用jni的方法:Android源码中编译自己的so库 -- http://www.linuxidc.com/Linux/2013-02/79007.htm
由于本人比较喜欢用系统代码编译文件,因此选用了这个参考例子来编译so库文件,从而调用。其实应该也可以搭建NDK开发环境来使用JNI,但本人还没搭建环境,所以这种尝试等以后再弄。
本人的开发环境是在ubuntu系统下,对于这个例子我先略记录一下我自己的学习过程,首先是在系统根目录下/packages/apps/里建立一个文件夹jni_test,文件夹里按照例子来新建
tools.h文件
代码:
#ifndef TOOLS_H
#define TOOLS_H
extern int add(int add1, int add2);
#endif
然后新建
tools.c文件
代码:
#include "tools.h"
int add(int add1, int add2){
return add1 + add2;
}
jni_call.c文件
代码
#include "tools.h"
#include <jni.h>
jint
Java_com_eton_helloworld_MainActivity_add(JNIEnv* env, jobject this, jint add1, jint add2){
return add(add1, add2);
}
Android.mk文件
内容:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libtools
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := tools.c
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libjnicall
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := jni_call.c
LOCAL_STATIC_LIBRARIES := libtools
include $(BUILD_SHARED_LIBRARY)
打开终端进入android系统根目录,设置编译环境,然后开始编译这个文件夹,输入 mmm packages/apps/jni_test/并回车,如下
/q8625$ mmm packages/apps/jni_test/(说明,$表明我不是在超级用户模式下,#就表示在超级用户模式下,这个对我而言无所谓)
编译完成提示:Install: out/target/product/msm8625/system/lib/libjnicall.so
这时在/out/target/product/msm8625/system/lib 文件夹内就会生成一个libjnicall.so的库文件,这就是让java加载的lib库。
编译完so库后,把so库push到测试机器里面(本人使用实体机来测试,并不是使用虚拟机,在机器的system目录下新建一个jni文件哟难于存放so库): adb push libjnicall.so /system/jni/
然后打开eclipse新建一个android工程,就一个最简单的hellowork工程,包名为com.example.jnicall 这里注意在包名里不要有_(下横杠)出现,因为后面的jni方法会有影响。
MainActivity.java文件代码:
package com.example.jnicall;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
编译工程跑起来就一句hello world就可以了。在onCreate函数下增加加载lib库函数,并且定义jni接口函数。代码如下:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
add(1,2);
}
static {
System.load("/system/jni/libjnicall.so");
}
public native int add(int add1, int add2);
}
保存并编译程序,用终端打印log会发现报错,错误截取:java.lang.UnsatisfiedLinkError: Native method not found: com.example.jnicall.MainActivity.add:(II)I
这个错误是java调用add函数时出错,为什么呢?是因为jni库里缺少了东西。
重新打开jni_call.c文件,修改如下:
#include "tools.h"
#include <jni.h>
#include "JNIHelp.h"
Java_com_example_jnicall_MainActivity_add(JNIEnv* env, jobject this, jint add1, jint add2){
return add(add1, add2);
}
#ifdef __cplusplus
extern "C" {
#endif
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
} // extern "C"
#endif
主要是添加JNI_OnLoad函数。和"JNIHelp.h"这个头文件。修改add函数名,注意,这个函数名是Java_com_example_jnicall_MainActivity_add,前面的Java的J一定要大写,后面是接java的包名和native 函数名(包名com.example.jnicall变成com_example_jnicall),包名里的 . 要用 _ 替换,这时就知道为什么建工程时不要有 _ 了。然后保存编译,重新把so库push进机器里。再编译运行apk,就发现apk能跑了,如果添加log,就能发现已经调用了jni_call.c里的add函数。这个就是较为简单的jnni方法实例。
我把工程代码放上共享:http://download.csdn.net/detail/u013820413/6991951 不需要积分,由于是ubuntu压缩,所以没试过windows解压是否会出错。
参考资料:使用NDK移植开源项目,JNI的使用技巧 http://terryblog.blog.51cto.com/1764499/792442
Android的NDK开发(1)————Android JNI简介与调用流程 http://blog.csdn.net/conowen/article/details/7521340