问题的引入

创建了一个 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 添加图标Image asset 有白框_c++

假设已经有了一个 Android Studio 工程(我这里是 javatest2),在创建它时,并不是 “Native C++”,现在需要在其中加入 “C++” 代码。

1 创建 cpp 文件夹

在 app/src/main 文件夹下(如下图所示),创建 cpp 文件夹,当然也可以是其他名字,比如 cxx,这个可以随意。

android studio 添加图标Image asset 有白框_android_02


创建好后如下图所示:

android studio 添加图标Image asset 有白框_java_03

2 在 cpp 文件夹下创建其他文件

在 cpp 中创建 inc 和 src 文件夹,以及 CMakeLists.txt,inc 用于存放 C++ 头文件,src 用于存放 C++ 源文件,这两个文件夹也不是必须的,只是我喜欢这样分类存放,CMakeLists.txt 是必须的,它用于指示如何编译 C++ 源文件。创建好后,如下图所示:

android studio 添加图标Image asset 有白框_android_04

3 创建 C++ 源文件

在 inc 文件夹中,放入你的 C++ 头文件(我这里是 CalMinMax.h),在 src 文件夹中,放入你的 C++ 源文件(我这里是 CalMinMax.cpp),在 cpp 文件夹中,创建一个源文件(我这里是 native-lib.cpp)。如下图所示:

android studio 添加图标Image asset 有白框_c语言_05

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 中调用,如下图所示:

android studio 添加图标Image asset 有白框_java_06

注意,在调用 System.loadLibrary 时,libname 要与 CMakeLists.txt 中的 name of the library 相同。下面的 “public native String stringFromJNI(int a, int b);” 就是在声明方法。

6 修改 gradle 文件

修改下面的 gradle 文件:

android studio 添加图标Image asset 有白框_android studio_07


修改如下,共有两处:

android studio 添加图标Image asset 有白框_android_08


android studio 添加图标Image asset 有白框_c++_09


注意,ndkVersion 与你自己 Android Studio 中安装的 ndk 保持一致。

7 运行测试

运行结果如下:

android studio 添加图标Image asset 有白框_android studio_10

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));