Android NDK 開發系列(1) : 環境搭建與JNI程序解析

一、JNI 與 NDK

1、JNI定義
JNI : Java Native Interface的縮寫,從字面意思理解即Java的本地接口,Java 與 Native(即C/C++)層通信的橋樑。
2、NDK的定義
NDK:Native Development Kit的縮寫,即Android的工具開發包。作用即將C/C++代碼編譯到原生庫中。
3、Java,JNI 與NDK三者之間的關係
1、NDK將C/C++編譯到原生庫,然後使用IDE的集成編譯系統將原生庫打包到APK中。
2、JNI編寫對應的接口函數,函數的內部實現就是由C/C++來完成的。
3、Java代碼通過調用JNI編寫好的接口函數,調用底層C/C++實現的功能函數。
關於編譯流程可參考 :https://developer.android.com/studio/build/

二、NDK下載

下載路徑:https://developer.android.com/ndk/downloads
當然也可使用Android Studio 進行下載,只需要通過SDK Manager進行下載
注意:在下載過程中,我們需要下載兩個構建工具:

  • CMake:一個跨平臺的編譯構建工具
  • LLDB:C/C++的調試工具

三、編寫JNI程序

1.AndroidStudio會自動生成一個native-lib.cpp以及CmakeLists.txt文件

2.MainActivity的分析

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        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());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

Java 調用本地方法,使用的是native關鍵字,而這個本地方法的實現在native-lib這個動態庫中實現的,如果說使用動態庫,就必須先加載這個動態庫,那麼就有了這個System.loadLibrary(“native-lib”)方法,這個是Java規定的。

3、native-lib.cpp的具體實現

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

extern "C" JNIEXPORT jstring JNICALL
Java_com_android_jni_demo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

那麼MainActivity中native方法的具體實現就是由native-lib.cpp的Java_com_android_jni_demo_MainActivity_stringFromJNI()來完成;

該方法使用的是靜態註冊的方式,即Java_包名_類名_方法名的形式。

4、調用流程解析

1、Gradle調用外部構建腳本CmakeLists.txt
2、CMake按照構建腳本中的命令將C++源文件native-lib.cpp編譯到共享庫中,並命名爲libnative-lib.so,Gradle編譯系統將該文件打包到APK中。
3、運行時,應用的MainActivity會調用靜態代碼塊System.loadLibrary()進行原生庫的加載,那麼應用就可以使用案例中原生庫的stringFromJNI()方法。
4、通過調用stringFromJNI() 返回"Hello from C++"字符串,也就完成了Java層調用原生庫中的函數返回的數據。

四、Gradle腳本的相關解析

要配置cmakelists.txt的路徑,gradle在進行編譯的時候,就會加載cmakelists.txt文件,通過cmake編譯工具,執行文件中所編寫的指令生成相應的動態庫。通過配置externalNativeBuild,
並且需要使用cmake{} 進行配置CMakeLists.txt 文件的路徑,用於cmake構建構建工具執行相關編譯指令。

 android {
  ..... 
  defaultConfig{...}
  externalNativeBuild{
		cmake{
			 path "src/main/cpp/CMakeLists.txt"
	          version "3.10.2"
		}
	}
}

五、CmakeLists.txt的相關解析

1、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_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.

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        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-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})
  • 1、指定cmake的最小編譯版本 :cmake_minimum_required
  • 2、添加需要生成的原生庫信息:add_library 其中包含原生庫的名稱,原生庫的源文件路徑,以及指定是生成共享庫(SHARED - .so文件)還是靜態庫(STATIC - .a文件)
  • 3、查找原生庫所需要的第三方庫或者是系統庫:find_library 其中指定的庫大多是系統的公共庫,cmake默認搜索的路徑也就是公共庫的路徑,在這個find_library( var path name) 其中name是存在的公共庫的名稱,var path 是一個變量用來存儲name庫的path路徑。
  • 4、最後一步就是將生成的原生庫和共享庫進行連接,即target_link_libraries(); 該指令裏面會指定3步中var path變量,通過${path)的方式找到這個公共庫,還有就是一個native-lib是該項目所要生成的原生庫的名稱。

可以參照我對於camkelists.txt文件的一些理解,如果有理解不對的地方,歡迎留言討論:

##指定cmake最小版本
cmake_minimum_required(VERSION 3.4.1)

## 指定需要生成庫的名稱,生成庫的源文件路徑,以及指定生成的庫是動態庫還是靜態庫
add_library(
    ##指定生成的庫的名稱比如名字叫做hash
    hash
    ##指定是動態庫還是靜態庫 比如說生成一個動態庫(SHARED), 靜態庫則是(STATIC)
    SHARED
    ##指定生成庫的源文件路徑比如說是工程下的src/main/cpp/hash.cpp
    src/main/cpp/hash.cpp
)

## 查找生成庫可能要依賴的其他庫 比如NDK 的 log 庫

## 該命令是指的是查找指定的庫文件; find_library(VAR name path) 查找到指定的預編譯庫,並將它的路徑存儲在變量中。
## 默認的搜索路徑爲cmake包含的系統庫(解釋: 也就是說你你指定一個log庫,該庫是位於NDK的公共庫中,屬於系統庫,那麼這個時候指定
庫的名稱即可,這個時候只需要指定該庫的一個變量名稱,方便在調用target_link_libraries 鏈接該庫的時候使用其聲明的一個變量名即可,
 該變量名存儲的是該系統庫的路徑。)
find_library(
    ## 首先指定該庫對應的變量名 比如說 log-lib
    log-lib

    ##
    log
)

## 關聯庫 原則 目標庫需要使用第三方庫或者系統庫,需要先進行find_library操作(1,找到這些庫的路徑,2,將這些庫的路徑保存到相應的
變量中,方便target_link_libraries調用)

target_link_libraries(
    ## 目標庫
    hash

    ## 需要關聯的第三方或者系統庫
    ${log_lib}

 )

此處也參考了許多前輩的總結經驗,如有侵犯,請告知。還有如果有需要轉載請註明。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章