知其然,知其所以然;方能以不变,应其万变。----张风捷特烈

NDK系列文章:
  • [-NDK 导引篇 -] 在NDK开发之前你应知道的东西
  • OpenCV专题1 - AndroidStudio的JNI工程及引用OpenCV
  • OpenCV专题2 - 人脸检测+自动尺寸裁剪

  • 前言

笔者看了一些NDK的项目。一些教程不是HelloWord就是直接整FFmpeg或OpenCV,可谓一个天一个地,而且目录结构和Android3.5的默认结构并不是太一致,一直没找到什么合心的文章。故写此文连接这天地,来总结一下在NDK开发之前你应知道的东西。


  • 在此之前,先划分三类人,如果不认清自己是什么角色(垃圾)就去玩NDK,你会很糟心:

user : 纯粹.so链接库使用者(伸手党)
creator : 纯粹ndk开发者,创作.so链接库(创作家)
designer : 在现有的.so上自己开发.so链接库实现特定功能(程序设计师)


  • 本文内容
1.本文将以user、creator、designer三者的视角来看NDK
2.AndroidStdio3.5的默认目录结构
3.有现成的C++代码,如何让Android调用它?
4.arm64-v8a、armeabi-v7a、x86、x86_64分别是干嘛的?
5.动态链接库.so是什么鬼,如何从c/c++生成.so?
6.libs,jniLibs,jin目录到底该怎么放?如何自定义文件放置的位置?
7.一些让人糟心的异常
复制代码

  • 前置知识

也许你很怕C++,就像你在新手村被3级的boss虐到心理阴影,但是你现在已经50级了,还怕曾经虐你的3级的boss吗? 建议阅读: [- C++趣玩篇1 -] 从打印开始说起 ,这篇对本文很重要, 是简单,也很有趣。现在情况如此:上篇中C++实现了一个打印脸的类,我想在Android中使用它。


一、对于纯粹.so使用者(User)

1.目录结构

当你只是单纯的使用动态链接库.so中的已有功能,也就是传说中的伸手党。
那你与NDK只是擦肩而过,并不需要理会C/C++,也不需要创建一个NDK的项目,甚至连JNI都有现成的。
你所需要做的只是在main下新建jniLibs,经过测试,其为默认的.so成放置地此时gradle文件你可以一字不动。


2.JNI接口定义

俗话说拿人家手短,吃人家嘴软。由于JNI是根据包名找到C/C++函数的,使用时必须和creator定义的接口完全一致(包括包名)。

---->[com.toly1994.jni_creator.Facer]--by 张风捷特烈-----
package com.toly1994.jni_creator;
public class Facer {public static native String getFacer( String top, String bottom, String brow, String eyes);
}
复制代码

3.库的使用

这个库是等会要创造的,这里先来演示。System.loadLibrary指定库名
其中库全名为libtoly_facer-lib.so,加载时toly_facer-lib即可
这样在上一篇[- C++趣玩篇1 -] 从打印开始说起中实现的打印类就可以在Android中使用。

public class MainActivity extends AppCompatActivity {static {//加载类库System.loadLibrary("toly_facer-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TextView textView=findViewById(R.id.hello);textView.setTextSize(30);//通过native接口getFacer使用类库中C++方法textView.setText(Facer.getFacer("-", "-", "~", "X"));}
}
复制代码

OK,现在80%的人问题解决了。(手动搞笑)


二、对于纯粹ndk开发者(Creator)

如果你有现成的C++代码想要直接用在Android上,或者想要手撸个什么高效的框架
或者想要让你的源码不容易破解,那么废话不多说,就开整吧。哥敬你是条好汉。
现在你需要创建一个Native C++ 的Android项目。这里就来实现toly_facer-lib

1.准备活动

上一篇中已经完成了C++类

  • 头文件
--->[app/src/main/cpp/Facer.h]----
//
// Created by 张风捷特烈 on 2019/10/3.
//#include <iostream>using namespace std;#ifndef TOLYC_FACER_H
#define TOLYC_FACER_Hclass Facer {
public:Facer(const string &top="#", const string &bottom="#", const string &brow="~", const string &eyes=".");~Facer();
public:string top;string bottom;string brow;string eyes;
public:void printFace() ;string getFace();
};#endif //TOLYC_FACER_H
复制代码

  • cpp实现文件
