Android JNI學(xué)習(xí)(二)——實(shí)戰(zhàn)JNI之“hello world”

本系列文章如下:

本地內(nèi)容主要簡(jiǎn)介如下:

  • 1、環(huán)境展示
  • 2、傳統(tǒng)方式的具體流程
  • 3、傳統(tǒng)方式的相關(guān)問題
  • 4、傳統(tǒng)方式的so文件
  • 5、通過CMake工具demo演示流程
  • 6、CMake工具demo的背后原理
  • 7、CMake的應(yīng)用
  • 8、使用experimental-plugin插件編譯
本篇文章大綱.png

一、環(huán)境展示

操作系統(tǒng)為


操作系統(tǒng).png

Android環(huán)境為:


Android環(huán)境.png

NDK環(huán)境


NDK環(huán)境.png

模擬器為


模擬器.png

二、傳統(tǒng)方式的具體流程

具體流程如下:

(一) 創(chuàng)建項(xiàng)目

首先在Android Studio創(chuàng)建一個(gè)Android項(xiàng)目,包名為gebilaolitou.ndkdemo

(二) 創(chuàng)建引用本地庫的工具類

然后創(chuàng)建一個(gè)classNDKTools

代碼如下:

package gebilaolitou.ndkdemo;

public class NDKTools {

    public static native String getStringFromNDK();

}

(三) 修改相關(guān)UI顯示

MainActivity對(duì)應(yīng)的xml中的textview添加id
如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="gebilaolitou.ndkdemo.MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

然后修改MainActivity,在里面調(diào)用NDKToolsgetStringFromNDK()方法。

package gebilaolitou.ndkdemo;

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

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String text = NDKTools.getStringFromNDK();
        Log.i("gebilaolitou","text="+text);
        ((TextView)findViewById(R.id.tv)).setText(text);
    }
}

(四) 獲取classes文件

在Android Studio中點(diǎn)擊Build中的Make Project或者Rebuild Project進(jìn)行編譯來獲取中間文件。如下圖

編譯.png

編譯完成后,我們就可以獲取class文件如下圖
classes文件.png

(五) 進(jìn)入相應(yīng)目錄

點(diǎn)擊Android Studio下面的Terminal,然后跳到NDKDemo/app/build/intermediates/classes/debug下(其中NDKDemo為是項(xiàng)目的根目錄),在Terminal執(zhí)行pwd確認(rèn)目錄。

(六) 獲取.h文件

NDKDemo/app/build/intermediates/classes/debug下執(zhí)行下面的命令javah -jni gebilaolitou.ndkdemo.NDKTools。如果沒有問題,則會(huì)在NDKDemo/app/build/intermediates/classes/debug下面生成gebilaolitou_ndkdemo_NDKTools.h文件。如下圖

頭文件.png

其內(nèi)容如下:

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

#ifndef _Included_gebilaolitou_ndkdemo_NDKTools
#define _Included_gebilaolitou_ndkdemo_NDKTools
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     gebilaolitou_ndkdemo_NDKTools
 * Method:    getStringFromNDK
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_gebilaolitou_ndkdemo_NDKTools_getStringFromNDK
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

如下圖


頭文件內(nèi)容.png

(七) 增加對(duì)應(yīng)的.c文件

在工程main目錄下創(chuàng)建一個(gè)名字為jni目錄,然后將剛才的.h文件剪切過來。在jni目錄下新建一個(gè)c文件。命名為ndkdemotest.c。此時(shí)項(xiàng)目目錄如下:

jnipng

(八) 編寫ndkdemotest.c文件

將ndkdemotest.c協(xié)商如下內(nèi)容

#include "gebilaolitou_ndkdemo_NDKTools.h"

JNIEXPORT jstring JNICALL Java_gebilaolitou_ndkdemo_NDKTools_getStringFromNDK
  (JNIEnv *env, jobject obj){
     return (*env)->NewStringUTF(env,"Hellow World,這是隔壁老李頭的NDK的第一行代碼");
  }
ndkdemotest.png

內(nèi)容不多,就是兩部分,第一部分就是 添加gebilaolitou_ndkdemo_NDKTools.h頭文件,然后就是具體實(shí)現(xiàn)Java_gebilaolitou_ndkdemo_NDKTools_getStringFromNDK函數(shù)

(九) 添加并編寫Android.mk文件

同樣在jni目錄下,添加一個(gè)Android.mk文件,其目錄結(jié)構(gòu)如下:


添加Android.mk文件.png

同樣在Android.mk文件里面編寫如下內(nèi)容

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := ndkdemotest-jni

LOCAL_SRC_FILES := ndkdemotest.c

include $(BUILD_SHARED_LIBRARY)
Android.mk內(nèi)容.png

關(guān)于Android.mk語言后面會(huì)單獨(dú)寫一篇文章進(jìn)行講解,這里重點(diǎn)說上面代碼的內(nèi)容

  • LOCAL_PATH := $(call my-dir):每個(gè)Android.mk文件必須以定義開始。它用于在開發(fā)tree中查找源文件。宏my-dir則由Build System 提供。返回包含Android.mk目錄路徑。
  • include $(CLEAR_VARS)CLEAR_VARS變量由Build System提供。并指向一個(gè)指定的GNU Makefile,由它負(fù)責(zé)清理很多LOCAL_xxx。例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等等。但不是清理LOCAL_PATH。這個(gè)清理是必須的,因?yàn)樗械木幾g控制文件由同一個(gè)GNU Make解析和執(zhí)行,其變量是全局的。所以清理后才能便面相互影響。
  • LOCAL_MODULE := ndkdemotest-jni:LOCAL_MODULE模塊必須定義,以表示Android.mk中的每一個(gè)模塊。名字必須唯一且不包含空格。Build System 會(huì)自動(dòng)添加適當(dāng)?shù)那熬Y和后綴。例如,demo,要生成動(dòng)態(tài)庫,則生成libdemo.so。但請(qǐng)注意:如果模塊名字被定義為libabd,則生成libabc.so。不再添加前綴。
  • LOCAL_SRC_FILES := ndkdemotest.c:這行代碼表示將要打包的C/C++源碼。不必列出頭文件,build System 會(huì)自動(dòng)幫我們找出依賴文件。缺省的C++ 源碼的擴(kuò)展名為.cpp。
  • include $(BUILD_SHARED_LIBRARY)BUILD_SHARED_LIBRARY是Build System提供的一個(gè)變量,指向一個(gè)GUN Makefile Script。它負(fù)責(zé)收集自從上次調(diào)用include $(CLEAR_VARS)后的所有LOCAL_xxxxinx。并決定編譯什么類型
    • BUILD_STATIC_LIBRARY:編譯為靜態(tài)庫
    • BUILD_SHARED_LIBRARY:編譯為動(dòng)態(tài)庫
    • BUILD_EXECUTABLE:編譯為Native C 可執(zhí)行程序
    • BUILD_PREBUILT:該模塊已經(jīng)預(yù)先編譯

PS:這里不編寫Android.mk會(huì)提示如下問題:

Error:Execution failed for task ':app:compileDebugNdk'.
> Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio.  Please switch to a supported build system.
  Consider using CMake or ndk-build integration. For more information, go to:
   https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile
   To get started, you can use the sample ndk-build script the Android
   plugin generated for you at:
   /Users/gebilaolitou/AndroidStudioProjects/JNIDemo/app/build/intermediates/ndk/debug/Android.mk
  Alternatively, you can use the experimental plugin:
   https://developer.android.com/r/tools/experimental-plugin.html
  To continue using the deprecated NDK compile for another 60 days, set 
  android.deprecatedNdkCompileLease=1523001628930 in gradle.properties

全是英文,簡(jiǎn)單的翻譯下如下:

錯(cuò)誤:執(zhí)行app:compileDebugNdk任務(wù)失敗
錯(cuò)誤:不再支持android.useDeprecatedNdk標(biāo)志,并且將會(huì)在未來的Android Studio版本中刪除這個(gè)標(biāo)志。請(qǐng)切換到CMake構(gòu)建系統(tǒng)或者ndk-build中集成。更多的信息請(qǐng)參考https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile。您可以使用Android的示例ndk-build腳本在以下位置生成的插件:
/Users/gebilaolitou/AndroidStudioProjects/JNIDemo/app/build/intermediates/ndk/debug/Android.mk。另外,你也可以使用實(shí)驗(yàn)性插件https://developer.android.com/r/tools/experimental-plugin.html
如果你還想繼續(xù)再使用已經(jīng)被棄用的NDK編譯60天,你需要再gradle.properties中設(shè)置android.deprecatedNdkCompileLease=1523001628930

