android使用jni
1. 如何在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,即可在模拟器上启动程序
2. 如何在android studio中用JNI调用静态库
android ndk开发经常遇到了动态库的问题,本文主要介绍: ① 动态链接库的生成; ② 在Java和C混合编程的情况下如何调用第三方动态链接库; ③ 使用dlopen程序运行时直接调用; ④ 纯c的方式开发调用; 本文重点推荐②和④,第③中太麻烦每个函数都需要dlsym调用一次; 代码的网络云链接: pan/s/1dD3qkQ9 密码:c5s3 工具/原料 Win8/article/63f236280b90690208ab3d12/article/c910274bfdd000cd371d2d4b/p/3247530080 2 根据“生成动态库”中的过程1到10,新建一个纯c的ndk程序: “D:\Android\android-ndk-r10\samples\native-activity" 3 把会用的so文件的目录libs拷贝到 jni目录下面 4 修改 Android.mk 文件,内容如下: LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := fkAdd LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libfkAdd.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := main LOCAL_SRC_FILES := main.c LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM LOCAL_STATIC_LIBRARIES := android_native_app_glue LOCAL_SHARED_LIBRARIES := fkAdd include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := native-activity LOCAL_SRC_FILES := NativeActivity.c LOCAL_LDLIBS := -llog -landroid LOCAL_STATIC_LIBRARIES := android_native_app_glue include $(BUILD_SHARED_LIBRARY) $(call import-mole,android/native_app_glue) 5 增加 NativeActivity.c 文件,添加内容如下: #include <jni.h> #include <errno.h> #include <dlfcn.h> #include <android_native_app_glue.h> void android_main(struct android_app* state) { // Make sure glue isn't stripped. app_mmy(); void* soAdd = dlopen("/data/data/com.example.native_activity/ lib/libfkAdd.so",RTLD_NOW); void* soMain = dlopen("/data/data/com.example.native_activity/ lib/libmain.so",RTLD_NOW); void (*fp_android_main)(struct android_app*) = (void (*)(struct android_app*))dlsym(soMain,"android_main"); fp_android_main(state); dlclose(soMain); dlclose(soAdd); } 6 在 main.c 文件中添加一行,方便测试: __android_log_print(ANDROID_LOG_DEBUG, "fuke", "engine_handle_input 100 + 200 = [%d] ", fkAdd(100, 200)); 7 编译运行,点击手机屏幕后,观察logcat 效果如下: END 注意事项 c++的函数在写动态链接库的时候,需要注意的是c++的函数会被系统修改,所以做动态库测试的使用最好用c语言
3. 如何在android studio中用JNI调用静态库
NDK中 apps下本身就有一个例子,two-libs,就是引用静态库,可以作为参考:
LOCAL_PATH:= $(call my-dir)
# first lib, which will be built statically
#
include $(CLEAR_VARS)
LOCAL_MODULE := libtwolib-first
LOCAL_SRC_FILES := first.c
include $(BUILD_STATIC_LIBRARY)
# second lib, which will depend on and include the first one
#
include $(CLEAR_VARS)
LOCAL_MODULE := libtwolib-second
LOCAL_SRC_FILES := second.c
LOCAL_STATIC_LIBRARIES := libtwolib-first
include $(BUILD_SHARED_LIBRARY)
4. Android源码学习之如何创建使用JNI
publicclassHelloWorld{
();//所有native关键词修饰的都是对本地的声明
static{
System.loadLibrary("hello");//载入本地库
}
publicstaticvoidmain(String[]args){
newHelloWorld().displayHelloWorld();
}
}
上面的就是使用JNI的方式,其中so库的名字是libhello.so,放到libs下面。
jni中的方法需要在修饰符后面加上native,
例如:();,
在使用之前需要导入so库,
导入方法:
static{
System.loadLibrary("hello");//载入本地库
}
在导入的时候是可以省略so库前面的lib的,当然加上也没事。
还有就是使用的地方一定要在固定的包名类名下。
希望能够帮助你。
5. 如何在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");
}
-
6. 如何在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) - 指明要编译成动态库
7. 如何在Android下使用JNI
首先看一下Android平台的框架图:(网上盗用)
可以看到Android上层的Application和ApplicationFramework都是使用Java编写,
底层包括系统和使用众多的LIiraries都是C/C++编写的。
所以上层Java要调用底层的C/C++函数库必须通过Java的JNI来实现。
下面将学习Android是如何通过Jni来实现Java对C/C++函数的调用。以HelloWorld程序为例:
第一步: