从 C 开始 Android NDK 实战 [4] : JNI

 


 


JNI 是 Java 调用 C 程序的接口规范. 理论说起来一大堆, 不如代码一段.

在 heelo project 下面的 hello\src\com\testing 目录下写一个接口程序

 

task.java 文件

package com.testing ;

public class task
{
 public native int  taska( );
 public native void taskb( int b );
}

 

这些 native 方法都将被用 C 实现.  用 javac 编译它, javac 就是 JDK 的那个 javac.

javac task.java

这时同一目录下产生了 task.class 文件, 使用下面命令, 编译一个 task 的 jni 头文件,

cd ......\hello\src  转到 hello\src 目录
javah -classpath . com.testing.task

这时产生了 com_testing_task.h 文件, 文件名太长, 改名为 task.h 转到 linux 下面去用. 现在, 那个
task.class 已经没有用了, 删除它.

头文件有了, 我们该用 C 语言实现它了. 转到 linux 下面, 建立一个目录, 例如 /hello 吧. 在它下面
建立一个目录 叫 jni, 即 /hello/jni. 这个 jni 不能随便, 只能是这个名字. 把前面生成的 task.h copy
到里面, 根据 task.h 函数原型写一个 task.c 或者 task.cpp 文件.

重要提示 : task.h 规定的函数原型不能被修改! 编写 task,cpp 时, 不要保存 java 变量, 例如 JavaVM 指
针和 JObject 指针. 来自 Java 的变量只能在其函数范围内使用. 看看 jni.h 文件有 Java 变量类型的说明.

现在到了编译它的时候了. 慢...慢...!, 你不能简单地用 NDK gcc make 了, 得用 NDK 提供的 ndk-build
工具. 具体做法是, 先在 jni 目录下编写一个 Android.mk 文件. 标准样本如下,


Android.mk 文件


LOCAL_PATH := $(call my-dir)

 

include $(CLEAR_VARS)

 

LOCAL_MODULE    := task

LOCAL_SRC_FILES := task.cpp

 

LOCAL_LDFLAGS := -llog -lstdc++ \

  ${NDK}/sources/cxx-stl/gnu-libstdc++/libs/armeabi/libsupc++.a

  

include $(BUILD_SHARED_LIBRARY)


注意我们加了 -llog, -lstdc++ 和 libsupc++.a 库 ( c++ 需要它 ), 它不在 /NDK-gcc 目录下, 而在下载的
NDK 目录下, ( 我们曾经把它安装到 /NDK 目录下 ) 按 google 的说法, 这个 libsupc++.a 不是它自己的,
而是其它人的. 那它是谁的呢? 这个网站 www.crystax.net 告诉你它的故事.

( 我不太明白, 为什么编译独立程序时, 不需要这个 libsupc++.a, 而编译 jni 程序时需要它. )

我们再编写一个执行脚本, 例如 cc, 放到 hello 目录下

#! /bin/sh

export NDK=/NDK
${NDK}/ndk-build clean
${NDK}/ndk-build

执行它 ./cc 就编译 hello\libs\armeabi\task.so 出来了. hello\libs 目录是编译时产生的.

看起来文件比较多了, 它们是

hello\cc
hello\jni\Android.mk
hello\jni\task.h
hello\jni\task.cpp
hello\libs\armeabi\task.so

这只是一个简单的 JNI 例子. 代码不多. 如果真的只是这么简单的代码, 就不费神到 linux 转一圈了. 直接用
windows 版的 NDK 就够了. 到现在为止, 上面的操作对于 windows 版本的 NDK 是适用的.

但我相信, 你不会只是用 NDK 做上面的小测试程序. 你一定会有一大堆 linux C 代码, 怎么办? 先用 NDK gcc
按 gcc 老办法把它们编译成静态库, 例如 libg729A.a , libPhoneAPI.a, 然后在 Android.mk 文件加入它们


LOCAL_PATH := $(call my-dir)

 

include $(CLEAR_VARS)

 

LOCAL_MODULE    := task

LOCAL_SRC_FILES := task.cpp

 

LOCAL_LDFLAGS := -llog -lstdc++ \
  $(LOCAL_PATH)/../../other/lib/libPhoneAPI.a \

  $(LOCAL_PATH)/../codec/lib/libg729A.a \

  ${NDK}/sources/cxx-stl/gnu-libstdc++/libs/armeabi/libsupc++.a

  

include $(BUILD_SHARED_LIBRARY)

这样就可以了. 如果 ndk-build 报告说谁谁谁找不到, 用对付 gcc 的办法, 把几个库复制几次

LOCAL_LDFLAGS := -llog -lstdc++ \
  $(LOCAL_PATH)/../../other/lib/libPhoneAPI.a \

  $(LOCAL_PATH)/../codec/lib/libg729A.a \

  $(LOCAL_PATH)/../../other/lib/libPhoneAPI.a \

  $(LOCAL_PATH)/../../other/lib/libPhoneAPI.a \

  ${NDK}/sources/cxx-stl/gnu-libstdc++/libs/armeabi/libsupc++.a

ndk-build 是一个 shell 脚本, 最终还是由 gcc 来编译的. Android.mk 就是它的 makefile

现在做大型的软件项目, 也没有任何问题了. 新工具, 老办法. 有点新的东西是 jni 目录和 ndk-build

把最后生成的 libtask.so 文件 copy 回到 windows 下的 hello\libs\armeabi 目录下使用了. ant 打包时,
它会自动把 libs 目录下的文件打进去.

如果还用到了其它的 .so 文件( 例如第三方提供的 .so 文件 ), 都放到 hello\libs\armeabi 目录下.

别忘记, 在 task.java 文件中加入下面一行,

 static { System.loadLibrary("task"); }

告诉 android, 程序开始就加载 libtask.so 动态库. 这样 task.java 文件成这个样子

package com.testing ;

public class task
{
 static { System.loadLibrary("task"); }

 public native int  taska( );
 public native void taskb( int b );
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值