因?yàn)橐陨显?,我所以我們需要設(shè)置Android.mk

(十) 修改相應(yīng)的配置文件

首先檢查local.properties文件中是否有NDK路徑,如果有沒有NDK路徑,則添加NDK路徑,比如我的如下:

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

其次修改app module目錄下的build.gradle中的內(nèi)容,如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "gebilaolitou.ndkdemo"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        ndk{

            moduleName "ndkdemotest-jni"
            abiFilters "armeabi", "armeabi-v7a", "x86"

        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        externalNativeBuild {
            ndkBuild {
                path 'src/main/jni/Android.mk'
            }
        }
        sourceSets.main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}

這樣就有了so文件(此時(shí)還沒生成so文件)

(十一) 修改引用類

最后NDKTools類中添加靜態(tài)初始化代碼,如下:

public class NDKTools {

    static {
        System.loadLibrary("ndkdemotest-jni");
    }

    public static native String getStringFromNDK();
}

最后run一下即可,如下圖


顯示.png

三、傳統(tǒng)方式的相關(guān)問題

有的同學(xué)在運(yùn)行的時(shí)候,會(huì)報(bào)如下錯(cuò)誤:

Error:Execution failed for task ':app:compileDebugNdk'.
> Error: Your project contains C++ files but it is not using a supported native build system.
  Consider using CMake or ndk-build integration. For more information, go to:
   https://d.android.com/r/studio-ui/add-native-code.html
  Alternatively, you can use the experimental plugin:
   https://developer.android.com/r/tools/experimental-plugin.html

首先把檢查你項(xiàng)目中g(shù)radle.properties文件后面加上一句

Android.useDeprecatedNdk=true 

四、傳統(tǒng)方式的so文件

大家可能會(huì)有疑問,那so去哪里了,我們平時(shí)使用第三方的sdk的so的時(shí)候,會(huì)要粘貼復(fù)制到項(xiàng)目里面,而我們上所述整個(gè)過程,并沒有出現(xiàn).so這個(gè)文件,那么這個(gè).so去哪里了?

其實(shí)Android Studio自動(dòng)幫我們把so放到apk里面,如果我們想找也能找到,如下圖:


so文件的位置.png

上面這套方式是傳統(tǒng)的Android Studio的模式,那有沒有更簡(jiǎn)單的方式,是有的,那下面我們就繼續(xù)來看下

五、通過CMake工具demo演示流程

(一) 首先確保你本地有CMake,我們來看下SDK Tools

SDK Tools.png

上面看到第三個(gè) CMake 我本地沒有,所以我要進(jìn)行安裝

(二) 勾選Include C++ Support復(fù)選框。

在向?qū)У?Configure your new project 部分,選中 Include C++ Support 復(fù)選框。
如下圖


勾選.png

這里有個(gè)坑,就是有好多同學(xué)說我沒有這個(gè)Include C++ Support復(fù)選框,這是因?yàn)锳ndroid Studio設(shè)計(jì)的的"bug",你把這個(gè)對(duì)話框進(jìn)行拉大,就出現(xiàn)了,因?yàn)橐话愕腁ndroid 項(xiàng)目用不到,所以在設(shè)計(jì)這個(gè)的時(shí)候,如果不特意的拉大,就選擇性的"隱藏"了,太JB坑了。

然后一直下一步,直到Customize C++ Support部分

(三) Customize C++ Support的自定義項(xiàng)目

如下:


模式.png

里面有個(gè)三個(gè)項(xiàng)目

  • C++ Standard:即C++標(biāo)準(zhǔn),使用下拉列表選擇你希望使用的C++的標(biāo)準(zhǔn),選擇Toolchain Default 會(huì)使用默認(rèn)的CMake設(shè)置。
  • Exceptions Support:如果你希望啟用對(duì)C++異常處理的支持,請(qǐng)選擇此復(fù)選框。如果啟動(dòng)此復(fù)選框,Android Studio 會(huì)將-fexceptions標(biāo)志添加到模塊級(jí)build.gradle文件的cppFlags中,Gradle會(huì)將其傳遞到CMake。
  • Runtime Type Information Support:如果開發(fā)者希望支持RTTI,請(qǐng)選中此復(fù)選框。如果啟用此復(fù)選框,Android Studio 會(huì)將-frtti標(biāo)志添加到模塊級(jí)build.gradle文件的cppFlags中,Gradle會(huì)將其傳遞到CMake。