--->[app/src/main/cpp/Facer.cpp]----
//
// Created by 张风捷特烈 on 2019/10/3.
//#include "Facer.h"Facer::Facer(const string &top,const string &bottom,const string &brow,const string &eyes) : top(top),bottom(bottom),brow(brow),eyes(eyes) {}void Facer::printFace() {cout<< getFace() << endl;
}Facer::~Facer() {}string Facer::getFace() {string result;for (int i = 0; i < 10; ++i) {i != 9 ? result+=top : result+=top+"\n";}result+= "|  " +brow + "   " + brow + " |" +"\n";result+= "|  " +eyes + "   " + eyes + " |" +"\n";result+= "|    -}    |\n";for (int i = 0; i < 10; ++i) {i != 9 ? result+=bottom : result+=bottom+"\n";}return result;
}
复制代码

2.项目结构

新建Native C++ 的项目之后,main文件夹下会有cpp文件夹,这就是C++代码的家
如果直接将两个Facer文件拷贝进去,会飘红。因为还没有在CmakeLists中进行配置


3.CmakeLists中的配置
cmake_minimum_required(VERSION 3.4.1)add_library(toly_facer-lib SHAREDtoly_facer-lib.cpp Facer.h Facer.cpp)#直接加入文件find_library(log-lib log)target_link_libraries(toly_facer-lib ${log-lib})
复制代码

当然也许你肯定懒得一个个添加,可以加载cpp文件夹下的所有.cpp和.c文件

