编译工具 CMake 以及 Android 上 JNI 的使用介绍。

编译工具 CMake

在Android Studio 2.2 之后,工具中增加了 CMake 的支持,于是我们有两种选择来编译 c/c++ 代码。一个是 ndk-build + Android.mk + Application.mk 组合,另一个是 CMake + CMakeLists.txt 组合。这2个组合与 Android 代码和 c/c++ 代码无关,只是不同的构建脚本和构建命令。

环境配置

Android Studio 的 SDK Tools 安装

  • CMake
  • LLDB
  • NDK

Hello World

先新建一个项目,记得要勾选 C++ support,看一下 Android Studio 自动生成的使用了 JNI 的项目是什么样子的。

可以看到,与普通 Android 项目不同的是,支持 C++ 的项目在 app 目录下多了一个 .externalNativeBuild 编译目录与 CMakeLists.txt,main 目录下多了 cpp 目录。

CMakeLists 文件

关于 CMakeLists 文件的作用,我的理解是它指定了编译 c++ 库时所用到的一些配置,先来看看项目里 CMakeList.txt 文件:

// 去掉注释
cmake_minimum_required(VERSION 3.4.1)
add_library(native-lib SHARED src/main/cpp/native-lib.cpp )
find_library(log-lib log)
target_link_libraries(native-lib ${log-lib} )
  • cmake_minimum_required(VERSION 3.4.1)

    允许构建的最低版本
  • add_library(name path)

    生成链接库,SHARED 表示生成动态库, STATIC表示生成静态库。并指定了参与编译的文件路径
  • find_library(log-lib log)

    添加在编译本地文件时依赖的库(log),并指定别名(log-lib)
  • target_link_libraries(lib1 lib2 ...)

    链接库,这里链接了我们自己的库 native-lib 与 log 库

默认的 so 库输出目录为 app/build/intermediates/cmake/debug/obj/${abi} 下,可以在 CMakeLists 中指定输出目录

#设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

再来看 cpp 目录下的 native-lib.cpp 文件:

#include <jni.h>
#include <string> extern "C"
JNIEXPORT jstring JNICALL
Java_com_yazhidev_cmakedemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

第一次看到 .cpp 格式的源文件肯定有点摸不着头脑,Java 与 C++ 是如何通信的呢?答案就是 JNI。

JNI 规范

JNI (Java Native Interface,Java本地接口)是一种编程框架,使得 Java 虚拟机中的 Java 程序可以调用本地应用/或库,也可以被其他程序调用。

从上面的 native-lib.cpp 文件我们可以一窥 JNI 中 C/C++ 的使用规范:

  • #include

    C 语音中使用 #include <> 直接到系统指定的目录下查找文件,我将其理解为类似 Java 中的导包。JNI 中首先头部需要引入 <jni.h> ,由于使用到了字符串,还导入了 <string>

  • JNIEXPORT

    JNIEXPORT 和 JNICALL 都是 JNI 的关键字,表示此函数是要被 JNI 调用的。

  • jstring

    是 JNI 中作为中介使 JAVA 的 String 与 C/C++ 的 String 交互的数据类型,JNI的数据类型包含两种,分别是基本类型和引用类型。

  • jobject

    指代调用该方法的对象。如果 Java 中该 native 方法是静态的,则指代该类,即 XXX.class。

  • JNIEnv

    这个env可以看做是 JNI 接口本身的一个对象,在头部引入的 jni.h 头文件中存在着大量被封装好的函数,这些函数也是 JNI 编程中经常被使用到的,要想调用这些函数就需要使用JNIEnv这个对象。除了上面使用到的传递返回值给 Java,还有获取类的 class 类型:evn->GetObjectClass(),改变 Java 中对象的某个变量的值 evn-> SetIntField(...) 等方法。

  • 命名方式

    Java_com_yazhidev_cmakedemo_MainActivity_stringFromJNI(),JNI 中对命名有规定,命名规范为:Java_包名_class_函数名,包名中的 . 也要改为 _,对应的,在 Java 中引用 Native 函数也需要声明关键字 native。

  • std::string、NewStringUTF

    这是 C++ 中字符串的一些写法,需要用时去翻一下语法,不多延伸。

以上只提到了项目里使用到的一些规范和注意点。下面通过写个 demo 实际操作一下。

实战

通过 JNI 对图片做变色处理。

jnigraphics 库

这里要使用到 NDK 里提供的 jnigraphics 库,该库

提供了基于 C/C++ 的接口,可以访问 Android 中的 Bitmap 的像素缓冲区(bitmap buffers)。

