问题的引入
创建了一个 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 <string>
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 <jni.h>
#include <string>
#include "CalMinMax.h"
extern "C" JNIEXPORT jstring JNICALL
Java_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 <jni.h>
#include <string>
#include "CalMinMax.h"
extern "C" JNIEXPORT jstring JNICALL
Java_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));