使用CMake構(gòu)建Android JNI工程

Android Studio(2.2+)構(gòu)建native庫(kù)可以使用原生構(gòu)建工具包ndk-build,也可以使用外部構(gòu)建工具CMake,搭配Gradle插件可以方便的構(gòu)建原生庫(kù),進(jìn)行Android JNI的開(kāi)發(fā)。使用Android Studio創(chuàng)建的native工程默認(rèn)使用的是CMake構(gòu)建工具,本文從零開(kāi)始介紹使用CMake搭建一個(gè)JNI工程。

為了闡述方便,我們以創(chuàng)建一個(gè)默認(rèn)的Android工程為例,不使用創(chuàng)建向?qū)Ю锏腎nclude C++ Support或者創(chuàng)建C++工程?,F(xiàn)在我們?cè)趎ative代碼(C++)中實(shí)現(xiàn)一個(gè)獲取字符串并返回的操作,然后使用java jni來(lái)調(diào)用。

一、下載NDK和構(gòu)建工具

打開(kāi)Android Studio -> Perferences -> Appearance&Behavoir -> System Setting -> Android SDK,或者直接在左側(cè)搜索Android SDK,選擇SDK Tools,下載NDK、CMake、LLDB這三個(gè)工具包。



新建的native工程(Include C++ Support)在local.properties中都會(huì)配置ndk的默認(rèn)的路徑:

ndk.dir=/Users/derek/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/derek/Library/Android/sdk

如果沒(méi)有,或者ndk在別的目錄下,需要手動(dòng)添加或修改路徑。

二、在java類中聲明native方法

在java中聲明要使用的native方法,這些方法以native前綴,只需聲明,無(wú)需實(shí)現(xiàn)。這些方法可以聲明為static或非static方法,可以是任何訪問(wèn)權(quán)限。

package com.tsia.example.jnitest;
...
public class MainActivity extends Activity {
  ...
  public native String stringFromJNI(String str);
}

三、添加C/C++代碼

在c++代碼中需添加和native方法對(duì)應(yīng)的函數(shù),注意如下幾點(diǎn):

  1. 文件名稱可隨意指定,可以只有源文件
  2. 頭文件或源文件中要#include <jni.h>
  3. 方法聲明要和java中的native方法對(duì)應(yīng):
  • 方法名稱。Java_包名_類名_方法名,包名也使用_分隔。
  • 參數(shù)。
    • 第一個(gè)參數(shù)為JNIEnv *
    • 如果為static方法,第二個(gè)參數(shù)為jclass;如果非static方法,第二個(gè)參數(shù)為jobject
    • 從第三個(gè)參數(shù)開(kāi)始,和java的native方法的參數(shù)類型和順序要一一對(duì)應(yīng)
  • 返回值。
    • 返回值類型要對(duì)應(yīng)java中的類型
    • 需要JNIEXPORT、JNICALL前綴

方法格式可以概括為:JNIEXPORT <返回類型> JNICALL Java_<包名><類名><方法名>(JNIEnv *, jobject,<參數(shù)>); 包名中的“.”用下劃線“_”代替。

#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL Java_com_tsia_example_jnitest_MainActivity_stringFromJNI
  (JNIEnv *env, jobject jobj, jstring str) {

  const char* c_name = env->GetStringUTFChars(str, NULL);

  std::string hello = "Hello, ";
  std::string name(c_name);

  return env->NewStringUTF((hello+name).c_str());
  }

如果擔(dān)心函數(shù)聲明寫錯(cuò),可以使用命令行生成native方法對(duì)應(yīng)的C++函數(shù)頭文件,只需要到j(luò)ava文件所在的包名目錄下執(zhí)行javah命令,比如在com所在目錄下執(zhí)行:

tsias-MacBook-Pro:java tsia$ javah -jni com.tsia.example.jnitest.MainActivity

執(zhí)行后會(huì)在該目錄下生成一個(gè)頭文件:com_tsia_example_jnitest_MainActivity.h,自動(dòng)生成的格式為包名+類名.h,中間會(huì)使用_分隔。(這個(gè)文件名為命令自動(dòng)生成的格式,可以隨意修改)

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_tsia_example_jnitest_MainActivity */

#ifndef _Included_com_tsia_example_jnitest_MainActivity
#define _Included_com_tsia_example_jnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_tsia_example_jnitest_MainActivity
 * Method:    stringFromJNI
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_tsia_example_jnitest_MainActivity_stringFromJNI
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

