Android 编译C++

news2025/7/27 12:09:09

Android 编译C++项目

  • 前言
  • 正文
    • 一、基本知识
      • ① 要做什么?
      • ② JNI是什么?
      • ③ NDK是什么?
    • 二、配置NDK
    • 三、创建新工程
      • ① 工程目录说明
      • ② 分析cpp文件
      • ③ JNI数据类型
    • 四、现有工程使用C++
      • ① 创建C++文件
      • ② 创建CMake
      • ③ 使用C++
    • 五、源码

前言

  在开发过程中,有一些底层库,算法、加解密之类的功能,不是用Java写的,而是C或者C++,而我们需要在Android工程中调用C/C++的函数达到理想的要求,那么这个时候你就需要知道怎么使用它们。

正文

  在之前我其实就遇到过这个问题,一顿操作之后可以掉用了,但是忘记记录了,导致我再次遇到这样的问题时,人傻了,就是那种似曾相似又解决不了的感觉,痛定思痛之下,我决定记录一下,好记性不如烂笔头。

  而编译C和C++项目只有两种情况,一种是已知的情况,另一种是未知的情况。分别说明一下,就是有一天老板告诉我要做一个项目,里面会用到一些C/C++的底层库,NDK等内容,你去了解一下,这属于已知情况,那么你在创建项目的时候就可以做好。但是不部分都是未知的情况,还是有一天老板告诉你之前的某个项目需要添加新的功能,软硬件相结合,硬件给你提供了C/C++的代码,让你在项目中使用,这属于未知情况。

  相对来说已知比未知要好,兵法有云:运筹帷幄之中,决胜千里之外, 所以两种情况我都会说明一下怎么处理,对你来说也许有用,也许没有用,交给缘分吧。

一、基本知识

  在写代码之前我们需要先知道要做的是什么?一些名词是否了解里面的含义,例如JNI是什么?NDK是什么?Java怎么调用C/C++?不知道没有关系,当场百度一下就知道了,有一个概念是很重要的,你就不会像无头苍蝇一样。

① 要做什么?

  我们最终的目的是通过Java能够调用C/C++的函数,获取返回值显示在Activity中,这是我们所需要的结果。

② JNI是什么?

  JNIJava Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。(PS:本段来自百度百科)

  我们总结一下:通过JNI,Java和C/C++的代码能互相调用。

③ NDK是什么?

  NDK(Native Development Kit缩写)一种基于原生程序接口的软件开发工具包,可以让您在 Android 应用中利用 C 和 C++ 代码的工具。通过此工具开发的程序直接在本地运行,而不是虚拟机。在Android中,NDK是一系列工具的集合,主要用于扩展Android SDK。NDK提供了一系列的工具可以帮助开发者快速的开发C或C++的动态库,并能自动将so和Java应用一起打包成apk。同时,NDK还集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so文件。(PS:本段来自知乎)

  我们总结一下:通过NDK,可以创建so文件,可以将so文件和Java代码一起打包成apk。

二、配置NDK

  如果你是新安装的Android Studio,那么它里面默认是没有NDK配置,File → Settings… → Android SDK 。

在这里插入图片描述

  切换到SDK Tools,然后找一下NDK和CMake两个工具,我这里是已经下载了,这里Apply是不可用的,你如果没有下载配置就勾选上,点击Apply会有弹窗提示你要下载那些内容。如果你想下载指定版本,就勾选上Show Package Details ,就能看到其他的工具版本了,例如:

在这里插入图片描述

三、创建新工程

下面我们创建支持 C/C++ 的新项目,这里我们在创建工程的时候选择Native C++,如下图所示:

在这里插入图片描述

  点击Next,输入工程名,这里有这样一句话:Creates a new project with an Empty Activity configured to use JNI,意思是创建一个配置为使用jni的空活动的新项目。

在这里插入图片描述

点击Next,然后选择C++的版本,你可以使用默认,也可以用其他的版本。

在这里插入图片描述

这里我们就使用默认的,点击Finish完成工程创建。

在这里插入图片描述

  创建工程出现问题了,这里的错误意思是在Android Studio中使用SDK管理器安装缺少的组件cmake 3.18.1。也就是说我们虽然安装了Cmake,但是安装的版本不符合这个NDK的编译要求。

