专栏名称: 鸿洋
你好,欢迎关注鸿洋的公众号,每天为您推送高质量文章,让你每天都能涨知识。点击历史消息,查看所有已推送的文章,喜欢可以置顶本公众号。此外,本公众号支持投稿,如果你有原创的文章,希望通过本公众号发布,欢迎投稿。
目录
相关文章推荐
开发者全社区  ·  蔚来多部门裁员,裁减10%-50%,20分钟完成 ·  15 小时前  
开发者全社区  ·  商·玉高冠鸟柄形器 ·  昨天  
开发者全社区  ·  太奇葩了!离职后被前公司调查去向...... ·  昨天  
鸿洋  ·  Android×AI 技术周刊 - 第3期 ·  2 天前  
51好读  ›  专栏  ›  鸿洋

Android下玩JNI的新老三种姿势

鸿洋  · 公众号  · android  · 2017-05-26 07:28

正文

本文作者


本文由 马北剑西 投稿。

马北剑西 的博客地址:

http://blog.csdn.net/mabeijianxi

https://github.com/mabeijianxi


说明:本篇不撸代码,只玩编译,其包含了Android studio 2.2最新的JNI玩法

编译环境:macOS 10.12.3

工具包含:Android Studio 2.2  NDK-r14


在Android下要玩jni首先下载ndk是必须的,可以直接去 https://developer.android.google.cn/ndk/downloads/index.html 下载,当然我们家AS为开发者也提供了便捷


只需如图勾选然后OK即可,我的版本是r14,值得一提的是 google ndk-build 命令在 r13 后默认使用 Clang,并将在后续版本中移除 GCC,其编译速度更快、编译产出更小、出错提示更友好。


1

徒手编写Android.mk然后ndk-build编译


这种编译其实是用make工具来玩的,在 Linux 徒手写并编译过c的应该很清楚,通过编写makefile,然后再用make编译已经比不停的用gcc命令逐个编译要爽很多,但是 makefile 的编写还是有点蛋疼。


程序员都是化繁为简善解人意的,通过 ndk 工具我们无需自己写 makefile 了,现在你只要安心撸自己关心的代码就行了。

1、在main下新建 jni 目录,如图:



2、再新建一个 c 或者 c++ 文件,如图:



3、在Java里面声明个 native 方法


private native String jniTellMeWhy(String hiJni);


4、copy全类名



然后去我们新建的那个 hi_jni.cpp 里面去声明一个方法,这里就添加头文件了,直接干。


命名规则是死的,粘贴一下把"."换成" _ "再加上“ _方法名 ” ,我们是有返回值的且是个 String 的,对应的就是 jstring ,最前面拼上固定的“ Java_ ”。


我们传入了一个参数,但是规定是每个函数默认都会两个参数,一个是 JNIEnv 指针类型的结构体,一个是调用者对象,比如我们这里就是MainActivity对象,其实玩过 c++ 的都知道里面每个函数其实默认也会传入个 this 指针的,不然一个类可以有那么多对象怎么知道是哪个对象调用的?


言归正传,还有一个参数就是我们传入的 String 值了。如下:



我这里用的是 c++ 所以得加上 extern "C" ,原因很简单,在 C++ 中函数在编译的时候会拼接上参数,这也是 c++ 中函数重载的处理机制,比如一个 set(int a) 和一个 set(int a,int b) ,在编译的时候就变成了 set_int 与 set_int_int ,我们加上extern ”C“ 就表示大爷想按照C来编译,所以函数名字后面就不会拼接上参数类型了。

5、在jni目录下新建两个文件一个叫 Android.mk ,一个叫 Application.mk 。

6、编写Android.mk,最简单的编写如下,后面将介绍一些稍微牛逼点点的。



LOCAL_PATH :是得最先配置的,它用于在开发tree中查找源文件。

include $(CLEAR_VARS) : CLEAR_VARS 变量指向特殊 GNU Makefile ,可为您清除许多 LOCAL_XXX 变量,例如:

LOCAL_MODULE 、 LOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES 。 请注意,它不会清除 LOCAL_PATH.。

LOCAL_PATH :此变量必须保留其值,因为系统在单一 GNU Make 执行环境(其中所有变量都是全局的)中解析所有构建控制文件。 在描述每个模块之前,必须声明(重新声明)此变量。

