LJ的Blog

学海无涯苦做舟

0%

Android-NDK学习(2)

写在前面

本文需要一些CMakeJNI的基础知识,对于CMake的使用推荐Android官网的NDK入门。CMake是Android Studio 2.2以上新增的支持原生编程的工具,CMake是一个跨平台的编译工具,可以用简单的语句描述所有平台的编译过程。恩,暂时先记一下吧,本文现处于自嗨状态,不适合作为各位将之作为NDK的学习资料。

环境配置

在创建项目时勾选include c++,在项目创建完毕,文件目录如下:

目录结构
可以看到,可以看到app目录下有一个CMakeLists.txt,我在这个txt里加了一些注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 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.4.1)

# 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.
# 增加cpp动态共享库
add_library( # Sets the name of the library.
hello

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/hello.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-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.
hello

# Links the target library to the log library
# included in the NDK.
${log-lib} )

如果你有多个cpp动态共享库,可以再添加一个add_library。在app的build.gradle中也有相关的配置:

1
2
3
4
5
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

关于CMake更详细的配置参见:https://developer.android.com/ndk/guides/cmake.html?hl=zh-cn

可以看到我的CMake文件中添加的动态库为hello,这里在Java文件中创建一个JNI入口类,叫做JNIEntry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.xiasuhuei321.forjni.entry;

import android.util.Log;

/**
* Created by xiasuhuei321 on 2017/8/26.
* author:luo
* e-mail:xiasuhuei321@163.com
*/

public class JNIEntry {
static {
System.loadLibrary("hello");
}

// 实例域
public String TAG1 = "JNIEntry_INSTANCE";
// 静态域
public static final String TAG2 = "JNIEntry_STATIC";
public static final String TAG = "JNIEntry";

public static void testStaticMethod(){
Log.e(TAG,"静态测试方法被调用了");
}

private void testInstanceMethod(){
Log.e(TAG,"实例测试方法被调用了");
}

/**
* 从C++获取字符串
*
* @return 字符串
*/
public static native String stringFromJNI();

/**
* 将大写字符串转成小写
*
* @return 小写字符串
*/
public static native String toLowCase(String str);

public static native void changeArray(int[] array);

public static native void accessField(Object obj);

public static native void accessMethod(Object obj);
}

hello.cpp完整代码

这里再记一下c++源码,方便以后自己查阅
cpp源码:hello.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//
// Created by xiasuhuei321 on 2017/8/25.
//

#include <jni.h>
#include <android/log.h>
#include <string>


extern "C"
{
JNIEXPORT jstring JNICALL
Java_com_xiasuhuei321_forjni_entry_JNIEntry_stringFromJNI(JNIEnv *env, jobject instance) {
// 用c字符串创建Java字符串
__android_log_print(ANDROID_LOG_DEBUG, "jni", "Hello World");
// 在内存溢出的情况下,返回NULL
return env->NewStringUTF("Hello World");
}

JNIEXPORT jstring JNICALL
Java_com_xiasuhuei321_forjni_entry_JNIEntry_toLowCase(JNIEnv *env, jclass type, jstring str_) {
// 为了在原生代码中使用Java字符串,需要先将Java字符串转换成C字符串
// 这个函数可以将Unicode格式的Java字符串转换成C字符串
// 第二个参数指定了是指向堆内原有对象还是拷贝一份新的对象
// 这种获取的字符串只读
const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
// 获取字符串长度
jint length = env->GetStringLength(str_);
char strs[length];

// 这里必须要这么复制,自己手动改会有一个特殊字符(猜测是结尾的符号),
// 在生成UTF字符串的时候会报错
strcpy(strs, str);
for (int i = 0; i < length; i++) {
char c = str[i];
if (c >= 'A' && c <= 'Z') {
strs[i] = (char) (c + 32);
} else {
strs[i] = c;
}
}

__android_log_print(ANDROID_LOG_DEBUG, "jni", "%s", strs);
// 释放JNI函数返回的C字符串
env->ReleaseStringUTFChars(str_, str);

return env->NewStringUTF(strs);
}

JNIEXPORT void JNICALL
Java_com_xiasuhuei321_forjni_entry_JNIEntry_changeArray(JNIEnv *env, jclass type,
jintArray array_) {
// 将Java数组复制到C数组中
// env->GetIntArrayRegion()
// 从C数组向Java数组提交所做的修改
// env->SetIntArrayRegion()
// 获取指向数组元素的直接指针
jint *value = env->GetIntArrayElements(array_, NULL);
jint length = env->GetArrayLength(array_);
// 这里对每个元素做+1操作
for (int i = 0; i < length; i++) {
value[i] += 1;
}
env->ReleaseIntArrayElements(array_, value, 0);
}

JNIEXPORT void JNICALL
Java_com_xiasuhuei321_forjni_entry_JNIEntry_accessField(JNIEnv *env, jclass type,
jobject instance) {
jclass clazz = env->GetObjectClass(instance);
// JNI提供了用域ID访问两类域的方法,可以通过给定实例的class对象获取域ID
// 用GetObjectClass函数可以获得class对象
// 获取id
jfieldID instanceFieldId = env->GetFieldID(clazz, "TAG1", "Ljava/lang/String;");
// 获取属性值
jstring jstr_value = (jstring) env->GetObjectField(instance, instanceFieldId);
const char *ch_value = env->GetStringUTFChars(jstr_value, NULL);

__android_log_print(ANDROID_LOG_DEBUG, "jni", "%s", ch_value);

// 获取id
jfieldID staticFieldId = env->GetStaticFieldID(clazz, "TAG2", "Ljava/lang/String;");
// 获取属性值
jstring static_value = (jstring) env->GetStaticObjectField(clazz, staticFieldId);
const char *st_value = env->GetStringUTFChars(static_value, NULL);
__android_log_print(ANDROID_LOG_DEBUG, "jni", "%s", st_value);

}

JNIEXPORT void JNICALL
Java_com_xiasuhuei321_forjni_entry_JNIEntry_accessMethod(JNIEnv *env, jclass type,
jobject obj) {
// 获取class对象
jclass clazz = env->GetObjectClass(obj);
// 获取方法id
jmethodID i_id = env->GetMethodID(clazz, "testInstanceMethod", "()V");
jmethodID s_id = env->GetStaticMethodID(clazz, "testStaticMethod", "()V");

// 调用实例方法
env->CallVoidMethod(obj, i_id);
// 调用静态方法
env->CallStaticVoidMethod(clazz,s_id);
}
}

凑个字数