最后點(diǎn)擊 Finish。

(四) 檢查 Android 目錄

在Android Studio 完成新項(xiàng)目的創(chuàng)建后,請(qǐng)從IDE左側(cè)打開Project 礦口并選擇Android 視圖。如下圖所示,Android Studio 將添加cppExternal Build Files 組

Android模式.png

該圖為開發(fā)者的原生源文件和外部構(gòu)建腳本的Android 視圖組。

PS:(此視圖無法反應(yīng)磁盤上的實(shí)際文件層次結(jié)構(gòu),而是將相似文件分到一組中,簡(jiǎn)化項(xiàng)目導(dǎo)航)。如果為Project模式則如下:

Project模式.png

那我們簡(jiǎn)單介紹下這兩個(gè)多出來的文件夾:

  • cpp 文件夾中:可以找到屬于項(xiàng)目的所有原生源文件等構(gòu)建庫。對(duì)于新項(xiàng)目,Android Studio會(huì)創(chuàng)建一個(gè)示例C++源文件 native-lib.cpp,并將其置于應(yīng)用模塊src/main/cpp/目錄中。這個(gè)示例代碼提供了一個(gè)簡(jiǎn)單的C++函數(shù)stringFromJNI(),此函數(shù)可以返回字符串“Hello from C++”
  • External Build Files 文件夾中:可以找到CMake或 ndk-build 的構(gòu)建腳本。與build.gradle文件指示Gradle構(gòu)建應(yīng)用一樣,CMake和ndk-build需要一個(gè)構(gòu)建腳本來了解如何構(gòu)原生庫。對(duì)于新項(xiàng)目,Android Studio 會(huì)創(chuàng)建一個(gè)CMake 構(gòu)建腳本CMakeLists.txt,并將其置于模塊根目錄中。

(五) 直接運(yùn)行項(xiàng)目

我們來直接 run一下這個(gè)項(xiàng)目,看下結(jié)果


結(jié)果1.png

(六) 修改native-lib.cpp

這時(shí)候我們修改下native-lib.cppnative-lib.cpp內(nèi)容如下:

native-lib.cpp內(nèi)容.png

再直接run一下項(xiàng)目,看下結(jié)果。如下:


結(jié)果2.png

我們看到對(duì)應(yīng)的文字已經(jīng)修改了

六、CMake工具demo的背后原理

我們看打了,我們什么都沒做,就自動(dòng)實(shí)現(xiàn)了C++的實(shí)現(xiàn),它的背后原理是什么那?我們大家就思考一下?

(一)CMake的入口

它既然可以跑起來,一定有一個(gè)入口,那這個(gè)入口在哪里那?

~~~~~~~~~~~~~~~~~~~~~~~~~~分隔符~~~~~~~~~~~~~~~~~~~~

先和大家說下我是怎么想象的,首先我們?cè)邳c(diǎn)擊Android Studio中的run按鈕的時(shí)候,它是執(zhí)行Gradle來進(jìn)行打包的,所以說關(guān)于CMake的是怎么植入進(jìn)去的,一定在項(xiàng)目的build.gradle,有相應(yīng)的入口。

通過上面的思想,我們能舉一反三得到什么?對(duì)的,就是類似于這種操作,一般都是在build.gradle里面實(shí)現(xiàn)的,因?yàn)樵谀壳癆ndroid Studio就是通過Gradle是實(shí)現(xiàn)的

那我們就來看下它的build.gradle里面的代碼,如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "gebilaolitou.cmakendkdemo"
        minSdkVersion 23
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

和我們平時(shí)搭建的項(xiàng)目差不多,就是多出來一塊內(nèi)容,externalNativeBuild。那這里我們重點(diǎn)說下externalNativeBuild

(二) externalNativeBuild

我們?cè)?code>build.gradle里面看到,有兩個(gè)地方用到了externalNativeBuild,一個(gè)是在defaultConfig里面,是一個(gè)是在defaultConfig外面。

  • defaultConfig外面的externalNativeBuild里面的cmake指明了CMakeList.txt的路徑(在本項(xiàng)目下,和是build.gradle在同一個(gè)目錄里面)。
  • defaultConfig里面的externalNativeBuild里面的cmake主要填寫的是CMake的命令參數(shù)。即由arguments中的參數(shù)最后轉(zhuǎn)化成一個(gè)可執(zhí)行的CMake的命令,可以在

