Android NDK基礎介紹及例子

来源:https://www.cnblogs.com/zsc02/archive/2023/08/24/17654719.html
-Advertisement-
Play Games

# [TOC] [Android開發中的NDK到底是什麼?(詳細解析+案例) - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/415536928) # NDK介紹 **(1)簡介** **定義:**`Native Development Kit`,是 ` ...


目錄

Android開發中的NDK到底是什麼?(詳細解析+案例) - 知乎 (zhihu.com)

NDK介紹

(1)簡介

定義:Native Development Kit,是 Android的一個工具開發包

  • NDK是屬於 Android 的,與Java並無直接關係

作用:快速開發CC++的動態庫,並自動將so和應用一起打包成 APK

  • 即可通過NDKAndroid中 使用JNI與本地代碼(如C、C++)交互

應用場景:Android的場景下 使用JNI

  • Android開發的功能需要本地代碼(C/C++)實現

java調用c的步驟

  1. package com.fs.test;
    public class HelloWorld {
    //本地方法
    	private native void show();
    	public static void main(String[] args)
    	{
    		System.loadL ibrary("wgr");
    		new HelloWorld().show();
    	}
    }
    

    1.System.loadL ibrary("wgr");載入動態庫

    2.private native void show();聲明一個動態庫對應的本地方法調用libwgr.so中的函數
    *void Java_ com_ fs_ test HelloWorld_ show(JNIEnv env, jobject thiz)

    3.直接調用show函數(動態庫中與show對應的函數)

一、NDK/JNI

NDK

NDK(Native Development Kit)-原生開發工具包,使得能夠在Android上
去使用C/C++代碼;

JNI

JNI即Java Native Interface,Java和Native介面,就是Java和C/C++之間通訊的橋梁; 為什麼要有JNI,因為Java和C/C++之間是無法直接相互調用的,也就是無法直接通訊,就和Java和JS之間也不能直接相互調用,中間需要翻譯者來處理,JNI就是為了實現Java和C/C++之間這兩種不同語言之間的通訊的;
JNI並不是Android中的技術,它是Java本身就具有的功能,是由JVM支持JNI功能

-那麼為什麼需要在Android上去使用C/C++代碼?

因為我們知道開發Android我們是使用Java/Kotlin語言進行開發的,那麼不論是Java還是Kotlin,最終都是編譯成位元組碼文件.dex文件,然而位元組碼文件是Java/Kotlin經過一定語義編譯之後的產物,並不是完全可以運行的機器碼指令;所以運行應用時還是需要進一步的編譯成機器碼才能夠執行;
而C/C++是完全的編譯型語言,編譯之後是機器碼文件,運行應用時,可以直接執行的,所以效率肯定是要比Java/Kotlin要高的;所以一些對一些比較耗性能的操作,如音視頻編解碼,圖像處理等操作,最好還是交由C/C++代碼中去執行,這樣能夠大大提高應用的執行效率;

NDK開發

新建一個Native項目

Android Studio 新建一個項目,新建項目時,項目模板選擇Native C++
新建項目成功後,我們可以看一下項目的目錄結構;

在src/main目錄下 有一個cpp目錄,與java目錄同級,cpp目錄下有一個 CMakeLists.txt文件和native-lib.cpp
CMkeLists.txt就是CMake腳本文件
native-lib.cpp 是C++源文件

app Module build.gradle中會有一些NDk相關的配置:

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.example.nativec'
    compileSdk 33

    defaultConfig {
        applicationId "com.example.nativec"
        minSdk 24
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.22.1'
        }
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

設置調試模式

在Run/Edit Configuration/Debug Configurations中 Debugger 調試有四個選項
Auto、 自動檢測
Java、 只調試Java代碼
Native、 只調試Native代碼
Dual、 Java+Native兩種都用
註意不要選擇Java和Native,選擇Auto或者Dual

編寫編譯腳本文件CMakeLists.txt

