【Android NDK】(一)cmake构建项目

1,617 阅读5分钟

本文记录了cmake新建项目和module手动集成NDK环境的步骤。
示例AS版本:Android Studio 4.1


一:配置环境

下载三个SDK Tools

  1. LLDB
    强大的NDK调试工具。
  2. NDK
    NDK开发环境
  3. CMake
    构建工具

二:新创建NDK项目

File->new->New Project,自定义项目名称什么的,其他的默认即可。

  1. 自动生成的主要是四个文件

  2. MainActivity.java

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        // 加载的so库,对应的so名称为libnative-lib.so,掐头去尾了
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * native关键字,声明了jni调用的方法
     */
    public native String stringFromJNI();
}
  1. 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支持的最低版本
cmake_minimum_required(VERSION 3.10.2)

# Declares and names the project.
#项目名称
project("ndktest")

# 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.
             # 生成的so库名称,此处生成的so文件名称是libnative-lib.so
             native-lib

             # Sets the library as a shared library.
             # STATIC:静态库,是目标文件的归档文件,在链接其他目标的时候使用
             # SHARED:动态库,会被动态链接,在运行时被加载
             # MODULE:模块库,是不会被链接到其他目标中的插件,但是可能会在运行时使用dlopen-系列的函数动态链接
             SHARED

             # Provides a relative path to your source file(s).
             # 资源文件,可以多个
             native-lib.cpp )

# 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日志库
              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.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
  1. native-lib.cpp
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_kongge_ndktest_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
  1. build.gradle
...
android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                cppFlags "" // 命令参数,非必须
            }
        }
    }

    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"  // 指定了该文件的路径,必须
            version "3.10.2"  // 支持的最低版本,非必须
        }
    }
}

5.构建或者运行之后,会生成对应的so文件

三:现有项目手动集成NDK

  1. 创建存放c文件的目录 src/mian 目录上右键->new->Directory->jni,创建c文件,我这里创建DataEncryption.cpp

  2. 新建CMakeLists.txt 这个位置可以随意放,我是在app根目录或者module的根目录创建的CMakeLists.txt文件,后面可以在build.gradle里面配置路径。

CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.4.1)

add_library(DataEncryptionLib SHARED src/main/jni/DataEncryption.cpp )
  1. 在build.gradle中配置CMakeLists.txt的路径。 app根目录或者module根目录下的build.gradle中配置:
android {
    ...
    externalNativeBuild {
        cmake {
            // 路径是ModuleName/CMakeLists.txt,则path 'CMakeLists.txt'
            // 如果路径是ModuleName/src/main/cpp/CMakeLists.txt,则path 'src/main/cpp/CMakeLists.txt'
            path 'CMakeLists.txt'
        }
    }
}
  1. 新建java文件,用于调用so方法。
package com.kongge.dataencryption.util;

public class DataEncryptionUtil {

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


    // 加密
    public static String encryptData(String data) {
        return encryptString(data);
    }

    // 解密
    public static String decryptData(String data) {
        return decryptString(data);
    }

    private static native String encryptString(String string);

    private static native String decryptString(String string);
}
  1. 此时native里面的方法是红色的,Alt+Enter会提示"Create function Java_xxx",会自动创建头文件。也可以使用javah来创建头文件,将头文件放到jni目录下。

com_kongge_dataencryption_util_DataEncryptionUtil.h内容如下

/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class com_kongge_dataencryption_util_DataEncryptionUtil */

#ifndef _Included_com_kongge_dataencryption_util_DataEncryptionUtil
#define _Included_com_kongge_dataencryption_util_DataEncryptionUtil

#include "../../../../../../../AndroidSdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include/jni.h"

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_kongge_dataencryption_util_DataEncryptionUtil
 * Method:    encryptString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_kongge_dataencryption_util_DataEncryptionUtil_encryptString
  (JNIEnv *, jclass, jstring);

/*
 * Class:     com_kongge_dataencryption_util_DataEncryptionUtil
 * Method:    decryptString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_kongge_dataencryption_util_DataEncryptionUtil_decryptString
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

  1. 修改DataEncryption.cpp文件
#include <iostream>
#include <jni.h>
#include "com_kongge_dataencryption_util_DataEncryptionUtil.h"

/*
 * Class:     com_kongge_dataencryption_util_DataEncryptionUtil
 * Method:    encryptString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_kongge_dataencryption_util_DataEncryptionUtil_encryptString
        (JNIEnv *env, jclass obj, jstring data) {
    const char* str;
    jboolean b = false;
    str = env->GetStringUTFChars(data, &b);
    if(str == NULL) {
        return NULL; /* OutOfMemoryError already thrown */
    }
    char strArr[strlen(str)];
    strcpy(strArr, str);
    int i = 0;
    while(strArr[i] != '\0') {
        strArr[i] = strArr[i] + 1;
        i++;
    }
    env->ReleaseStringUTFChars(data, str);
    jstring rtstr = env->NewStringUTF(strArr);
    return rtstr;
}

/*
 * Class:     com_kongge_dataencryption_util_DataEncryptionUtil
 * Method:    decryptString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_kongge_dataencryption_util_DataEncryptionUtil_decryptString
        (JNIEnv *env, jclass obj, jstring data) {
    const char* str;
    jboolean b = false;
    str = env->GetStringUTFChars(data, &b);
    if(str == NULL) {
        return NULL; /* OutOfMemoryError already thrown */
    }
    char strArr[strlen(str)];
    strcpy(strArr, str);
    int i = 0;
    while(strArr[i] != '\0') {
        strArr[i] = strArr[i] - 1;
        i++;
    }
    env->ReleaseStringUTFChars(data, str);
    jstring rtstr = env->NewStringUTF(strArr);
    return rtstr;
}
  1. 运行或者构建即可。

四:遇到的问题

  1. Android:No implementation found for native

    去掉.h头文件出现的,加上头文件就好了。或者cpp文件里面使用extern "C"的标志。

  2. com_kongge_dataencryption_util_DataEncryptionUtil.h这个头文件一定需要吗?

    不一定需要,只是一个标准,里面自动生成的方法名称会和java里面的native方法对应,可以直接把里面的方法拷贝到cpp文件里面重写就可以了。如果要删除的话,需要在cpp文件里面加上extern "C"的标志,不然会报错Android:No implementation found for native。
    一般建议删除,因为里面引入jni.h的时候,使用的是你本地SDK相对路径,导致在另一台机器上这个路径不一致的问题。

  3. 删除头文件之后,clean,使用Build-Make Module 'xxx项目',构建出错,报Execution failed for task ':xxx项目:externalNativeBuildDebug'.下面详细内容有use of undeclared identifier 'strlen'。

    运行没问题,但是build就出错了,很奇怪,看日志是使用了strlen函数导致的,Ctrl+左键还是可以链接到string.h里面的,手动加上#include <string.h>就好了