defaultConfig外面的 externalNativeBuild - cmake,指明了 CMakeList.txt 的路徑;
defaultConfig 里面的 externalNativeBuild - cmake,主要填寫 CMake 的命令參數(shù)。即由 arguments 中的參數(shù)最后轉(zhuǎn)化成一個(gè)可執(zhí)行的 CMake 的命令,可以在 app/externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt中查到。如下
路徑位置如下圖:

路徑.png

內(nèi)容如下:

arguments : 
-H/Users/gebilaolitou/Desktop/codeLib/CMakeNDKDemo/app
-B/Users/gebilaolitou/Desktop/codeLib/CMakeNDKDemo/app/.externalNativeBuild/cmake/debug/x86
-DANDROID_ABI=x86
-DANDROID_PLATFORM=android-23
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/Users/gebilaolitou/Desktop/codeLib/CMakeNDKDemo/app/build/intermediates/cmake/debug/obj/x86
-DCMAKE_BUILD_TYPE=Debug
-DANDROID_NDK=/Users/gebilaolitou/Library/Android/sdk/ndk-bundle
-DCMAKE_CXX_FLAGS=
-DCMAKE_TOOLCHAIN_FILE=/Users/gebilaolitou/Library/Android/sdk/ndk-bundle/build/cmake/android.toolchain.cmake
-DCMAKE_MAKE_PROGRAM=/Users/gebilaolitou/Library/Android/sdk/cmake/3.6.4111459/bin/ninja
-GAndroid Gradle - Ninja
jvmArgs : 

更多的可以填寫的命令參數(shù)和含義可以參見Android NDK-CMake文檔

ok上面既然提到了CMakeLists.txt,那我們就來看下CMakeLists.txt

(三) CMakeLists.txt

CMakeLists.txt這個(gè)文件主要定義了哪些文件需要編譯,以及和其他庫的關(guān)系等,那讓我們來看下我們項(xiàng)目中的CMakeLists.txt的內(nèi)容

# 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).
             src/main/cpp/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} )

上面很多是注釋,我們除去注釋來個(gè)"精簡(jiǎn)干練版"的如下:

cmake_minimum_required(VERSION 3.4.1)

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).
             src/main/cpp/native-lib.cpp )


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 )

target_link_libraries( # Specifies the target library.
                       native-lib

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

CMakeLists.txt我們看到這里主要是分為四個(gè)部分,下面我們就依次來看下

  • cmake_minimum_required(VERSION 3.4.1):指定CMake的最小版本
  • add_library:創(chuàng)建一個(gè)靜態(tài)或者動(dòng)態(tài)庫,并提供其關(guān)聯(lián)的源文件路徑,開發(fā)者可以定義多個(gè)庫,CMake會(huì)自動(dòng)去構(gòu)建它們。Gradle可以自動(dòng)將它們打包進(jìn)APK中。
    • 第一個(gè)參數(shù)——native-lib:是庫的名稱
    • 第二個(gè)參數(shù)——SHARED:是庫的類別,是動(dòng)態(tài)的還是靜態(tài)的
    • 第三個(gè)參數(shù)——src/main/cpp/native-lib.cpp:是庫的源文件的路徑
  • find_library:找到一個(gè)預(yù)編譯的庫,并作為一個(gè)變量保存起來。由于CMake在搜索庫路徑的時(shí)候會(huì)包含系統(tǒng)庫,并且CMake會(huì)檢查它自己之前編譯的庫的名字,所以開發(fā)者需要保證開發(fā)者自行添加的庫的名字的獨(dú)特性。
    • 第一個(gè)參數(shù)——log-lib:設(shè)置路徑變量的名稱
    • 第一個(gè)參數(shù)—— log:指定NDK庫的名子,這樣CMake就可以找到這個(gè)庫
  • target_link_libraries:指定CMake鏈接到目標(biāo)庫。開發(fā)者可以鏈接多個(gè)庫,比如開發(fā)者可以在此定義庫的構(gòu)建腳本,并且預(yù)編譯第三方庫或者系統(tǒng)庫。
    • 第一個(gè)參數(shù)——native-lib:指定的目標(biāo)庫
    • 第一個(gè)參數(shù)——${log-lib}:將目標(biāo)庫鏈接到NDK中的日志庫,

這其實(shí)是一個(gè)最基礎(chǔ)的CMakeLists.txt ,其實(shí)CMakeLists.txt里面可以非常強(qiáng)大,比如自定義命令、查找文件、頭文件包含、設(shè)置變量等等。這里推薦CMake官網(wǎng)文檔,不過是英文的,不好閱讀,大家可以參考中文的CMake手冊(cè)

上面分析完畢CMakeLists.txt,我們就大致的知道了CMake整體的構(gòu)建流程,那我們就來看下

(四) CMake的運(yùn)轉(zhuǎn)流程

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

PS:這里注意一點(diǎn)就是:Instant Run 與使用原生的項(xiàng)目不兼容

如果想看Gradle是否將原生庫打包到APK中,可以使用Analyze APK來檢測(cè)。

七、CMake的應(yīng)用

我們?cè)谧鋈粘P枨蟮臅r(shí)候,往往會(huì)遇到一個(gè)問題,即在已有的項(xiàng)目中,添加C庫,這樣就不能通過上面的創(chuàng)建流程,來使用CMake。那怎么辦?