CMake是一個跨平臺的C++編譯工具,所謂編譯就是把C++代碼編譯成靜態庫或者動態庫
CMakeLists.txt就是CMake的編譯腳本文件,
CMake編譯C++是通過CMakeLists.txt編譯腳本來進行編譯的;
CMakeLists.txt常用編譯腳本

 (1)include_directories(dir_path) 

   //設置頭文件的查找目錄,可以使用多次include_directories設置多個頭文件

                         //查找目錄

   舉例: include_directories(${CMAKE_CURRENT_SOURCE_DIR}/giflib)
   
   (2)add_library(編譯生成的庫名,

                  編譯生成庫的類型(STATIC/SHARED),

                  編譯的庫所使用的C/C++源文件(.c/.cpp文件)(可能會有很多) 
                  .
                  .
                  .

                  )  //指定編譯生成庫

      舉例:add_library(

             native-lib

             SHARED

             native-lib.cpp)   
      
            上面這個add_library就指定了編譯生成的庫的名字為native-lib,庫的類型為

            動態庫,庫所使用或者依賴的所以C/C++源文件只有一個native-lib.cpp

            這裡編譯native-lib庫只使用了一個C/C++源文件native-lib.cpp,但是大多情況下,
            
            編譯庫,可能會使用依賴到很多C/C++源文件;               
            
            如:

            add_library(

             native-lib

             SHARED

             native-lib.cpp,

             a.cpp,

             b.cpp
             ....

             ) 

            這樣把所有使用到的C/C++源文件一個個寫進來是可以的,但是當非常多的時候,就很容易寫漏,

            而且也很麻煩; 可以使用file() 這個腳本,來設置C/C++的查找路徑的別名,最終使用這個查找路徑

            別名,就可以替代寫這個路徑下的所有C/C++源文件了
            

    (3) file(GLOB 別名 某路徑下某種類型文件) 查找某路徑下所有某種類型文件
        
        舉例:

        #設置查找GifLib庫實現源文件(.c)文件目錄
        file(GLOB giflib_C_DIR ${CMAKE_CURRENT_SOURCE_DIR}/giflib/*.c)

        #設置自己編寫的C++實現源文件(.CPP)查找目錄
        file(GLOB CSUTOM_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)

        然後上面說到的add_library()設置編譯生成庫依賴很多C/C++源文件的是偶,就可以使用

        file()設置C/C++源文件查找別名

         add_library(

             native-lib

             SHARED

             ${CSUTOM_CPP_DIR}
             
             ${giflib_C_DIR}

             ) 

  (4) target_link_libraries(生成庫的名字,

      生成庫所鏈接的或者說是使用到的其他動態庫或者靜態庫

      ) //設置生成庫使用到的所有其他動態庫或者靜態庫

      舉例:
      
      target_link_libraries(
        
        native-lib

        a

        b
        ....
        
        )       
      
      生成庫使用到了哪些其他動態庫或者靜態庫都要寫進來,如果很多的情況下一個個寫就很麻煩了

      和add_library()寫使用到的所有C/C++源文件一樣,也可以藉助file()來設置使用到的動態庫或者

      靜態庫文件查找路徑別名;

      #查找所有so庫
      file(GLOB SO_DIR ../../../libs/${CMAKE_ANDROID_ARCH_ABI}/*.so)

      target_link_libraries( # Specifies the target library.

                       native-lib

                       ${SO_DIR}
                       
                        )

 (5) set(變數名  變數值) //用於定義一個變數指代後面的變數值

     比如但我們需要使用一個目錄路徑的時候,這個目錄路徑很長,並且在CMakeLists.txt中

     多個地方使用到,我們可以通過set()來定義一個變數指代這個目錄路徑,後面在使用到這個

     路徑的時候,使用這個簡單的變數來替代;
     
     set(LINK_DIR ../../../../libs/${CMAKE_ANDROID_ARCH_ABI})

     file(GLOB SO_DIR ${LINK_DIR}/*.so)


  (6)find_library(庫的別名 庫名) //從系統查找依賴庫

      find_library( 
        log-lib
        log)

      find_library(
        jnigraphics-lib
        jnigraphics
      ) 

       target_link_libraries( # Specifies the target library.

                       native-lib

                       ${SO_DIR}

                       ${log-lib}
                       
                       ${jnigraphics}
                       
                        )

     log和jnigraphics都是NDK中自帶的動態鏈接庫

新建Native項目後,CMakeLists.txt預設已經有了一些一些基本配置,可以讓我們正常編譯使用native-lib.cpp了;

# 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.22.1)

# Declares and names the project.

project("nativec")

# 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.
        nativec

        # 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.
        nativec

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

載入C/C++庫

Android 系統 API提供了兩種載入C/C++庫的方式:
一種是直接載入APK中的C/C++庫文件;
第二種是通過文件地址來載入C/C++庫文件;

(1)載入APK中的C/C++庫文件

庫文件可能是動態庫so庫,或者是靜態庫.a,或者是C/C++源文件
載入方式:

 static {
        System.loadLibrary("native-lib"); //註意不要寫庫文件擴展名
   }

(2)載入外部的C/C++ so庫

如SD卡下的C/C++庫 ,這裡要註意的是,如果是載入外部的庫文件,只能載入動態庫so庫,並且這個庫文件的位置只能在/system/lib目錄下,或者是在應用的安裝包目錄下/data/data/packagename/,
而且這兩個路勁又是有許可權保護的不能直接訪問,所以一般我們是將庫文件複製到我們應用自己的應用目錄下/data/data/packagename/com.xxx.xxx

  System.load(" C/C++庫文件的絕對路徑(註意不需要寫庫文件擴展名)");

實現Java和C++互相調用

因為我們新建的項目模板是Native C++,所以項目中已經自動為我們創建了一個native-lib.cpp,C++源代碼文件,MainActivtiy中也聲明瞭一個native方法與native-lib.cpp中對應的native方法對應,並且在MainActivtiy中調用了native方法;
來演示Java中調用C/C++方法

下麵我們來分析一下native方法:

Native方法中的包名要和Java方法中的包名對應上,Native層中JNI方法命名格式

為Java_包名類名方法名 之間全部是下劃線分隔 :

Java_com_nado_jniproject_MainActivity_stringFromJNI

參數:JNIEnv *env和jobject thiz

這個jobject類型的thiz就是對應外層的Java對象 jobject就是Java中的Object

Java類型 C類型 以及 JNI別名 對應如下:

Java 類型	JNI 別名  	    C 類型
boolean	    jboolean	    unsigned char
byte	    jbyte	        signed char
char	    jchar	        unsigned short
short	    jshort	        short
int		    jint             int
long	    jlong	        long
float	    jfloat	        float
double	    jdouble	        double
String	    jstring	        char*
Class	    jclass	        /
Object	    jobject	        /

由於Java和C語言之間是無法直接調用的,兩種語言的基本類型是不一樣的,例如Java中有boolean類型,而在C中就沒有這種類型,但是C語言還是有if-else判斷的,那怎麼判斷true或者false? C中使用char類型,當char的值是0就是false非0就是true;基於這種情況,JNI重新定義了一些類型,以便C和Java對應上;

運行應用,我們可以在MainActivity中成功的顯示從調用C/C++方法獲取到的字元串

上面我們學習了Java中調用C/C++方法,為了清楚的瞭解Java和C之間的互相調用,還需要學習一下Native中調用Java方法

生成so文件

build一下,在這裡找到生成的so文件

參考

https://blog.csdn.net/mq2856992713/article/details/118090046


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 記錄一次EF實體跟蹤錯誤 # 前言 在我寫文章編輯介面的,出現了一個實體跟蹤的錯誤,詳情如下 > System.InvalidOperationException: The instance of entity type 'Tag' cannot be tracked because anoth ...
  • # UGUI的Image(圖片)組件的介紹及使用 ## 1. 什麼是UGUI的Image(圖片)組件? UGUI的Image(圖片)組件是Unity引擎中的一種UI組件,用於顯示2D圖像。它提供了一種簡單而靈活的方式來在游戲中載入和顯示圖片。 ## 2. 為什麼要使用UGUI的Image(圖片)組件 ...
  • # 記錄http請求 ## 環境 * .net7 ## 一、過濾器(Filter) 這個過程用的的是操作過濾器(`ActionFilter`) ## 二、 ### 2.1 繼承`IAsyncActionFilter` ### 2.2 重寫`OnActionExecutionAsync` `OnAct ...
  • [toc] # Linux運維工程師面試題(2) > 祝各位小伙伴們早日找到自己心儀的工作。 > 持續學習才不會被淘汰。 > 地球不爆炸,我們不放假。 > 機會總是留給有有準備的人的。 > 加油,打工人! ## 1 訪問一個網站的流程 1. 打開瀏覽器,輸入網址。首先查找本地緩存,如果有就打開頁面, ...
  • 操作系統是電腦不可或缺的一部分,它連接著硬體和應用程式。內核是操作系統的核心,負責管理進程和線程、記憶體、硬體設備以及提供系統調用介面。電腦啟動過程中,ROM負責載入並執行BIOS程式,而RAM用於存儲運行中的程式和數據。系統調用是操作系統提供給應用程式的介面,通過系統調用可以訪問操作系統的功能。... ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230822120346228-1599813347.png) # 1. 不需要考慮排除任何列 ## 1.1. 清除數據表中所有的內容 ## 1.2. 暫存新數據倉庫的數據 # ...
  • [數據治理](https://www.dtstack.com/?src=szsm)是推動大型集團企業轉型升級、提升競爭優勢、實現高質量發展的重要引擎。 通過搭建[大數據平臺](https://www.dtstack.com/?src=szsm),實現對業務系統數據的採集、清理、建模、整合,建立一個符 ...
  • HP DP(Data Protector Manager)上一個剛剛遷移升級的資料庫備份作業失敗,具體失敗信息如下 .................................RMAN-08503: piece handle=c-1684727642-20230822-00 comment=A ...
一周排行
    -Advertisement-
    Play Games
  • WPF本身不支持直接的3D繪圖,但是它提供了一些用於實現3D效果的高級技術。 如果你想要在WPF中進行3D繪圖,你可以使用兩種主要的方法: WPF 3D:這是一種在WPF應用程式中創建3D圖形的方式。WPF 3D提供了一些基本的3D形狀(如立方體、球體和錐體)以及一些用於控制3D場景和對象的工具(如 ...
  • 一、XML概述 XML(可擴展標記語言)是一種用於描述數據的標記語言,旨在提供一種通用的方式來傳輸和存儲數據,特別是Web應用程式中經常使用的數據。XML並不預定義標記。因此,XML更加靈活,並且可以適用於廣泛的應用領域。 XML文檔由元素(element)、屬性(attribute)和內容(con ...
  • 從今年(2023)三月份開始,Github開始強制用戶開啟兩步驗證2FA(雙因數)登錄驗證,毫無疑問,是出於安全層面的考慮,畢竟Github賬號一旦被盜,所有代碼倉庫都會毀於一旦,關於雙因數登錄的必要性請參見:別讓你的伺服器(vps)淪為肉雞(ssh暴力破解),密鑰驗證、雙向因數登錄值得擁有。 雙因 ...
  • 第一題 下列代碼輸入什麼? public class Test { public static Test t1 = new Test(); { System.out.println("blockA"); } static { System.out.println("blockB"); } publi ...
  • 本文主要涉及的問題:用ElementTree和XPath讀寫XML文件;解決ElementTree新增元素後再寫入格式不統一的問題;QTableWidget單元格設置控制項 ...
  • QStandardItemModel 類作為標準模型,主打“類型通用”,前一篇水文中,老周還沒提到樹形結構的列表,本篇咱們就好好探討一下這貨。 還是老辦法,咱們先做示例,然後再聊知識點。下麵這個例子,使用 QTreeView 組件來顯示數據,使用的列表模型比較簡單,只有一列。 #include <Q ...
  • 一、直充內充(充值方式) 直充: 包裝套餐直接充值到上游API系統。【PID/Smart】 (如:支付寶、微信 話費/流量/語音/簡訊 等 充值系統)。 內充(套餐打包常見物聯卡系統功能): 套餐包裝 適用於不同類型套餐 如 流量、簡訊、語音 等。 (目前已完善流量邏輯) 二、套餐與計費產品 計費產 ...
  • 在前面幾天中,我們學習了Dart基礎語法、可迭代集合,它們是Flutter應用研發的基本功。今天,我們繼續學習Flutter應用另一個必須掌握知識點:非同步編程(即Future和async/await)。它類似於Java中的FutureTask、JavaScript中的Promise。它是後續Flut... ...
  • 針對改動範圍大、影響面廣的需求,我通常會問上線了最壞情況是什麼?應急預案是什麼?你帶開關了嗎?。當然開關也是有成本的,接下來本篇跟大家一起交流下高頻發佈支撐下的功能開關技術理論與實踐結合的點點滴滴。 ...
  • 1.d3.shuffle D3.shuffle() 方法用於將數組中的元素隨機排序。它使用 Fisher–Yates 洗牌演算法,該演算法是無偏的,具有最佳的漸近性能(線性時間和常數記憶體)。 D3.shuffle() 方法的語法如下: d3.shuffle(array, [start, end]) 其中 ...