知其然,知其因此然;方能以不变,应其万变。----张风捷特烈
java
前言
笔者看了一些NDK的项目。一些教程不是HelloWord就是直接整FFmpeg或OpenCV,可谓一个天一个地,并且目录结构和Android3.5的默认结构并非太一致,一直没找到什么合心的文章。故写此文链接这天地,来总结一下在NDK开发以前你应知道的东西。linux
在此以前,先划分三类人,若是不认清本身是什么角色(垃圾)就去玩NDK,你会很糟心:
user
: 纯粹.so连接库使用者(伸手党)
creator
: 纯粹ndk开发者,创做.so连接库(创做家)
designer
: 在现有的.so上本身开发.so连接库实现特定功能(程序设计师)android
本文内容
1.本文将以user、creator、designer三者的视角来看NDK
2.AndroidStdio3.5的默认目录结构
3.有现成的C++代码,如何让Android调用它?
4.arm64-v8a、armeabi-v7a、x8六、x86_64分别是干吗的?
5.动态连接库.so是什么鬼,如何从c/c++生成.so?
6.libs,jniLibs,jin目录到底该怎么放?如何自定义文件放置的位置?
7.一些让人糟心的异常
复制代码
前置知识
也许你很怕C++,就像你
在新手村被3级的boss虐到心理阴影
,可是你如今已经50级了,还怕曾经虐你的3级的boss吗? 建议阅读: [- C++趣玩篇1 -] 从打印开始提及 ,这篇对本文很重要, 是简单,也颇有趣。如今状况如此:上篇中C++实现了一个打印脸的类,我想在Android中使用它。ios
纯粹.so使用者
(User)当你只是单纯的使用
动态连接库.so
中的已有功能,也就是传说中的伸手党。
那你与NDK只是擦肩而过,并不须要理会C/C++,也不须要建立一个NDK的项目,甚至连JNI都有现成的。
你所须要作的只是在main下新建jniLibs
,通过测试,其为默认的.so成放置地
,此时gradle文件你能够一字不动。
c++
俗话说
拿人家手短,吃人家嘴软
。因为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);
}
复制代码
这个库是等会要创造的,这里先来演示。
System.loadLibrary
指定库名
其中库全名为libtoly_facer-lib.so
,加载时toly_facer-lib
便可
这样在上一篇[- C++趣玩篇1 -] 从打印开始提及中实现的打印类就能够在Android中使用。bash
public class MainActivity extends AppCompatActivity {
static {//加载类库
System.loadLibrary("toly_facer-lib");
}
@Override
protected 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
架构
上一篇中已经完成了C++类app
头文件
--->[app/src/main/cpp/Facer.h]----
//
// Created by 张风捷特烈 on 2019/10/3.
//
#include <iostream>
using namespace std;
#ifndef TOLYC_FACER_H
#define TOLYC_FACER_H
class 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;
}
复制代码
新建
Native C++
的项目以后,main文件夹下会有cpp文件夹,这就是C++代码的家
若是直接将两个Facer文件拷贝进去,会飘红。由于尚未在CmakeLists中进行配置
cmake_minimum_required(VERSION 3.4.1)
add_library(toly_facer-lib SHARED
toly_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})
复制代码
此方法所属类名、包名对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转化为string
env->GetStringUTFChars(top, 0),
env->GetStringUTFChars(bottom, 0),
env->GetStringUTFChars(brow, 0),
env->GetStringUTFChars(eyes, 0)
);
return env->NewStringUTF(facer.getFace().c_str());
}
复制代码
基本上流程就是这样。
arm64-v8a、armeabi-v7a、x8六、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文件
能够经过
app下的build.gradle
来指定编译的.so类型
注意只有这四种类中
,之前不少项目中存在abiFilters 'armeabi'
但如今会崩
android {
defaultConfig {
externalNativeBuild {
cmake {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
复制代码
这样清一下项目,再编译出来的只有
'armeabi-v7a', 'arm64-v8a'
此时运行到模拟器上,会发现找不到类库,则说明模拟器去X86的。运行到真机无误,则说明真机是arm的
.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里愉快的玩耍了。
我的建议
习惯优于配置
,用默认挺好的。若是你是很是有个性的...也能够在gradle里进行制定
虽然你也许不会用到,可是看一下,看到要认得,不至一脸蒙圈。
对于使用者,能够随意指定盛放.so
的文件夹,须要在app下的build.gradle
配置
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['target']//指定so库的位置,加载so库
}
}
}
复制代码
对于创造者,也可使用
jni.srcDirs
来指定C++代码盛放的位置
sourceSets.main{
jni.srcDirs = ["src/main/cpp"]
}
复制代码
俗话说
难的不是重写,而是对烂代码的重构
,有时候修改比创做更难
已有的.so文件
但功能上又须要定制,因而第三类就诞生了,也是最头疼的
其实FFmpeg和OpenCV等都是这第三类,用已存在事物去构建新事物,即是设计。
算法和核心代码已经实现,咱们须要作的是结合业务进行接口封装及方法调用
这里我就用OpenCV的使用来进行演示: 你须要建立的是Native C++项目
(Opencv下载什么的,不废话了,详见:OpenCV专题1 - AndroidStudio的JNI工程及引用OpenCV)
这时,你是设计者,兼具创造者和使用者两重角色。但比纯粹的创造要简单,比纯粹的使用要难。
这时能够经过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_cv
lib_opencv
jnigraphics
log
)
复制代码
你能够定义一个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);//将图片的像素信息灰度化盛放在dstMat
return createBitmap(env,dstMat,argb8888);//使用dstMat建立一个Bitmap对象
}
复制代码
笔者也并不是一路畅通无阻,走的坑也挺多,下面几个坑来给你说说
仔细排查
CmakeLists
,多是.so文件的路径不对
仔细排查
CmakeLists
,多是你的C++代码文件路径不对
说明你的库加载异常,看看你的库名有没有写对
说明你的JNI接口和.so比匹配,自行匹配放到相应包名下
待续...
因此,在决心奋战NDK的时候,先认清本身是什么角色(
垃圾
),才好分类。
Creator太过遥远,我就想作个安安静静的Designer。
我一直在找一篇这样的文章,可是没找到。因此本身写了一篇,但愿对你有所帮助。
我是张风捷特烈
,若是有什么想要交流的,欢迎留言。也能够加微信:zdl1994328