行业知识
如何在现有的 Android Studio 工程中添加 C++ 代码
问题的引入
创建了一个 Android Studio 工程,在实现了许多功能后,需要在当前的工程中调用一些 C++ 代码,在网上查了一些资料,发现,都是在创建 Android Studio 工程时,就加入 C++ 支持,我不想重新创建带 C++ 支持的 Android Studio 工程,因为有些工作需要在新工程中重复做,很麻烦,于是,就想直接在当前 Android Studio 工程添加 C++ 代码。
实现方法简述
我采用的这种方法:创建了一个 Android Studio 工程,选用 “Empty Activity”,再创建了一个 Android Studio 工程,选用 “Native C++” ,然后对比了两个工程的文件差异,从这些差异中,找到了方法。
具体步骤
下面详述添加方法。我的环境是:win10,Android Studio 版本如下:
假设已经有了一个 Android Studio 工程(我这里是 javatest2),在创建它时,并不是 “Native C++”,现在需要在其中加入 “C++” 代码。
1 创建 cpp 文件夹
在 app/src/main 文件夹下(如下图所示),创建 cpp 文件夹,当然也可以是其他名字,比如 cxx,这个可以随意。
创建好后如下图所示:
2 在 cpp 文件夹下创建其他文件
在 cpp 中创建 inc 和 src 文件夹,以及 CMakeLists.txt,inc 用于存放 C++ 头文件,src 用于存放 C++ 源文件,这两个文件夹也不是必须的,只是我喜欢这样分类存放,CMakeLists.txt 是必须的,它用于指示如何编译 C++ 源文件。创建好后,如下图所示:
3 创建 C++ 源文件
在 inc 文件夹中,放入你的 C++ 头文件(我这里是 CalMinMax.h),在 src 文件夹中,放入你的 C++ 源文件(我这里是 CalMinMax.cpp),在 cpp 文件夹中,创建一个源文件(我这里是 native-lib.cpp)。如下图所示:
4 编写源代码
CalMinMax.h 代码:
#ifndef JAVACPP_CALMINMAX_H#define JAVACPP_CALMINMAX_H#include std::string calMinMax(int a, int b);#endif //JAVACPP_CALMINMAX_H
CalMinMax.cpp 代码:
#include "CalMinMax.h"std::string calMinMax(int a, int b){ if (a > b) return std::string("Max string"); else return std::string("Min string");} native-lib.cpp 代码,这里面定义了一些 JNICALL,它会直接调用到 C++ 代码(在这里,它调用了 calMinMax),要根据你的实际情况修改。
Java_com_example_javatest2_MainActivity_stringFromJNI 中,com_example_javatest2 跟包名相同(“.” 用 “_” 代替了),MainActivity 是要调用的 java 文件,stringFromJNI 是真正的方法名,注意名字不能有错误,不然在运行时会出错。
#include #include #include "CalMinMax.h"extern "C" JNIEXPORT jstring JNICALLJava_com_example_javatest2_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */, jint a, jint b) { std::string hello = calMinMax(a, b); return env->NewStringUTF(hello.c_str());} CMakeLists.txt 代码,注意,如果你创建的文件夹或者文件名不同,要做相应的修改,有些路径也要相应地修改。
# For more information about using CMake with Android Studio, read the# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.10.2)# Declares and names the project.project("javacpp")# Creates and names a library, sets it as either STATIC# or SHARED, and provides the relative paths to its source code.# You can define multiple libraries, and CMake builds them for you.# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library. javacpp # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). native-lib.cpp src/CalMinMax.cpp)include_directories(inc/)# Searches for a specified prebuilt library and stores the path as a# variable. Because CMake includes system libraries in the search path by# default, you only need to specify the name of the public NDK library# you want to add. CMake verifies that the library exists before# completing its build.find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log)# Specifies libraries CMake should link to your target library. You# can link multiple libraries, such as libraries you define in this# build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library. javacpp # Links the target library to the log library # included in the NDK. ${log-lib}) 说明一下,上面的 CMakeLists.txt 代码 与 native-lib.cpp 代码,用了 Android Studio 工程(选择 “Native C++” 时)自动生成的代码,但是做了一点修改。
5 在 java 文件中调用 C++ 代码
我这里示例,在 MainActivity.java 中调用,如下图所示:
注意,在调用 System.loadLibrary 时,libname 要与 CMakeLists.txt 中的 name of the library 相同。下面的 “public native String stringFromJNI(int a, int b);” 就是在声明方法。
6 修改 gradle 文件
修改下面的 gradle 文件:
修改如下,共有两处:
注意,ndkVersion 与你自己 Android Studio 中安装的 ndk 保持一致。
7 运行测试
运行结果如下:
8 其他说明
上面只是一个示例,有些地方写得不够规范,更好的做法是,将要调用 C/C++ 函数的 native 方法,全部写到一个 java 类中,统一管理。比如,上面的示例中,可以新建一个 java 类 MyC_CppFunc,将 native 声明放到这个类中,如下所示:
// 这里放你的包名package com.example.javatest2;public class MyC_CppFunc { // Used to load the 'javacpp' library on application startup. static { System.loadLibrary("javacpp"); }/** * A native method that is implemented by the 'javacpp' native library, * which is packaged with this application. */ public native String stringFromJNI(int a, int b);} 这时,native-lib.cpp 代码要做一些修改,主要是 JNICALL 的名字,因为直接调用 C/C++ 函数的 java 类发生了变化,所以,名字要改为:Java_com_example_javatest2_MyC_1CppFunc_stringFromJNI,这里有一个小细节,前面的名字中,是 MyC_1CppFunc 而不是 MyC_CppFunc,下划线后面多了一个 “1”,因为包名中的 “.” 用下划线代替了,而 java 类名中有一个 下划线,如果不做特殊处理,会解析出错,所以规定要在 下划线后面加上 “1”。
#include #include #include "CalMinMax.h"extern "C" JNIEXPORT jstring JNICALLJava_com_example_javatest2_MyC_1CppFunc_stringFromJNI( JNIEnv* env, jobject /* this */, jint a, jint b) { std::string hello = calMinMax(a, b); return env->NewStringUTF(hello.c_str());} 如果要在 MainActivity.java 中调用,可以这样做,先创建 MyC_CppFunc 类的对象,然后用它的对象调用:
MyC_CppFunc c_cppFunc = new MyC_CppFunc();TextView tv = findViewById(R.id.tv_show);tv.setText(c_cppFunc.stringFromJNI(6, 2));