在这里插入图片描述

勾选上这个需要的版本,点击Apply,然后出现弹窗提示,点击OK,之后就是下载了。

在这里插入图片描述

下载完点击Finish,在回到SDK管理窗口点击Apply,最后看到工程窗口,并没有自动去编译。

在这里插入图片描述

你可以点击这个图标或者Try Again,再编译一次。

在这里插入图片描述

① 工程目录说明

出现这样的字样就代表编译成功了,也意味着我们的项目创建成功了,我们来看看工程目录。

在这里插入图片描述

  • cpp
    这里面就是关于C++的一些配置,我们可以在这里面写C/C++的代码。
  • includes
    它里面就是项目所使用ndk的版本,可能会多个ndk版本对应一个cmake版本。
  • CMakeLists.txt
    这是一个配置文件,你可以理解为build.gradle文件,主要作用就是配置C++。
  • native-lib.cpp
    这是一个C++文件,里面就是C++代码了,我们需要详细的了解这个文件。
  • app下的build.gradle
    在这个gradle也配置Native,在里面可以找到这样一段代码:
	externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }

就是配置一下C++的Build文件,下面我们先运行一下看看。

在这里插入图片描述

运行没有问题,下面我们需要分析一下,打开MainActivity。

public class MainActivity extends AppCompatActivity {