LOCAL_MODULE :存储您要构建的模块的名称,并指定想生成的 so 叫什么名字。当然生成产物的时候前面会自动拼接上 lib,后面会自动拼接上 .so 。

LOCAL_SRC_FILES :要编译的源文件,多个文件以空格分开即可。当导入 .a 或者 .so 文件的时候一个模块只能添加一个文件,后面将演示。

LOCAL_C_INCLUDES :可以使用此可选变量指定相对于 NDK root 目录的路径列表,以便在编译所有源文件(C、C++ 和 Assembly)时添加到 include 搜索路径,通常是原文件地址、头文件地址等。
LOCAL_LDLIBS :这里是添加一个本地依赖库,比如可以添加一个 log 库,当然我没用到就注释了。

include $(BUILD_SHARED_LIBRARY) :这一行帮助系统将所有内容连接到一起, BUILD_SHARED_LIBRARY 变量指向GNU Makefile 脚本,用于收集您自最近 include 后在 LOCAL_XXX 变量中定义的所有信息。 此脚本确定要构建的内容及其操作方法。 BUILD_SHARED_LIBRARY 代表动态库, BUILD_STATIC_LIBRARY 代表静态库 。

7、编写 Application.mk :


# 指定生成哪些cpu架构的库
APP_ABI := armeabi-v7a
# 此变量包含目标 Android 平台的名称
APP_PLATFORM := android-22


8、在 jni 目录下面打开命令行工具,然后执行 ndk-build ,即可在 libs 目录下得到产物:




9、把产物放到 jniLibs 下面(当然你可以在采用 builde.gradle 的 sourceSets 里面改变其路径, jniLibs.srcDirs=['src/main/libs']) 。



10、 Java 层调用:



结果是:



添加一预构建库编译:

其实和咋们Android中的添加依赖差不多,我们要编译一个原生库,这个库的功能是可以按照 H264 编码视频,然后我们不想自己写那么多代码,所以我们引入开源的 libx264 ,我们拿到编译好的 libx264.a 或者 libx264.so 和其头文件,这个时候我们只需要导入一起编译即可: 目录结构如图



有了头文件我们即可include入我们的 hi_jni.cpp 里面自由蹂蹑。

接下来修改下 Android.mk 。



如果我们导入 .a 的静态库的话第一组就如上所写,每添加一组的时候必须执行 include $(CLEAR_VARS) ,LOCAL_MODULE 的值就各自喜好了,第一组的 LOCAL_SRC_FILES 我们指向想导入的静态库地址,第一组的LOCAL_C_INCLUDES 指向其头文件地址,然后 include $(PREBUILT_STATIC_LIBRARY) 代表生成静态预构建。


我们在第二组中引用第一组的静态预构建也就是 LOCAL_STATIC_LIBRARIES := x264 ,引用动态预构建只需把 STATIC 修改为SHARED 即可。


配置完成即可在当前目录打开命令行执行 ndk-build 命令生成产物。如果第一组你指定的是 .so 的动态库,使用的时候也得在 java 层 System.loadLibrary("x264") 。


2

通过配置AS中build.gradle来编译


这种方式比上一中又简化了很多,无需再自己编写 Android.mk 了,但原理都是一样的。


1、在 main 下新建 jni 目录,如图:



2、再新建一个 c 或者 c++ 文件,如图:



3、找到你项目的 gradle.properties ,添加一行 android.useDeprecatedNdk=true

4、打开你主 Module 的 build.gradle ,在 defaultConfig 里添加:

ndk{
    moduleName 'hi_jni'
    abiFilter 'armeabi-v7a'
}


名如其实, moduleName 是你给生成的 So 取的名字,当然它会在前面拼接上 “ lib ”,会在后面拼接上 .so ,于是生成的名字就 libhi_jni.so , abiFilter 嘛就是你想保留哪些架构类型的 so ,一般 arm 的就够玩了,当然除了这些还有很多可配参数,比如想添加个日志库来玩玩,那就添加这行呗: IdLibs “log“ 。


5、在Java里面声明个 native 方法


private native String jniTellMeWhy(String hiJni);


6、copy全类名



然后去我们新建的那个 hi_jni.cpp 里面去声明一个方法,这里就添加头文件了,直接干。


命名规则是死的,粘贴一下把"."换成" _ "再加上“ _方法名 ” ,我们是有返回值的且是个 String 的,对应的就是 jstring ,最前面拼上固定的“ Java_ ”。







请到「今天看啥」查看全文