头文件中引入 android/bitmap.h,其典型用法如下(摘至 android/bitmap.h 详解):

a) 用 AndroidBitmap_getInfo() 函数从位图句柄(从JNI得到)获得信息(宽度、高度、像素格式)

b) 用 AndroidBitmap_lockPixels() 对像素缓存上锁,即获得该缓存的指针。

c) 用C/C++ 对这个缓冲区进行读写

d) 用 AndroidBitmap_unlockPixels() 解锁

我们利用该用法对 bitmap 做处理。

新建 Module

首先新建个 module,并新建类 BitmapUtil:

static {
// 不要忘记加载库
System.loadLibrary("bitmap-util");
} public class BitmapUtil {
public static native void processBitmap(Bitmap bitmap;
}

并在 main 目录下新建 cpp 目录,新建类 bitmap-util.cpp:

#include <jni.h>
#include <android/bitmap.h> extern "C" JNIEXPORT void JNICALL
Java_com_yazhidev_ndkdemo_BitmapUtil_processBitmap(JNIEnv *env, jobject /* this */, jobject bitmap) {
//构造 AndroidBitmapInfo
AndroidBitmapInfo info = {0};
//将 bitmp 的信息填充给 info
AndroidBitmap_getInfo(env, bitmap, &info);
int *buf=NULL;
//对 bitmap 解码并获取解码后的像素保存在内存中的地址指针,赋值给 srcBuf
AndroidBitmap_lockPixels(env, bitmap, (void **) &buf);
//处理像素
int w = info.width;
int h = info.height;
int32_t *srcPixs = (int32_t *) buf;
int alpha = 0xFF << 24;
int i, j;
int color;
int red;
int green;
int blue;
for (i = 0; i < h; i++) {
for (j = 0; j < w; j++) {
// get the color of per pixel
color = srcPixs[w * i + j];
red = ((color & 0x00FF0000) >> 16);
green = ((color & 0x0000FF00) >> 8);
blue = color & 0x000000FF;
color = (red + green + blue) / 3;
color = alpha | (color << 16) | (color << 8) | color;
srcPixs[w * i + j] = color;
}
}
//释放锁定,显示出被修改的像素数据
AndroidBitmap_unlockPixels(env, bitmap);
}

module 根目录下新建 CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.4.1)
add_library(bitmap-util SHARED src/main/cpp/bitmap-util.cpp )
# 链接 jnigraphics 库
target_link_libraries(native-lib jnigraphics)

在 module 的 build.gradle 中引用 CMakeLists 文件:

android {
...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}

Java 中调用:

// Kotlin image -> ImageView(android:id="@+id/image")
val drawable = resources.getDrawable(R.mipmap.google) as BitmapDrawable
val bitmap = drawable.bitmap
BitmapUtil.processBitmap(bitmap)
image.setImageBitmap(bitmap)

遇到的问题

java.lang.UnsatisfiedLinkError: No implementation found

extern "C"

扩展

项目中导入 so 库

在使用 JNI 时有时可能只有编译好的 so 库,那么如何在项目中使用 so 库呢?

右键 app 目录,选择 new - Folder -JNI Folder,新建一个 JNI 目录用于存放 so 文件。

so 库(CPU)的兼容

使用 CMake 编译 so 库时,可通过配置 gradle 文件指定编译的 so 库架构

android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ""
// 生成.so库的目标平台
abiFilters "armeabi-v7a", "armeabi", "x86"
}
}
}
}

对于CPU来说,不同的架构并不意味着一定互不兼容,根据目前Android共支持七种不同类型的CPU架构,其兼容特点可总结如下:

armeabi设备只兼容armeabi;

armeabi-v7a设备兼容armeabi-v7a、armeabi;

arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;

X86设备兼容X86、armeabi;

X86_64设备兼容X86_64、X86、armeabi;

mips64设备兼容mips64、mips;

mips只兼容mips;

根据以上的兼容总结,我们还可以得到一些规律:

armeabi的SO文件基本上可以说是万金油,它能运行在除了mips和mips64的设备上,但在非armeabi设备上运行性能还是有所损耗;

64位的CPU架构总能向下兼容其对应的32位指令集,如:x86_64兼容X86,arm64-v8a兼容armeabi-v7a,mips64兼容mips。

更多 so 文件的信息可参考:Android SO文件的兼容和适配

参考

AndroidStudio项目CMakeLists解析

JNI技术规范

Android NDK之旅——图片高斯模糊

Jni接口-深入研究参数的传递(一)

android/bitmap.h 详解