其實(shí)沒關(guān)系的,CMake也提供這樣的功能的,現(xiàn)在我們就回到上面的第一個(gè)demo中,刪除和NDK的有關(guān)的所有代碼,刪除后其目錄如下:


新目錄.png

(一) 創(chuàng)建源文件

即在main目錄下新建一個(gè)目錄,我們就叫cpp好了。然后在該目錄下創(chuàng)建一個(gè)C++ Source File(右鍵點(diǎn)擊您剛剛創(chuàng)建的目錄,然后選擇 New > C/C++ Source File)。我們將其命名為native-lib。

創(chuàng)建后,目錄如下:


創(chuàng)建源文件.png

(二) 創(chuàng)建CMake構(gòu)建腳本

因?yàn)槟壳斑@個(gè)項(xiàng)目沒有CMake的構(gòu)建腳本,所以咱們需要自行創(chuàng)建一個(gè)并包含適當(dāng)?shù)腃Make命令。CMake構(gòu)建腳本是一個(gè)純文本的文件,而且這個(gè)名字必須是是CMakeLists.txt

要常創(chuàng)建一個(gè)可以用作CMake構(gòu)建腳本的純文本文件,請(qǐng)按以下步驟操作:

  • 1、從Android Studio左側(cè)打開Project窗格并從下拉菜單中選擇Project視圖。
  • 2、右鍵點(diǎn)擊 模塊的根目錄并選擇 New——> File。
    PS:這個(gè)位置不是不固定的,位置可以隨意,但是配置構(gòu)建腳本時(shí),需要將這個(gè)位置寫入構(gòu)建腳本
  • 3、輸入CMakeLists.txt作為文件并點(diǎn)擊OK

創(chuàng)建后,目錄如下:


CMakeLists.txt.png

(三) 向CMake腳本文件寫入數(shù)據(jù)

這塊上面講解了過了,就不詳細(xì)說明了,內(nèi)容如下:

cmake_minimum_required(VERSION 3.4.1)

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). 
             src/main/cpp/native-lib.cpp )

find_library( # Defines the name of the path variable that stores the
              # location of the NDK library.
              log-lib

              # Specifies the name of the NDK library that
              # CMake needs to locate.
              log )
              

target_link_libraries( # Specifies the target library.
                       native-lib

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

(四) 向Gradle 關(guān)聯(lián)到原生庫

要將Gradle關(guān)聯(lián)到原生庫,需要提供一個(gè)指向CMake或ndk-build 腳本文件的路徑。在構(gòu)建應(yīng)用時(shí),Gradle會(huì)以依賴項(xiàng)的形式運(yùn)行CMake或ndk-build,并將共享的庫打包到APK中。Gradle還是用構(gòu)建腳本來了解將那些文件添加到Android 項(xiàng)目中。
如果原生文件還沒有構(gòu)建腳本,需要?jiǎng)?chuàng)建CMake構(gòu)建腳本

關(guān)于 關(guān)聯(lián)到原生庫有兩種方式,一種是通過Android Studio,一種是手動(dòng),其實(shí)其背后的東西是一致的,我們就一一來說明

