本文记录了cmake新建项目和module手动集成NDK环境的步骤。
示例AS版本:Android Studio 4.1
一:配置环境
下载三个SDK Tools
- LLDB
强大的NDK调试工具。 - NDK
NDK开发环境 - CMake
构建工具
二:新创建NDK项目
File->new->New Project,自定义项目名称什么的,其他的默认即可。
-
自动生成的主要是四个文件
-
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();
}
- 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} )
- 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());
}
- build.gradle
...
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "" // 命令参数,非必须
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt" // 指定了该文件的路径,必须
version "3.10.2" // 支持的最低版本,非必须
}
}
}
5.构建或者运行之后,会生成对应的so文件
三:现有项目手动集成NDK
-
创建存放c文件的目录 src/mian 目录上右键->new->Directory->jni,创建c文件,我这里创建DataEncryption.cpp
-
新建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 )
- 在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'
}
}
}
- 新建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);
}
- 此时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
- 修改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;
}
- 运行或者构建即可。
四:遇到的问题
-
Android:No implementation found for native
去掉.h头文件出现的,加上头文件就好了。或者cpp文件里面使用extern "C"的标志。
-
com_kongge_dataencryption_util_DataEncryptionUtil.h这个头文件一定需要吗?
不一定需要,只是一个标准,里面自动生成的方法名称会和java里面的native方法对应,可以直接把里面的方法拷贝到cpp文件里面重写就可以了。如果要删除的话,需要在cpp文件里面加上extern "C"的标志,不然会报错Android:No implementation found for native。
一般建议删除,因为里面引入jni.h的时候,使用的是你本地SDK相对路径,导致在另一台机器上这个路径不一致的问题。 -
删除头文件之后,clean,使用Build-Make Module 'xxx项目',构建出错,报Execution failed for task ':xxx项目:externalNativeBuildDebug'.下面详细内容有use of undeclared identifier 'strlen'。
运行没问题,但是build就出错了,很奇怪,看日志是使用了strlen函数导致的,Ctrl+左键还是可以链接到string.h里面的,手动加上#include <string.h>就好了