Android-jni开发

本文详细介绍了如何在Android开发中使用JNI(Java Native Interface),包括交叉编译的概念、NDK的使用方法、eclipse CDT插件的介绍以及cygwin的作用。通过具体步骤展示了如何在eclipse环境中设置JNI开发环境,并提供了常见错误的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android-jni

JNI( Java Native Interface)
JNI 是一个协议,通过这个协议用来沟通java代码和外部的本地代码(c/c++).
通过这个协议,java代码就可以调用外部的c/c++代码,外部的c/c++代码也可以调用java代码。

在Android架构中,Android的底层是linux kernel,而Android的Framewok classes的运行也依赖于,系统库。
linux kernel和系统库都是用c或c++编写的。C/C++都是可以直接操作硬件的语言。 因此若想通过java代码操作硬件,
那么java调C/C++是不可避免的。

学习jni应先了解几件事:

1)交叉编译
简单的说,就是在一个平台下,编译出另一个平台能够执行的二进制的代码。

为什么要这么做呢?普通的android的开发,我们写的java类是运行在JVM中的,并不需要交叉编译。
而对于jni的开发, 我们编写的是C/C++代码。显然他们不可能在JVM中运行。C/C++是可以直接操作硬件的语言,所以他们可以直接由
硬件支持而执行,一般android手机的硬件平台都有arm、x86、mips。
对于交叉编译,比如,我们在windows下进行C/C++代码的编译,而这些代码的最终执行平台显然不是windows,而是在手机上执行。

2)NDK(native development kit)
NDK是常见的交叉编译工具

3)eclipse CDT
eclipse插件, 使用这个插件,我们可以在eclipse下进行jni的开发。

4)cygwin
一个模拟器,让我们可以在window是运行linux指令。
NDK开发大都涉及到C/C++在GCC环境下编译、运行,所以在Windows环境下,需要用Cygwin模拟Linux编译环境。

For Windows, Cygwin 1.7 or higher is required. The NDK will not work with Cygwin 1.5 installations.

NDK

先来看一下NDK的目录中一些重要的文件。(以ndk-r9d为例)
* docs:帮助文档
* build/tools:linux的批处理文件
* platforms:不同平台编译c代码需要使用的头文件和类库
“jni.h”这个头文件就在这里。
这个头文件中定义了java类型与C/C++类型的关系。
还有其他一些与jni开发相当重要的内容
* prebuilt:预编译使用的二进制可执行文件
* sample:jni的使用例子
* source:ndk的源码
* toolchains
进行交叉编译所使用的各种工具链
* ndk-build.cmd:编译打包c代码的一个指令

具备了C/C++运行的基本环境,那么java与C++是怎么样实现互相调用的呢??

jni简单入门

以在eclipse下进行jni开发为例,基本步骤为:

1)在项目根目录下创建jni文件夹
该文件夹下存放我们编写的C代码

2)在java代码中,创建一个本地方法,例如 helloFromC
public native String helloFromC();

java代码调的肯定是java方法。 他不可能那么生猛的去调C方法,而本地方法,就是java调C的桥梁

3) 编写C代码(需要引入jni.h头文件)
若想实现jni,那么我们的C代码必须遵循一定的格式,根据本地方法名,我们的C方法应这样写
返回类型: Java类型 (jni头文件中的)
方法名(应叫函数名): Java_本地方法类的全路径_本地方法名
函数参数:类型应该为java类型, 且 JNIEnv* env, jobject obj这两个参数必须存在。
例如 helloFromC() 对应的C函数名应为:
jstring Java_com_suixin_helloworld1_MainActivity_helloFromC(JNIEnv* env, jobject obj)

4) 把返回类型,由C类型转换为java类型
例如:

    char* cstr = "hello from c";  //C字符串
        jstring jstr = (*env)->NewStringUTF(env, cstr);  //jni.h头文件
        return jstr;

5) 在jni文件中创建Android.mk文件(与我们编写的C代码位于同一路径)
内容为:
LOCAL_PATH := (callmydir)include (CLEAR_VARS)
#编译生成的文件的类库叫什么名字
LOCAL_MODULE := hello
#要编译的c文件
LOCAL_SRC_FILES := Hello.c
include $(BUILD_SHARED_LIBRARY)
这个文件主要用来指示ndk去按其定义内容生成相应的C链接库,即 libXXX.so文件