拥抱 C/C++ : Android JNI 的使用的更多相关文章

  1. [转载]—— Android JNI知识点

    Java Native Interface (JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互.JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 ...

  2. Android JNI 和 NDK

    1.Android NDK 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第 ...

  3. Android jni简便开发流程

    <Android jni helloworld>中介绍了开发jni helloworld的步骤,本文将介绍jni简便开发流程 ① 写java代码 native 声明本地方法 ② 添加本地支 ...

  4. Android jni系统变量、函数、接口定义汇总

    在做Android jni开发时,jni为我们提供了哪些函数.接口.变量,有时候一头雾水,今天就把jni.h中定义的所有内容列出来,供自己查阅: /* * Copyright (C) 2006 The ...

  5. android JNI调用(转)

    Android jni开发资料--NDK环境搭建 android开发人员注意了 谷歌改良了ndk的开发流程,对于Windows环境下NDK的开发,如果使用的NDK是r7之前的版本,必须要安装Cygwi ...

  6. Android JNI如何调用第三方库

    http://www.2cto.com/kf/201504/388764.html Android JNI找不到第三方库的解决方案 cannot load library 最近做一个jni项目,拿到的 ...

  7. Android JNI之JAVA与C++对象建立对称关联(JNI优化设计,确保JNI调用的稳定性)

    转载请声明:原文转自:http://www.cnblogs.com/xiezie/p/5930503.html Android JNI之JAVA与C++对象建立对称关联 1.JAVA对象持有C++对象 ...

  8. 利用gdb 调试android jni c动态库

    http://blog.dornea.nu/2015/07/01/debugging-android-native-shared-libraries/ Since I haven't done thi ...

  9. Android JNI使用方法

    经过几天的努力终于搞定了android JNI部分,下面将我的这个小程序和大家分享一下.android JNI是连接android Java部分和C/C++部分的纽带,完整使用JNI需要Java代码和 ...

随机推荐

  1. 获取Win和Linux系统启动时间,类似uptime功能,用于判断是否修改过系统时间

    目录 前言 测试代码 Win测试 Linux测试 总结 前言 有时候需要判断系统是否有修改过时间,最简单的方法就是获取当前时间A,然后sleep X秒,然后获取 时间B,如果 时间B - 时间A ≠ ...

  2. 在EXCEL带有字母的数字下拉如何能自动排序

    在excel中0,1,2,3,4,5,6,7,8,9会自动排序,a,b,c,d,e,f,g.....会自动排序,所以可以分布来实现. 例如排序:fish1a.png,fish1b.png,fish1c ...

  3. Beta冲刺随笔——Day_Three

    这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 Beta 冲刺 这个作业的目标 团队进行Beta冲刺 作业正文 正文 其他参考文献 无 今日事今日毕 林涛: ...

  4. moviepy音视频开发:音频剪辑基类AudioClip详解

    ☞ ░ 前往老猿Python博文目录 ░ 一.背景知识介绍 1.1.声音三要素: 音调:人耳对声音高低的感觉称为音调(也叫音频).音调主要与声波的频率有关.声波的频率高,则音调也高. 音量:也就是响度 ...

  5. 第4.4节 Python解析与推导:列表解析、字典解析、集合解析

    一.    引言 经过前几个章节的介绍,终于把与列表解析的前置内容介绍完了,本节老猿将列表解析.字典解析.集合解析进行统一的介绍. 前面章节老猿好几次说到了要介绍列表解析,但老猿认为涉及知识层面比较多 ...

  6. PyQt(Python+Qt)学习随笔:QScrollArea滚动区域的alignment属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 滚动区域的alignment属性对应QScrollArea的alignment属性,用于控制滚动区域 ...

  7. requests的再次学习

    title: requests模块的再次理解 date: 2020-03-10 22:44:26 tags: 1.response的解析 当requests模块发送请求后,我们会对其响应的数据也就是r ...

  8. uni与小程序,vue的区别

    标签区别 uni使用小程序的标签,vue使用web端的标签 标签名变化的: 标签描述\类别 vue uniapp 文本 span\font text 链接 a navigator/ router-li ...

  9. Alpha冲刺——序言篇(任务与计划)

    Alpha冲刺--序言篇(任务与计划) 1.整个项目预期的任务量 需求规格说明书 架构设计,原型设计,原型改进(给目标用户展现原型,并进一步理解需求) 编码规范完成.平台环境搭建完成.初步架构搭建 队 ...

  10. Alpha冲刺阶段Day4

    [Alpha冲刺阶段]Scrum Meeting Daily4 1.会议简述 会议开展时间 2020/5/25   7:30-7:50 PM 会议基本内容摘要 讨论了各自任务完成情况以及明日计划 参与 ...