可以在.h文件中看到所有native方法的聲明,格式就是我們上文講的一樣。手動(dòng)編寫時(shí)也可參考自動(dòng)生成頭文件的格式,避免出錯(cuò)。

在調(diào)用native方法的時(shí)候會(huì)匹配函數(shù)名和參數(shù),需要按照格式書寫,不可隨意修改。

四、編寫CMakeLists.txt文件

接下來(lái)就是創(chuàng)建CMake的構(gòu)建腳本,它是一個(gè)純文本文件,必須命名為CMakeLists.txt。構(gòu)建腳本用來(lái)告訴CMake將如何創(chuàng)建一個(gè)so庫(kù),例子中我們要將c++代碼編譯成一個(gè)名為native-lib的庫(kù),給jni調(diào)用。

cmake_minimum_required(VERSION 3.4.1)
add_library(
       # 設(shè)置so文件名稱.
       native-lib

       # 設(shè)置這個(gè)so文件為動(dòng)態(tài)庫(kù)(SHARED)。靜態(tài)庫(kù)使用STATIC
       SHARED

       # c/c++源文件的相對(duì)路徑(相對(duì)于CMakeLists.txt)
       src/main/java/jnitest.cpp)

當(dāng)工程編譯的時(shí)候,Gradle會(huì)自動(dòng)將動(dòng)態(tài)庫(kù)native-lib庫(kù)打包到APK中。腳本中指定的庫(kù)名稱為native-lib,但實(shí)際CMake生成的名稱為libnative-lib.so。
CMake 使用以下規(guī)范來(lái)為庫(kù)文件命名:

lib庫(kù)名稱.so

五、配置Gradle關(guān)聯(lián)

在構(gòu)建應(yīng)用時(shí),Gradle 會(huì)以依賴項(xiàng)的形式運(yùn)行CMake,并將共享的庫(kù)打包到的 APK中,因此我們需要提供一個(gè)指向 CMake腳本文件的路徑。

手動(dòng)配置

externalNativeBuild {} 塊添加到模塊級(jí) build.gradle 文件中,并使用 cmake {} 對(duì)其進(jìn)行配置

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  externalNativeBuild {
    cmake {
      path "CMakeLists.txt"
    }
  }
}

這里的path為相對(duì)于build.gradle文件的路徑,需正確配置。

使用Android Studio配置關(guān)聯(lián)

從IDE左側(cè)打開(kāi) Project 窗格并選擇 Android視圖,右鍵點(diǎn)擊您想要關(guān)聯(lián)到原生庫(kù)的模塊(例如 app 模塊),并從菜單中選擇 Link C++ Project with Gradle。


選擇使用CMake構(gòu)建,并制定CMakeLists的路徑。

完成后可以看到模塊級(jí)build.gradle 文件中會(huì)增加externalNativeBuild {} 塊配置,和我們手動(dòng)配置的結(jié)果是一樣的。因?yàn)間radle配置有修改,sync project下。

此時(shí)執(zhí)行build -> Make Module 'app',可以看到所有架構(gòu)的so都會(huì)打到APK的lib目錄下。


五、在java文件中加載so庫(kù)

在Java代碼中加載so庫(kù)時(shí),請(qǐng)使用您在 CMake 構(gòu)建腳本中指定的名稱。如CMake生成的而文件是libnative-lib.so,只要指定加載native-lib即可。

package com.tsia.example.jnitest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        String ret = stringFromJNI("world");
        Log.i("jnitest", ret+"");
    }
    
    public native String stringFromJNI(String str);
}

然后在代碼中調(diào)用native方法打印結(jié)果。運(yùn)行后打印結(jié)果: Hello, world

上述就是一個(gè)簡(jiǎn)單的jni工程搭建步驟,如下是在構(gòu)建和運(yùn)行時(shí)的基本過(guò)程:

  1. Gradle調(diào)用外部構(gòu)建腳本CMakeLists.txt
  2. CMake按照構(gòu)建腳本中的命令將 C++源文件jnitest.cpp 編譯到共享的對(duì)象庫(kù)中,并命名為libnative-lib.so,Gradle隨后在編譯的時(shí)候會(huì)將其打包到APK中。
  3. 運(yùn)行時(shí),應(yīng)用的 MainActivity 會(huì)使用 System.loadLibrary() 加載原生庫(kù)。這時(shí)候應(yīng)用可以使用庫(kù)的原生函數(shù) stringFromJNI(String str)
  4. MainActivity.onCreate() 調(diào)用 stringFromJNI("world"),這將返回“Hello, world”并打印。

參考:https://developer.android.com/studio/projects/add-native-code

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容