学过硬件编程的同学,Android.mk 是不是类似于makefile文件了???
libXXX.so是动态C链接库文件,java代码在调用C时,调用的就是其中的内容。(这个链接库文件出来后,.c文件就不需要了)

6)在jni文件夹下执行ndk-build.cmd指令
ndk-build.cmd 即编译我们C代码生成,*.so文件

在这里编译出来的*.so文件只能在arm架构上使用(eclipse默认)
若想编译出在x86平台上使用的*.so文件, 则应在jni文件夹下新建Application.mk文件
并添加 APP_ABI := armeabi armeabi-v7a x86 就ok了, 这时候就会生成x86、armv7、armeabi的链接库了
还可以 APP_ABI := all

7)运行工程。
java代码中加载so类库,调用本地方法
前面干了这么多事, 其实咱就是为了生成.so类库

通过以上步骤,就可以简单实现java调用c代码。

jni开发常见错误

  • findLibrary returned null

    • CPU平台不匹配
    • 加载类库时,写错类库名字
  • 本地方法找不到

    • 忘记加载类库
    • c代码中方法名写错了 ##

使用eclipse提供好的NDK开发环境

在eclipse下开发,只要添加了NDK开发环境,其实并不需要向上面那么麻烦的一步一步的去自己操作。
步骤:
1.配置NDK路径。(类似与配置SDK路径)
2.右击Android项目, Android tools -> add Native support
直接写上我们最终想产生的.so文件的名字。 eclipse会自动产生 jni文件夹, 对应的c文件和Android.mk文件
3.接下来我们所要做的就是在产生的C文件中,写C代码就ok了。

在编写C代码时,由于我们没有关联源文件(可以这样理解), 所以eclipse是无法自动提示的。为了简化开发
我们还应关联C/C++的源文件
-右击项目->propertites->C/C++ Gernel-Path and Symbols->include (我们把ndk下对应平台的include目录文件夹选定)
接下来, 编写C代码就方便多了

javah指令

在进行jni开发时,可能会发现在根据java方法写对应c函数名时,是一件十分蛋疼的事。
这是我们可以借助javah指令,来帮我们根据java方法名生成c方法名。
步骤:
1)在项目的src路径下,Shift+右击, 打开命令窗口。
2)javah 包名.类名 (相对于src目录) (注意, 这要求jdk1.7以上, 1.7以下要在bin/classes目录下执行这个命令)
3)这样就会生成一个xxx.h头文件, 里面就有自动生成的c函数名

java类型与C类型的关键点

java中,我们平常编写代码都是对象类型,一般我们持有的都是java引用类型变量。
熟悉C的同学可能会感觉到java引用类型变量就类似c的指针。
所以我们在将java类型(java引用类型变量)传递给C时,C接收它是可以的。因为C需要的就是指针。
其实在jni.h头文件中也可以看出:
typedef void* jobject;
是不是!!! ->java中是一切对象上帝的Object, 在C中被定义为 void*指针 (void*指针在C中也是类似Object的存在)。

在C中调java

java调用C方法是通过本地方法实现的, 而C调java方法,则类似于java的反射。

既然类似反射, 那么就先把一个范例代码 贴出来:

    jclass clazz = (*env)->FindClass(env, "com/suixin/ccalljava/MainActivity");  //jclass -> void* 
    jmethodID methodID = (*env)->GetMethodID(env, clazz, "show", "(Ljava/lang/String;)V");   
    (*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, "反射吗????"));

是不是和java的反射一模一样呢?
1)先拿到类对象
2)再根据方法名和类对象得到方法
3)调用方法。

细节: “(Ljava/lang/String;)V” 是我们要调用的java方法的签名
这个签名怎么的到呢? 我相信你拍脑袋是拍不出来的。 这里我们可以使用 javap指令
这里与javah不同 -> 在bin/classes/ 下 -> Shift+右击, 打开命令窗口 -> javah -s 包名.类名

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值