cmake_minimum_required(VERSION 3.4.1)#定义全局 my_source_path 变量
file(GLOB my_source_path${CMAKE_SOURCE_DIR}/*.cpp${CMAKE_SOURCE_DIR}/*.c)add_library(toly_facer-lib SHARED ${my_source_path})find_library(log-lib log)target_link_libraries(toly_facer-lib ${log-lib})
复制代码

4.设计JNI的native接口方法和C++实现

此方法所属类名、包名对user都至关重要。对于creator随意啦,就是任性

---->[src/main/java/com/toly1994/jni_creator/Facer.java]----
package com.toly1994.jni_creator;public class Facer {public static native String getFacer( String top, String bottom, String brow, String eyes);
}
复制代码

C++与Java的相互作用,就是Java进行输入,经C++转化将有价值的东西传给Java端

---->[src/main/cpp/toly_facer-lib.cpp]----
#include <jni.h>
#include <string>
#include "Facer.h"extern "C"
JNIEXPORT jstring JNICALL
Java_com_toly1994_jni_1creator_Facer_getFacer(JNIEnv *env, jclass clazz, jstring top,jstring bottom, jstring brow, jstring eyes) {Facer facer(//使用 env->GetStringUTFChars将jstring转化为stringenv->GetStringUTFChars(top, 0), env->GetStringUTFChars(bottom, 0), env->GetStringUTFChars(brow, 0), env->GetStringUTFChars(eyes, 0));return env->NewStringUTF(facer.getFace().c_str());
}
复制代码

基本上流程就是这样。


三、扫盲科普

1.arm64-v8a、armeabi-v7a、x86、x86_64
arm 架构注重的是续航能力:大部分的移动设备
x86 架构注重的是性能:大部分的台式机和笔记本电脑arm64-v8a :第8代、64位ARM处理器
armeabi-v7a :第7代及以上的 ARM处理器
x86:x86 架构的 CPU(Intel 的 CPU)
x86_64:x86 架构的64位 CPU(Intel 的 CPU)
复制代码

默认会编译出四种.so文件


2.配置输出的.os架构类型

可以通过app下的build.gradle来指定编译的.so类型
注意只有这四种类中,以前很多项目中存在abiFilters 'armeabi'但现在会崩

android {defaultConfig {externalNativeBuild {cmake {abiFilters 'armeabi-v7a', 'arm64-v8a'}}}
复制代码

这样清一下项目,再编译出来的只有'armeabi-v7a', 'arm64-v8a'
此时运行到模拟器上,会发现找不到类库,则说明模拟器去X86的。运行到真机无误,则说明真机是arm的


3..so文件是什么?

如果说.dll估计你会说:哦,好像见过
其实.so.dll并没有本质的区别,它们都是一个C++实现的功能团
只不过.so是用在linux上的,.dll是用在Windows上的。
如今操作系统三足鼎立,当然少不了MacOS,类似的在MacOS中有.dylib文件。
它们都是 C++动态链接库(Dynamic Link Library )

而Android作为Linux的一员,C++ 编译出的.so便是顺理成章
那如何将C++编译成.so库?这便是NDK在做的事,也是上面在做的事。
打包时gradle会将对应的.so包打到apk里,然后.so就能在linux里愉快的玩耍了。


4.如何自定义资源文件位置

个人建议习惯优于配置,用默认挺好的。如果你是非常有个性的...也可以在gradle里进行制定
虽然你也许不会用到,但是看一下,看到要认得,不至一脸蒙圈。
对于使用者,可以随意指定盛放.so的文件夹,需要在app下的build.gradle配置

android {...sourceSets {main {jniLibs.srcDirs = ['target']//指定so库的位置,加载so库}}}
复制代码

对于创造者,也可以使用jni.srcDirs来指定C++代码盛放的位置

sourceSets.main{jni.srcDirs = ["src/main/cpp"]}
复制代码

四、对于程序设计师(Designer)

俗话说难的不是重写,而是对烂代码的重构,有时候修改比创作更难
已有的.so文件但功能上又需要定制,于是第三类就诞生了,也是最头疼的
其实FFmpeg和OpenCV等都是这第三类,用已存在事物去构建新事物,便是设计。

1.项目结构

算法和核心代码已经实现,我们需要做的是结合业务进行接口封装及方法调用
这里我就用OpenCV的使用来进行演示: 你需要创建的是Native C++项目
(Opencv下载什么的,不废话了,详见:OpenCV专题1 - AndroidStudio的JNI工程及引用OpenCV)


2.你的角色

这时,你是设计者,兼具创造者和使用者两重角色。但比纯粹的创造要简单,比纯粹的使用要难。
这时可以通过CmakeLists去链接到OpenCV的.so文件,这样你就可以使用OpenCV的头文件进行功能实现

cmake_minimum_required(VERSION 3.4.1)include_directories(include)#引入include文件夹#定义全局 my_source_path 变量
# file(GLOB my_source_path ${CMAKE_SOURCE_DIR}/*.cpp ${CMAKE_SOURCE_DIR}/*.c)
aux_source_directory(. my_source_path) #上行的简化:将本文件夹下文件加入
add_library(toly_cv SHARED ${my_source_path})#添加动态链接库
add_library(lib_opencv SHARED IMPORTED)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libopencv_java4.so) #so文件位置## 在ndk中查找log库 取别名log-lib
find_library(log-lib log)
# 在ndk中查找jnigraphics库 取别名jnigraphics-lib  jnigraphics
find_library(graphics jnigraphics)target_link_libraries(toly_cvlib_opencvjnigraphicslog
)
复制代码

你可以定义一个JNI接口来暴露你在C++层实现的方法,再打包成.so供他人使用
这便是开源的魅力,比如下面的灰色图像,使用者可以拿着打出的.so包通过TolyCV来使用

---->[com.toly1994.jni_designer.TolyCV]----
public class TolyCV {public static native Bitmap grayBitmap(Bitmap bitmap,Bitmap.Config argb8888);
}---->[src/main/cpp/toly_cv.cpp]----
#include <jni.h>
#include <string>
#include "bitmap_utils.h"
#include <opencv2/imgproc/types_c.h>extern "C"
JNIEXPORT jobject JNICALL
Java_com_toly1994_jni_1designer_TolyCV_grayBitmap(JNIEnv *env, jclass clazz, jobject bitmap,jobject argb8888) {Mat srcMat;Mat dstMat;bitmap2Mat(env, bitmap, &srcMat);cvtColor(srcMat, dstMat, CV_BGR2GRAY);//将图片的像素信息灰度化盛放在dstMatreturn createBitmap(env,dstMat,argb8888);//使用dstMat创建一个Bitmap对象}
复制代码

五、让人糟心的异常

笔者也并非一路畅通无阻,走的坑也挺多,下面几个坑来给你说说

1.ninja: error: 巴拉巴拉... missing and no known rule to make it

仔细排查CmakeLists,可能是.so文件的路径不对


2.CMake Error at 巴拉巴拉... (add_library):

仔细排查CmakeLists,可能是你的C++代码文件路径不对


3.java.lang.UnsatisfiedLinkError: 巴拉巴拉... "XXX.so"

说明你的库加载异常,看看你的库名有没有写对


4. java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String 巴拉巴拉...

说明你的JNI接口和.so比匹配,自行匹配放到相应包名下

待续...


所以,在决心奋战NDK的时候,先认清自己是什么角色(垃圾),才好分类。
Creator太过遥远,我就想做个安安静静的Designer。
我一直在找一篇这样的文章,但是没找到。所以自己写了一篇,希望对你有所帮助。
我是张风捷特烈,如果有什么想要交流的,欢迎留言。也可以加微信:zdl1994328


[-NDK 导引篇 -] 在NDK开发之前你应知道的东西相关推荐

  1. NDK篇 - JNI NDK 初探

    前几天一直在忙项目,所以断更了两天,今天继续写.进入到 NDK 篇了,先来了解下 NDK 与 JNI,后面的文章将带大家来交叉编译一些成熟的开源项目. 目录: 什么是 JNI 什么是 NDK Andr ...

  2. android ndk 博客,Android Studio 2上利用NDK进行OpenCV 3.1开发

    本文主要分为两部分,第一部分采用实验性插件(Gradle Experimental Plugin)建立一个一般性的NDK应用,第二部分采用稳定版插件建立一个简单的灰度处理的OpenCV JNI应用. ...

  3. android.mk ndk编译选项优化,Android NDK 编译脚本分析 之一

    版权信息:本文为本人原创,欢迎转载,但请著明出处,并保留本版权信息. Android NDK编译脚本编写起来还是是比较简单条理的,然而它的语法和传统的linux GNU Make编译脚本的编写似乎有很 ...

  4. Android NDK 概述(Android NDK Overview)

    Android NDK 功能概述 Android NDK就是一套用于把C/C++源码编译得到的二进制机器码嵌入应用安装包的工具. Android NDK是对Android SDK的一个补充,可以帮助你 ...

  5. stm32cubeide烧写程序_stm32mp157 Cortex M4开发篇:stm32CubeIDE开发环境搭建

    写在前面: 本文章为<STM32MP1系列教程之Cortex-M4开发篇>系列中的一篇,全系列总计11篇.笔者使用的开发平台为华清远见FS-MP1A开发板(STM32MP157开发板).针 ...

  6. 单选选择才可以提交_第二篇:DJANGO开发产品选择表amp;调查问卷

    锅大虾:第一篇:DJANGO开发产品选择表&调查问卷​zhuanlan.zhihu.com 三.调查问卷部分 前端实现效果:首页 首页效果图 需求: 1.单项.多项选择,并且随意增加" ...

  7. stm32f407手册_入门篇 | STM32F407库函数开发L按键控制Led灯

    让文化·去旅行 点击上方蓝字可以关注我们哦 按键控制灯的亮与灭的效果图: 一.寄存器开发与库函数开发的优缺点 1.寄存器开发 缺点: (1)开发难度大,查阅相关手册比较多 (2)开发效率相对低,产品周 ...

  8. kotlin开发Android入门篇八Kotlin开发Android的基本使用

    基础篇:Kotlin开发Android的基本使用 使用AndroidStudio3.0及以上开发Kotlin在新建项目中勾选这个选项则会默认开发语言为Koltin,然后再gradle(Project的 ...

  9. NDK Build 用法(NDK Build)

    1.ndk-build的用法 Android NDKr4引入了一个新的.小巧的shell脚本ndk-build,来简化源码编译. 该文件位于NDK根目录,进入你的工程根目录或子目录之后,在命令行下调用 ...

  10. 豁然开朗篇:安卓开发中关于线程那些事(下篇)

    彻底搞懂线程这一块,看这一篇就够了 前言 本系列详细讲解并发的知识,从基础到底层,让大家彻底搞懂线程和锁的原理,当然里面会涉及到一些内存结构的知识,所以如果为了更好地阅读效果,也可以先去看以下这两篇: ...

最新文章

  1. Python | 一万多条拼车数据,看春运的迁徙图
  2. ViewPager 入门一
  3. linux下将硬件时钟调整为与本地时钟一致
  4. OpenCV2马拉松第22圈——Hough变换直线检測原理与实现
  5. 文件编码和RandomAccessFile文件流的使用--IO学习笔记(一)
  6. windows服务器下的ftp server搭建
  7. Com/ATL编程 一些学习链接
  8. 男人到了中年,还是没钱没人脉,就越要有这3种心理,总会有出息
  9. 虚拟化云计算-centos7上使用virt-manager安装虚拟机
  10. 羽毛球 中的 切球 与 旋球
  11. java 引用机制_Java编程开发之浅析Java引用机制
  12. pc 浏览器最小字体12px
  13. CentOS操作系统防火墙添加端口
  14. java多线程编程详细入门教程
  15. 凤凰android root x86,凤凰OS 3.0.5版 root教程
  16. Ceres-Solver
  17. Django 开发微信公众号
  18. 关于生物医学工程{血站+软件}的看法
  19. glGenTextures(GLsizei n, GLuint *textures)函数说明
  20. Solidity入门级别|用智能合约实现房屋贷款系统

热门文章

  1. ssms远程服务器地址,SSMS如何远程到SQL SERVER?
  2. php pear pecl 区别,pecl 简单介绍
  3. Zookeeper实现注册中心
  4. matlab 三角函数 积化和差,瞬间记住三角函数和差化积积化和差公式
  5. vue-ES2015:
  6. 计算机桌面图标有阴影,桌面图标有阴影怎么去掉?教你轻松解决
  7. 【excel入门学习】
  8. [云原生专题-45]:Kubesphere云治理-基于Kubernetes 构建的企业级容器平台简介与总体架构
  9. 【刷题篇】鹅厂文化衫问题
  10. STM32L051测试 (三、I2C协议设备的添加测试)