寫在前面
本文需要一些CMake
和 JNI
的基礎(chǔ)知識叼丑,對于CMake的使用推薦Android官網(wǎng)的NDK入門。CMake是Android Studio 2.2以上新增的支持原生編程的工具负拟,CMake是一個跨平臺的編譯工具,可以用簡單的語句描述所有平臺的編譯過程歹河。恩掩浙,暫時先記一下吧花吟,本文現(xiàn)處于自嗨狀態(tài),不適合作為各位將之作為NDK的學(xué)習(xí)資料厨姚。
環(huán)境配置
在創(chuàng)建項目時勾選include c++衅澈,在項目創(chuàng)建完畢,文件目錄如下:
可以看到谬墙,可以看到app目錄下有一個CMakeLists.txt今布,我在這個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.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動態(tài)共享庫
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.
# 生成鏈接動態(tài)庫
target_link_libraries( # Specifies the target library.
hello
# Links the target library to the log library
# included in the NDK.
${log-lib} )
如果你有多個cpp動態(tài)共享庫,可以再添加一個add_library拭抬。在app的build.gradle中也有相關(guān)的配置:
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
關(guān)于CMake更詳細(xì)的配置參見:https://developer.android.com/ndk/guides/cmake.html?hl=zh-cn
可以看到我的CMake文件中添加的動態(tài)庫為hello部默,這里在Java文件中創(chuàng)建一個JNI入口類,叫做JNIEntry:
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";
// 靜態(tài)域
public static final String TAG2 = "JNIEntry_STATIC";
public static final String TAG = "JNIEntry";
public static void testStaticMethod(){
Log.e(TAG,"靜態(tài)測試方法被調(diào)用了");
}
private void testInstanceMethod(){
Log.e(TAG,"實例測試方法被調(diào)用了");
}
/**
* 從C++獲取字符串
*
* @return 字符串
*/
public static native String stringFromJNI();
/**
* 將大寫字符串轉(zhuǎn)成小寫
*
* @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
//
// 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字符串創(chuàng)建Java字符串
__android_log_print(ANDROID_LOG_DEBUG, "jni", "Hello World");
// 在內(nèi)存溢出的情況下傅蹂,返回NULL
return env->NewStringUTF("Hello World");
}
JNIEXPORT jstring JNICALL
Java_com_xiasuhuei321_forjni_entry_JNIEntry_toLowCase(JNIEnv *env, jclass type, jstring str_) {
// 為了在原生代碼中使用Java字符串,需要先將Java字符串轉(zhuǎn)換成C字符串
// 這個函數(shù)可以將Unicode格式的Java字符串轉(zhuǎn)換成C字符串
// 第二個參數(shù)指定了是指向堆內(nèi)原有對象還是拷貝一份新的對象
// 這種獲取的字符串只讀
const char *str = env->GetStringUTFChars(str_, JNI_FALSE);
// 獲取字符串長度
jint length = env->GetStringLength(str_);
char strs[length];
// 這里必須要這么復(fù)制算凿,自己手動改會有一個特殊字符(猜測是結(jié)尾的符號)份蝴,
// 在生成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函數(shù)返回的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數(shù)組復(fù)制到C數(shù)組中
// env->GetIntArrayRegion()
// 從C數(shù)組向Java數(shù)組提交所做的修改
// env->SetIntArrayRegion()
// 獲取指向數(shù)組元素的直接指針
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函數(shù)可以獲得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");
// 調(diào)用實例方法
env->CallVoidMethod(obj, i_id);
// 調(diào)用靜態(tài)方法
env->CallStaticVoidMethod(clazz,s_id);
}
}
湊個字?jǐn)?shù)