Android NDK開發(fā)(一)

最近公司轉做回收,我這個做Android的就無事可干,等著"畢業(yè)",剛好準備一下接下來即將到來的面試等相關信息,順便復習一下當前比較熱門的一些技術,做一下自我總結。以下是在學習NDK開發(fā)過程中的一些筆記,以及自身的一些理解。

NDK的一般使用場景

1.重用一些現(xiàn)成的庫,例如已經(jīng)用C/C++編寫好的openCV庫
2.前面提到的高性能計算,例如很多Bitmap的處理到放在NDK進行處理。
3.一些敏感信息的保護,例如密鑰等信息(密鑰等信息還是要經(jīng)過加鹽才能放到NDK中,不然還是會有別反編譯的風險)

NDK開發(fā)環(huán)境配置

NDK:Android原生開發(fā)套件
CMAKE:外部編譯工具
LLDB:原生代碼調試工具

下載完之后便可以進行配置,配置的過程可進行百度,具體的位置在settings->Appearance&Behavior->System Settings->Android SDK->SDK Tools中,值得注意的是,在比較高版本的IDEA中,LLDB已經(jīng)內置在工具中,所以當你在SDK Tools中找不到LLDB工具時,說明你的工具已經(jīng)自帶了,無需自行安裝;


NDK開發(fā)的簡單流程

首先要創(chuàng)建你的c++原生庫文件,在你的native module中的src/main目錄下創(chuàng)建一個放置C++以及頭部文件的目錄jni(有的人喜歡改為cpp,這個目錄的名稱是自定義的),然后New>C/C++ Source File,輸入一個名稱,例如my-ndk;由于我用的是idea,不是Android studio,所以我直接手動創(chuàng)建my-ndk.cpp文件;
然后創(chuàng)建我們的本地代碼,比如我這里:

package com.hyz.sdk;
public class Utils {
    static {
        System.loadLibrary("my-ndk");
    }

    public static native String getString();
}

然后進入到該類的目錄中,調用命令行javah即可生成.h頭部文件,比如我這里是:
javah com.hyz.sdk.Utils; 這里的命令行還沒有添加相關的參數(shù),比如輸出目錄什么的,為了方便創(chuàng)建構建.h頭部文件,避免每次都需要我們手動去敲一遍javah命令,我們可以為IDEA添加Javah命令工具,我使用的是windows,具體位置如下:

javah擴展工具配置

具體的配置

program: javah的路徑
arguments: -encoding UTF-8 -jni -classpath $OutputPath$ -d $ContentRoot$\src\main\jni $FileClass$
working directory: $ContentRoot$\src

在使用javah命令工具構建之前,你需要先build一下我們的module項目。而后右鍵選擇extend tools中javah工具便可創(chuàng)建我們的.h文件頭,這個 頭文件其實就是Java與C/C++之間的橋梁JNI;

》》擴展:構建SO動態(tài)庫

構建SO動態(tài)庫跟上面的java與C++代碼相互間的調用完全沒有關系,只是單純的想要創(chuàng)建能給給到外部調用的so文件而已。因為我們不可能直接將C++源碼丟給別人,否則為何還這么麻煩,直接使用java不是更好嗎對吧??!構建動態(tài)so庫有有兩種方式:一種是 ndk-build + Android.mk + Application.mk 的方式,另一種是CMake + CMakeLists.txt 的方式,后者是AS 2.2之后,工具中添加的支持,是目前比較簡單且推薦使用的方式。下面是CSDNByteSaid在博客中關于這兩者的使用推薦比較:

如果非必須,不推薦使用 ndk-build 來構建,因為這樣構建源碼后,是無法使用方法跳轉、方法提示等功能的!如果要改代碼,就等于文本編輯器寫代碼。相反 CMake 是支持這些的,因此更有助于提高開發(fā)效率。如果是新建項目就使用 CMake,如果是使用 ndk-build 的老項目,也可以按照下面的步驟轉為CMake方式。

1.既然是推薦,那就先介紹第二種使用CMake+CMakeLists的方式:
首先,第一步、在項目目錄下,創(chuàng)建CMakeLists.txt文件,當然如果你在創(chuàng)建項目的時候已經(jīng)指定了該項目是Native C++項目,它就已經(jīng)自動幫你添加了該文件;文件的內容如下:

#指定最低版本
cmake_minimum_required(VERSION 3.4.1)

#設置生成的so動態(tài)庫最后輸出的路徑
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