1、通過Android Studio 實(shí)現(xiàn)
  • 1、從IDE 左側(cè)打開Project 窗格 并選擇 Android 視圖
  • 2、右鍵點(diǎn)擊想要關(guān)聯(lián)到原生庫的模塊(咱們這里是app 模塊),并從菜單中選擇 Link C++ Project with Gradle。如下圖
  • 3、在下拉菜單中選擇CMake。使用Project Pat來為外部的CMake項(xiàng)目指定剛剛的``CMakeLists.txt`腳本文件
  • 4、點(diǎn)擊OK。
Link C++ Project with Gradle.png
2、手動(dòng)實(shí)現(xiàn)

要手動(dòng)配置Gradle 以關(guān)聯(lián)到原生庫,需要將externalNativeBuild{} 塊添加到模塊級(jí) build.gradle 文件中,并使用cmake {}對(duì)其進(jìn)行配置

代碼如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "gebilaolitou.ndkdemo"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

    }

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

(五) 編寫native-lib.cpp

這塊很簡(jiǎn)單,內(nèi)容如下:

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

extern "C"
JNIEXPORT jstring

JNICALL
Java_gebilaolitou_ndkdemo_NDKTools_getStringFromNDK(
        JNIEnv *env, jobject /* this */) {
    std::string hello = "(*^__^*) 嘻嘻……~Hello from C++ 隔壁老李頭";
    return env->NewStringUTF(hello.c_str());
}

然后在NDKTools.java添加引用,如下:

package gebilaolitou.ndkdemo;


public class NDKTools {

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

    public static native String getStringFromNDK();
}

然后直接運(yùn)行,即可,結(jié)果如下:


結(jié)果3.png

八、使用experimental-plugin插件簡(jiǎn)介

我們?cè)谑褂肗DK開發(fā)有件比較麻煩的事情,就是編寫Android.mk和Application.mk,兒Android Studio的插件gradle-experimental就是用來解決這個(gè)問題的。所以使用gradle-experimental插件可以不用再編寫.mk文件情況下進(jìn)行NDK開發(fā)。

gradle-experimental是Android Studio的一個(gè)實(shí)驗(yàn)性的項(xiàng)目,是基于gradle的一個(gè)插件,主要用來自動(dòng)化NDK的配置實(shí)現(xiàn),無需自己編寫Android.mk和Android.mk,對(duì)于調(diào)試NDK項(xiàng)目也更加友好,不過現(xiàn)在已經(jīng) 不支持 ,詳細(xì)請(qǐng)看Experimental Plugin User Guide

Note to experimental Android plugin users: The experimental plugin will no longer be supported after version 0.11.0 (released October 25, 2017). That's because the experimental plugin is designed around a Software Component Model that Gradle announced they will no longer support (read their blog post here). Gradle has backported many features from the component model, which are now available with Android plugin 3.0.0, such as variant-aware dependency resolution, and api and implementation dependency configurations. Gradle is working on backporting built-in support for compiling C/C++ code, and the Android plugin will integrate that support when it becomes available. Until then, you can either keep using experimental plugin 0.11.0 with Android Studio 3.0 or later, or migrate to Android Studio's support for using external native build tools.

簡(jiǎn)單翻譯下如下:

對(duì)使用experimentalAndroid插件的用戶請(qǐng)注意:自2017年10月25日發(fā)布的0.11.0后,我們將不再支持experimental插件了。因?yàn)镚radle不再支持這個(gè)依靠軟件組件模型設(shè)計(jì)experimental插件了(通過他們的博客)。在Gradle Android插件的3.0.0版本,現(xiàn)在已經(jīng)支持組建模型中的許多功能。例如variant-aware dependency resolutionapi and implementation dependency configurations。Gradle現(xiàn)在支持編譯C/C++代碼的內(nèi)置支持,并且Android插件再可用時(shí)集成該支持。在此之間,您可以繼續(xù)使用Android Studio3.0或者更高版本的experimental插件,或者使用Android Studio支持的外部原生構(gòu)建工具。

上一篇文章Android JNI(一)——NDK與JNI基礎(chǔ)
下一篇文章Android JNI學(xué)習(xí)(三)——Java與Native相互調(diào)用

最后編輯于
?著作權(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)容