    // Used to load the 'studynative' library on application startup.
    static {
        System.loadLibrary("studynative");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'studynative' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

  这里的代码有几个关键点,第一个是加载库,System.loadLibrary("studynative");,这里的studynative你可以在CMakeList.txt中看到,然后我们写了一个stringFromJNI()方法,用于调用C++的代码,得到一个String的返回值,然后设置在TextView上,MainActivity基本的内容分析完成了,下面我们需要分析一下这个stringFromJNI函数是怎么调用C++的,看一下native-lib.cpp文件。

② 分析cpp文件

首先我们看一下这个cpp文件的完整代码,如下所示:

#include <jni.h>
#include <string>

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

  #include,是包含头文件命令,这两个就是头文件,这两句代码就是说在这个cpp文件的这个位置插入这两个头文件的代码。

  extern "C",用于避免编绎器按照C++的方式去编绎C函数。

  JNIEXPORT,用来表示该函数是否可导出(即:方法的可见性,有一些类似于public修饰符)。

  JNICALL,用来表示函数的调用规范。

你按住Ctrl键,点击一下JNIEXPORTJNICALL,会跳转到jni.h文件中,如下图所示:

在这里插入图片描述
你会看到JNIEXPORTJNICALL有一个#define

  #define,在C语言中,可以用 #define 定义一个标识符来表示一个常量。其特点是:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。那么对于

#define JNIEXPORT  __attribute__ ((visibility ("default")))
#define JNICALL

这里的JNIEXPORT和JNICALL表示宏定义,宏可这样理解:

  ① 宏 JNIEXPORT 代表的就是右侧的表达式: __attribute__ ((visibility ("default")))
  ② JNIEXPORT 是右侧表达式的别名,宏可表达的内容很多,如:一个具体的数值、一个规则、一段逻辑代码等;

  然后就是visibility表示是否可见,default表示外部可见,类似于public,可以被外部调用。如果改成hidden :表示隐藏,类似于private修饰符,只能被内部调用。

  jstring,这是一个数据类型,是 Java中String数据类型在 JNI 中的代表,宏JNICALL 右边是空的,说明只是个空定义,空定义是可以去掉的,我们试一下去掉再运行,如下图所示:

在这里插入图片描述
  Java_com_llw_studynative_MainActivity_stringFromJNI,这是一个函数名,有没有似曾相识的感觉,好像和我们在MainActivity看到的stringFromJNI()函数相似,但是呢,名字没有这么长啊,而实际上是同一个函数,你可以按住Ctrl键,点击Java_com_llw_studynative_MainActivity_stringFromJNI跳转到MainActivity中的stringFromJNI(),也可以点击stringFromJNI()跳转到native-lib.cpp中的Java_com_llw_studynative_MainActivity_stringFromJNI

  那么回到之前的问题,为什么函数名字变长了,这跟JNI native函数的注册方式有关,JNI Native函数有两种注册方式:① 静态注册:按照JNI接口规范的命名规则注册;② 动态注册:在.cpp的JNI_OnLoad方法里注册;

  JNI接口规范的命名规则:Java_<PackageName>_<ClassName>_<MethodName> ,例如Java_com_llw_studynative_MainActivity_stringFromJNI,下面我们来体验一下这种注册方式,在MainActivity中增加这样一行代码:

public native String testFromJNI();

代码位置如下图所示:

在这里插入图片描述

  这个函数我们还有在cpp文件中创建中创建,所以爆红很正常,鼠标点击这个函数,Alt + Enter,会出现一个提示框。

在这里插入图片描述

第一项的意思是创建名为testFromJNI的JNI函数,回车一下就会快速在native-lib.cpp中创建了。

在这里插入图片描述

可以看到这个函数自动生成了,是不是很方便呢?然后我们在这个函数体里面写几行代码,如下图所示:

在这里插入图片描述

然后修改一下MainActivity中的onCreate()方法中的代码,调用testFromJNI函数。

在这里插入图片描述

再运行一下:

在这里插入图片描述

代码可行,说明我们刚才写的函数没有问题,下面我们继续分析函数中的参数。

  JNIEnv,代表了Java环境,通过JNIEnv*就可以对Java端的代码进行操作,如:① 创建Java对象;② 调用Java对象的方法;③ 获取Java对象的属性等;

  jobject,代表了定义native函数的Java类 或 Java类的实例:如果native函数是static,则代表类Class对象;如果native函数非static,则代表类的实例对象。我们可以通过jobject访问定义该native方法的成员方法、成员变量等。

③ JNI数据类型

  前面说到jstring表示Java中的String类型,那么其他的数据类型在JNI中怎么表示呢,进入jni.h,找到最上方的位置,我们可以看到一些数据类型的定义。

在这里插入图片描述

  这里可以看到常见的变量,int、boolean、float、double、byte、char等,这里是JNI所定义的,他们之间有一个映射关系,参考下表:

JNIJavaC/C++
jint / jsizeintint
jshortshortshort
jlonglonglong / long long (__int64)
jbytejbytesigned char
jbooleanbooleanunsigned char
jcharcharunsigned short
jfloatfloatfloat
jdoubledoubledouble
jobjectObject_jobject*
jstringStringstring

那么新项目中怎么样使用C++就说完了,下面我们说明一下在现有的项目中怎么增加C++的使用。

四、现有工程使用C++

这里我们将项目结构改成Project

在这里插入图片描述
然后右键StudyNative,点击New → Module ,出现弹窗。

在这里插入图片描述
点击Next,这里就是默认选中Empty Activity的。

在这里插入图片描述

点击Next。

在这里插入图片描述

点击Finish。

在这里插入图片描述

工程创建完成,这个功能结构你应该很熟悉了,那么怎么添加C++进去呢?

① 创建C++文件

右键点击old工程的 main 目录,然后依次选择 New > Directory,会出现一个小窗口。

在这里插入图片描述

输入cpp然后回车。

在这里插入图片描述

右键点击 cpp 目录,然后依次选择 New > C/C++ Source File,出现弹窗,输入C++文件名。

在这里插入图片描述

点击OK。

在这里插入图片描述

② 创建CMake

右键点击old目录,然后依次选择 New > File。输入“CMakeLists.txt”作为文件名,然后回车。

在这里插入图片描述

这个文件的名字不能乱改的,如果你不习惯的话可以把这个文件再移动到cpp文件夹下。

在这里插入图片描述
然后我们配置一下old的build.gradle文件,添加如下代码:

	externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }

这里的路径很重要,如果你的CMakeLists.txt不在cpp下面,你就要改成对应的路径,Sync Now。

③ 使用C++

下面我们在MainActivity中加载使用C++,首先需要这样的代码:

	static {
        System.loadLibrary("old");
    }

这是加载C++的库,这个名字就是CMakeList.txt中的project()中的名字。

在这里插入图片描述

下面我们添加一个native函数。

