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

7 篇文章 0 订阅
7 篇文章 0 订阅

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

NDK系列文章:

  • 前言

笔者看了一些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");
    }

    @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

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_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;
} 

2.项目结构

新建Native C++ 的项目之后,main文件夹下会有cpp文件夹,这就是C++代码的家

如果直接将两个Facer文件拷贝进去,会飘红。因为还没有在CmakeLists中进行配置


3.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}) 

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转化为string
            env->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_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对象

} 

五、让人糟心的异常

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

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


本文转自 https://juejin.cn/post/6844903957504983047,如有侵权,请联系删除。# 最后

按照国际惯例,给大家分享一套十分好用的Android进阶资料:《全网最全Android开发笔记》。

整个笔记一共8大模块、729个知识点,3382页,66万字,可以说覆盖了当下Android开发最前沿的技术点,和阿里、腾讯、字节等等大厂面试看重的技术。

图片

图片

因为所包含的内容足够多,所以,这份笔记不仅仅可以用来当学习资料,还可以当工具书用。

如果你需要了解某个知识点,不管是Shift+F 搜索,还是按目录进行检索,都能用最快的速度找到你要的内容。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照整个知识体系编排的。

详细文档可以点我下载,记得点赞哦~

(一)架构师必备Java基础

1、深入理解Java泛型

2、注解深入浅出

3、并发编程

4、数据传输与序列化

5、Java虚拟机原理

6、高效IO

……

图片

(二)设计思想解读开源框架

1、热修复设计

2、插件化框架设计

3、组件化框架设计

4、图片加载框架

5、网络访问框架设计

6、RXJava响应式编程框架设计

……

图片

(三)360°全方位性能优化

1、设计思想与代码质量优化

2、程序性能优化

  • 启动速度与执行效率优化
  • 布局检测与优化
  • 内存优化
  • 耗电优化
  • 网络传输与数据储存优化
  • APK大小优化

3、开发效率优化

  • 分布式版本控制系统Git
  • 自动化构建系统Gradle

……

图片

(四)Android框架体系架构

1、高级UI晋升

2、Android内核组件

3、大型项目必备IPC

4、数据持久与序列化

5、Framework内核解析

……

图片

(五)NDK模块开发

1、NDK开发之C/C++入门

2、JNI模块开发

3、Linux编程

4、底层图片处理

5、音视频开发

6、机器学习

……

图片

(六)Flutter学习进阶

1、Flutter跨平台开发概述

2、Windows中Flutter开发环境搭建

3、编写你的第一个Flutter APP

4、Flutter Dart语言系统入门

……

图片

(七)微信小程序开发

1、小程序概述及入门

2、小程序UI开发

3、API操作

4、购物商场项目实战

……

图片

(八)kotlin从入门到精通

1、准备开始

2、基础

3、类和对象

4、函数和lambda表达式

5、其他

……

图片

好啦,这份资料就给大家介绍到这了,*有需要详细文档的小伙伴可以点我下载~~~~*

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值