#添加動態(tài)庫,如果有多個,可以有多個add_library
add_library( # 設置你的動態(tài)庫名稱
             my-ndk-name
             # 模式
             SHARED
             # 提供動態(tài)庫的文件相對路徑
             src/main/jni/my-ndk.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( # 跟上面的動態(tài)庫名稱相同
                       my-ndk-name
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
#添加相關的依賴
include_directories(src/main/jni/include/)

值得注意的是上面的set(CMAKE_LIBRARY_OUTPUT_DIRECTORY {PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
這是指定生成的so文件的輸出路徑,(這里我是指定了在項目根目錄中創(chuàng)建的jniLibs目錄專門來存放項目生成的so文件),當然你也可以不指定,那默認的輸出路徑就是在module項目的build\intermediates\cmake\debug(release)\obj目錄中。

CMakeLists方式構建圖

第二步,修改module下的build.gradle

    externalNativeBuild {
  externalNativeBuild {
            cmake {
                //cppFlags "-std=c++11 -stdlib=libc++ -fPIC -w"
                cppFlags ""
              //特定參數(shù)
              // arguments "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=c++_shared", "-DANDROID_ARM_MODE=arm", "-DANDROID_ARM_NEON=TRUE", "-DANDROID_PLATFORM=android-21"
                //表示需要生成的so庫目錄
                abiFilters 'arm64-v8a','armeabi-v7a','x86'
            }
        }
        cmake {
            path file('CMakeLists.txt')
        }
    }

至此CMake的配置方式就此完成,點擊module,rebuild一下便可以在輸出目錄看到對應的可使用的so文件了。

2.當然咯,有些人可能會想要使用ndk-build構建方式,或者說有的老項目使用的就是ndk-build構建方式,自己出于某些原因不想修改,那接下來也給出ndk-build的構建方式。
首先使用ndk-build構建方式,本質上跟CMake是沒有什么區(qū)別的。
ndk-build本質上是一個腳本,他的位置就在NDK目錄的最上層,即在<NDK>/ndk-build路徑下。所以我們可以給IDEA配置一個擴展的工具,來快速的構建ndk,而不需要每次都手動構建,類似上面的javah工具,在settings->Tools->External Tools中添加如下工具:

ndk-build擴展工具構建圖

在使用ndk-build構建so時,有兩個重要的配置文件,Android.mkApplication.mk注意不要寫錯名稱哦??!將這兩個文件放在自己的.cpp文件所在的目錄中(比如我的是jni).
由于這兩個文件中的語法并不是咱們的重點,所以大致了解配置中的各個字段的說明即可。

Android.mk一般寫法:

#指定該mk的路徑,$(call my-dir)調用NDK內部的函數(shù)獲取當前mk文件的路徑(固定寫法)
LOCAL_PATH := $(call my-dir)

#清空了除了LOCAL_PATH之外的所有LOCAL_xxx變量的值,(固定寫法)
#這個清理動作是必須的,因為所有的編譯控制文件由同一個GNU Make解析和執(zhí)行,其變量是全局的。所以清理后才能避免相互影響。
include $(CLEAR_VARS)
#.............對于模塊參數(shù)的設置,主要包括:模塊名字、模塊源文件、模塊類型、編譯好的模塊存放位置、以及編譯的平臺等...........
#LOCAL_MODULE模塊必須定義,以表示Android.mk中的每一個模塊,這是一個名字,系統(tǒng)會自動添加適當?shù)那昂缶Y,比如libxxx,下面將生成libmyndk
LOCAL_MODULE := myndk
#LOCAL_SRC_FILES變量必須包含將要打包如模塊的C/C++ 源碼,不必列出頭文件,系統(tǒng)會自動幫我們找出依賴文件,這里請?zhí)顚懽约旱腸pp文件
LOCAL_SRC_FILES := my-ndk.cpp
#.................................
include $(BUILD_SHARED_LIBRARY)
Android.mk

Application.mk的一般寫法:

#過濾,跟cmake方式中的`abiFilters `是一樣的作用。
APP_ABI := armeabi-v7a arm64-v8a
#NDK 構建系統(tǒng)提供了由 Android 系統(tǒng)給出的最小 C++ 運行時庫 (system/lib/libstdc++.so)的 C++ 頭文件
APP_STL := c++_shared
#指定創(chuàng)建的動態(tài)庫的平臺
APP_PLATFORM :=android-21
#該變量是可選的,用來定義 “release” 或者 “debug” ,“release” 模式是默認的
APP_OPTIM := release

至此ndk-build配置方式便配置成功了,右鍵你的native module目錄下的任意一個文件,選擇extend tools中的ndk-build工具,便可生成,當然,你也可以通過進入到module目錄,打開doc命令行,輸入ndk-build命令也是可以的。


ndk-build構建so

如此同時,會生成兩個目錄,一個libs目錄,里面放置的就是你的so文件,一個名為obj的目錄,這個 是中間目錄可以不用理會,是不是跟之前cmake生成的默認目錄一樣呢,里面同樣存在有你需要的so文件,這里我想應該也是可以跟cmake一樣修改輸出目錄的,具體的這里就不作介紹了。
以下是兩種構建方式,配置文件中參數(shù)的相互對應表:


》》SO庫文件的引用與使用

要是用已經(jīng)打好的so庫文件或者以來第三方庫的so文件,首先需要將so庫文件放置在libs目錄或者自定義的目錄中(比如有些人喜歡放在src目錄下的jniLibs目錄中),然后再module下的build.gradle中引用so庫,具體如下:

android {
    //...
    defaultConfig {
       //version,versioncode,applicationID等信息
        ndk {
            //針對自己項目的架構對應添加相應的so目錄
            //目前的手機架構基本上都是arm架構,x86的基本上沒有,基本上是平板
            abiFilters "armeabi-v7a",//arm架構的32位
                    "armeabi",//十年前的手機CPU架構,基本上已經(jīng)不存在了
                    "arm64-v8a",//arm架構的64位
                    "x86",//x86架構的 32位
                    "x86_64"http://x86架構的64位
        }
    }
      //省略其他配置...
    sourceSets {
        main {
            //這里的libs需要替換成你放置so庫的目錄,比如jniLibs
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
   //省略其他配置....
}

注意當你的使用so跟構建so都在同一個module中的時候,這里的abiFilters會覆蓋掉上面構建so庫文件過程中配置的'abiFilters'。故此不建議將構建so跟使用so都放在同一個項目中,不熟悉可能會感覺有點亂??梢詥为殞⑿枰獦嫿╯o庫的代碼放在一個以來項目中進行構建。


參考鏈接:
https://blog.csdn.net/weixin_42011443/article/details/117307993
https://blog.csdn.net/weixin_34583170/article/details/94864797
https://blog.csdn.net/hello_1995/article/details/108875866

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容