	public native String oldString();

在这里插入图片描述
  这里要注意一点就是你要生成的JNI函数是在那个cpp文件中,这里有两个,我们需要在old下的old-lib.cpp中生成,选中回车,在old-lib.cpp中生成如下代码:

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_llw_old_MainActivity_oldString(JNIEnv *env, jobject thiz) {
    std::string result = "This old project used C++";
    return env->NewStringUTF(result.c_str());
}

然后我们调用运行一下,就在onCreate()打印一下看看。

在这里插入图片描述
OK,这样我们在老项目中就可以使用C++了。

在这里插入图片描述

这里可以自己切换使用那个工程,都能够正常运行的。

五、源码

欢迎 Star 和 Fork

源码地址:StudyNative

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/16719.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

集采报告丨国家药品带量采购政策及趋势分析

本人在医药领域从事药品数据分析工作多年&#xff0c;前几天在了解VBP药品带量采购相关事项时遇见了一些网上不好查找答案的问题&#xff08;国家药品带量采购目录、流程、区别、数据分析、政策、执行、结果、公示、网站、意思&#xff09;&#xff0c;对此笔者将其整理出来并解…

追求极致性能,RocketMQ 消息通信详解

1 介绍 RocketMQ 消息队列架构主要包括 NameServe、Broker(Master/Slave)、Producer、Consumer 4 个核心部件&#xff0c;基本执行流程如下&#xff1a; 点击查看大图 NameServer 优先启动。NameServer 是整个 RocketMQ 的“中央大脑” &#xff0c;作为 RocketMQ 的服务注册中…

计算机网络(下)

运输层 运输层概述 概念 进程之间的通信 从通信和信息处理的角度看&#xff0c;运输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功能中的最低层。当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通…

【深度学习】U-net网络结构搭建 | pytorch

文章目录前言一、U-net网络结构复现&#xff08;上采样部分采用转置卷积nn.ConvTranspose2d&#xff09;1.1、整体结构介绍1.2、encoder部分实现&#xff08;左边网络部分&#xff09;1.3、decoder部分实现&#xff08;右边网络部分&#xff09;1.4、整个网络搭建二、U-net网络…

React源码分析5-commit

前两章讲到了&#xff0c;react 在 render 阶段的 completeUnitWork 执行完毕后&#xff0c;就执行 commitRoot 进入到了 commit 阶段&#xff0c;本章将讲解 commit 阶段执行过程源码。 总览 commit 阶段相比于 render 阶段要简单很多&#xff0c;因为大部分更新的前期操作都…

Dubbo框架基本使用

一&#xff1a;软件架构的演练过程【了解】 单体应用架构--->垂直应用架构--->分布式架构&#xff08;SOA架构/微服务架构) 1.单体应用架构 单体应用架构&#xff0c;就是将一个系统的多个模块做成一个项目&#xff0c;然后部署到tomcat服务器上 优点&#xff1a; 项目架…

第01章+Java概述

课程链接&#xff1a;韩顺平Java_程序举例_哔哩哔哩_bilibili 什么叫程序 程序&#xff1a;计算机执行某些操作或解决某个问题而编写的一系列有序指令的集合。 Java版本迭代 官网介绍&#xff1a; Oracle Java SE Support Roadmap LTS为长期支持版本&#xff1a;推荐使用…

IQM的Unimon:一种新的量子比特,可促进量子计算机的实用化

​ 量子处理器中unimon 量子比特的艺术效果图。&#xff08;图片来源&#xff1a;网络&#xff09; 来自芬兰IQM量子计算机公司、阿尔托大学和芬兰VTT技术研究中心的一组科学家发现了一种新的超导量子比特——unimon&#xff0c;可提高量子计算的准确性。该团队已经实现了第一…

解读阿里Q2财报:阿里云的跨周期引擎

昨天&#xff0c;阿里巴巴公布今年6月到9月财务业绩&#xff0c;显示云业务总收入为267.6亿元&#xff0c;在除去阿里内部使用的额度后&#xff0c;抵销跨分部交易后营收为207.57亿元&#xff0c;比上一个季度增长超17%。 具体看&#xff0c;值得关注的有三点&#xff1a; 1、…

Python爬取公交线路信息及站点shp数据 文末附数据下载地址

本篇主要记录爬取公交网整个过程,由于这次所用方法虽比较常规,但由于该网站页面内容转码原因以及遍历链接较多,所以小坑还是比较多的,特在此进行记录。 以前爬过百度地图,当时用的是API平台,加上网站比较规范,所以标签节点什么的都比较清晰,但这次由于特殊原因所选择的…

对JavaScript中的Math.random随机函数破解

什么是随机 在通常的说法中&#xff0c;随机性是指事件中明显实际缺乏可预测性&#xff0c;事件、符号或步骤的随机序列通常没有顺序 举个例子&#xff0c;比如我们在抛硬币&#xff0c;硬币的结果取决于很多因素&#xff0c;比如说我们施加的力&#xff0c;空气阻力&#xff…

Linus shell 在一个脚本中调用另外一个脚本变量

1.新建public.sh文件&#xff0c;并添加以下内容&#xff1a; 2.新建ceshi.sh文件&#xff0c;并添加以下内容&#xff1a; 3.在终端赋予ceshi.sh文件执行权限&#xff0c;并运行该文件。

角度回归(复数与欧拉公式,L1,L2)

文章目录1 BEV下&#xff0c;Eula 损失函数2 BEV下&#xff0c;PointPillars使用sin联合SmoothL13 透视图下&#xff0c; MultiBin 全局方向损失4 L1/L2-norm 的周期损失函数1 BEV下&#xff0c;Eula 损失函数 Yolo-complex的论文中&#xff0c;对于BEV视角下&#xff0c;目标…

SDN和NFV的区别?

前言 网络功能虚拟化&#xff08;Network Functions Virtualization&#xff0c;NFV&#xff09;是一种关于网络架构的概念。我们平时使用的x86服务器由硬件厂商生产&#xff0c;在安装了不同的操作系统以及软件后实现了各种各样的功能。而传统的网络设备并没有采用这种模式&am…

2000-2019年各省产业结构合理化指数(干春晖泰尔指数)

2000-2019年省级产业结构合理化指数&#xff08;干春晖泰尔指数&#xff09; 1、来源&#xff1a;统计NJ及各省统计NJ 2、时间2000-2019年 3、数据说明&#xff1a;含原始数据和计算过程 4、范围包括全国31省 5、指标包括&#xff1a;各省总产值、第一产业增加值、第二产业…

C++基础知识要点--表达式 (Primer C++ 第五版 · 阅读笔记)

目录表达式基础算术运算符逻辑和关系运算符赋值运算符递增和递减运算符成员访问运算符条件运算符位运算符sizeof运算符逗号运算符类运算符运算符优先级表表达式 基础 当一个对象被用作右值的时候&#xff0c;用的是对象的 值&#xff08;内容);当对象被用作左值的时候&#x…

Linux 信号

概念&#xff1a;信号不是信号量&#xff0c;信号量是进程间的一种通信方式&#xff0c;信号是系统中的软件中断&#xff0c;指一种事件通知机制&#xff0c;通知进程发生了某个事件&#xff0c;打断当前的操作&#xff0c;去处理这个事件。 种类&#xff1a;一共有62种信号&a…

Linux之用户管理、权限管理、程序安装卸载

一. 用户管理 1. 查看账户 (1). 查看当前账号&#xff1a;whoami ​​(2). 查看系统当前登录的账号&#xff1a;who ​​补充常用选项&#xff1a; ​​(3). 查看系统所有的账号&#xff1a; cat /etc/passwd ​​2. exit&#xff1a;退出登录账户 如果是图形界面&#xff0c…

curl命令的常用操作

curl是非常实用的命令行工具&#xff0c;用来与服务器之间传输数据。它的命令行参数多达几十种。 在Linux环境中使用curl命令可以进行接口测试。利用curl对http协议发送Get/Post/Delete/Put请求,同时还可以携带header来满足接口的特定需求。 curl命令的语法 curl[options] [U…

Linux03-网络设置

一、说明 在上一节&#xff0c;咱使用VMware安装了虚拟机&#xff0c;网络设置选择了 “桥接模式” &#xff0c;本节咱们来具体讨论一下网络连接方式和网络设置。 实验环境&#xff1a;CentOS7 VMware 二、桥接模式 当我们设置桥接模式时&#xff0c;虚拟机是直接使用物理…