Android NDK學習(2)

xiasuhuei321發表於2017-12-13

寫在前面

本文需要一些CMakeJNI的基礎知識,對於CMake的使用推薦Android官網的NDK入門。CMake是Android Studio 2.2以上新增的支援原生程式設計的工具,CMake是一個跨平臺的編譯工具,可以用簡單的語句描述所有平臺的編譯過程。恩,暫時先記一下吧,本文現處於自嗨狀態,不適合作為各位將之作為NDK的學習資料。

環境配置

在建立專案時勾選include c++,在專案建立完畢,檔案目錄如下:

目錄結構
可以看到,可以看到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動態共享庫
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中也有相關的配置:

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
複製程式碼

關於CMake更詳細的配置參見:https://developer.android.com/ndk/guides/cmake.html?hl=zh-cn

可以看到我的CMake檔案中新增的動態庫為hello,這裡在Java檔案中建立一個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";
    // 靜態域
    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

//
// 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);
}
}
複製程式碼

湊個字數

相關文章