Android: 在native中訪問assets全解析

来源:https://www.cnblogs.com/willhua/archive/2018/09/23/9692529.html
-Advertisement-
Play Games

本文總結在Android Native C++開發中訪問APK中的 assets 資源的方法 在CMake中添加相關NDK LIB的 依賴 因為我們接下來用到的一些函數實現在NDK庫libandroid.so中,因此我們直接在 CMakeList.txt 中添加對其依賴即可: 如果沒有添加此依賴,顯 ...


本文總結在Android Native C++開發中訪問APK中的assets資源的方法

在CMake中添加相關NDK LIB的 依賴

因為我們接下來用到的一些函數實現在NDK庫libandroid.so中,因此我們直接在CMakeList.txt中添加對其依賴即可:

target_link_libraries( # Specifies the target library.
                       native-lib
                       #lib to link
                       android
                       # other libs
                       )

如果沒有添加此依賴,顯然會提示undefined reference錯誤,比如:

error: undefined reference to 'AAssetManager_fromJava'
error: undefined reference to 'AAssetManager_open'
error: undefined reference to 'AAsset_getLength'
error: undefined reference to 'AAsset_getBuffer'
error: undefined reference to 'AAsset_close'
error: undefined reference to 'AAssetManager_open'
error: undefined reference to 'AAsset_getLength'
error: undefined reference to 'AAsset_openFileDescriptor'
error: undefined reference to 'AAsset_close'
error: undefined reference to 'AAssetManager_openDir'
error: undefined reference to 'AAssetDir_getNextFileName'
error: undefined reference to 'AAssetManager_open'

獲得AssetManager

在Java中,我們可以通過Context.getAssets()輕鬆獲得AssetManager。在NDK中,提供了AAssetManager_fromJava來獲得Native中對應的AAssetManager。顧名思義,fromJava,肯定是要從Java層獲取了,也即意味著要通過JNI來獲得。代碼如下:

/***code in Java, such as MainActivity.java***/

//decale the jni func
public native void setNativeAssetManager(AssetManager assetManager);

//call it, such as during Activity.onCreate()
setNativeAssetManager(getAssets());

/***end of java***/


/***code in native c++***/
extern "C"
JNIEXPORT void JNICALL
Java_willhua_androidstudioopencl_MainActivity_setNativeAssetManager(
    JNIEnv *env, 
    jobject instance,
    jobject assetManager) {
            AAssetManager *nativeasset = AAssetManager_fromJava(env, assetManager);
            
            //the use of nativeasset
}

下麵所有的代碼都是在Java_willhua_androidstudioopencl_MainActivity_setNativeAssetManager內實現。

訪問assets下的文件

我們知道,assets文件夾下麵是可以有子文件夾的,因為,下麵我以讀取圖片為例,介紹各種情況的訪問方法。例子中用到OpenCV的相關方法,在此不介紹,自行瞭解。
測試用assets文件夾目錄:
iutTBt.jpg

已知完整路徑的訪問

如果我們已經知道assets下某個文件的完整路徑,比如"sz.jpg","dir/cs.jpg",那麼我們可以直接把這個路徑傳給AAssetManager_open來獲得AAsset.

//open file
AAsset *assetFile = AAssetManager_open(nativeasset, "sz.jpg", AASSET_MODE_BUFFER);
//this will also be ok 
//AAsset *assetFile = AAssetManager_open(nativeasset, "dir/cs.jpg", AASSET_MODE_BUFFER);
//get file length
size_t fileLength = AAsset_getLength(assetFile);
char *dataBuffer2 = (char *) malloc(fileLength);
//read file data
AAsset_read(assetFile, dataBuffer2, fileLength);
//the data has been copied to dataBuffer2, so , close it
AAsset_close(assetFile);

//decode the file data to cv::Mat 
std::vector<char> vec2(dataBuffer2, dataBuffer2 + fileLength);
cv::Mat mat2 = cv::imdecode(vec2, CV_LOAD_IMAGE_COLOR);
LOGD("asset file %d x %d  %d", mat2.cols, mat2.rows, mat2.channels());

//free malloc
free(dataBuffer2);

獲取文件下的名字並訪問之

如果我們只知道文件夾的名字,但並不知道文件夾下麵有哪些具體文件,比如我們只知道有個dir文件夾,但不知道下麵的具體情況。那麼我們可以使用AAssetDir_getNextFileName來獲取文件夾的文件名。但是有個問題,這個方法只能獲得文件夾下的文件名,而無法獲得子文件夾,有哪位知道的請告知。
註意AAssetDir_getNextFileName只返迴文件名,而不是該文件的完整路徑,比如只會返回cs.jpg,而不是dir/cs.jpg,所以如果直接把AAssetDir_getNextFileName的返回結果傳給AAssetManager_open會讀取不到正確的文件,返回NULL.

AAssetDir *assetDir = AAssetManager_openDir(nativeasset, "dir");
const char *filename = AAssetDir_getNextFileName(assetDir);
while (filename != NULL){
    char fullname[1024];
    sprintf(fullname, "dir/%s", filename); //get the full path
    AAsset *file = AAssetManager_open(nativeasset, fullname, AASSET_MODE_BUFFER);
    if(file == NULL){
        LOGD("FILE NULL  %s", filename);
        break;
    }
    size_t fileLength = AAsset_getLength(file);
    LOGD("filename next:%s,  size:%d", filename, fileLength);
    char *buffer = (char*)malloc(fileLength);
    AAsset_read(file, buffer, fileLength);
    AAsset_close(file);

    //do something with the buffer


    free(buffer);

    filename = AAssetDir_getNextFileName(assetDir);
}
AAssetDir_close(assetDir);  //remember to close it

使用AAsset_getBuffer讀整個文件內容

在上面的case中,我們拿到AAsset之後都是malloc記憶體,然後把文件信息讀出來的形式,其實這種方式適合不一次性讀取整個文件內容的情況,按照官網的說法就是:

Attempt to read 'count' bytes of data from the current offset.

也就是AAsset_read應該配合AAsset_seek使用更美味。
對於一次性讀取整個文件的內容,更好的方式是使用AAsset_getBuffer

AAsset *assetFile = AAssetManager_open(nativeasset, "sz.jpg", AASSET_MODE_BUFFER);
size_t fileLength = AAsset_getLength(assetFile);
const char *dataBuffer2 =(const char *) AAsset_getBuffer(assetFile);

std::vector<char> vec2(dataBuffer2, dataBuffer2 + fileLength);
cv::Mat mat2 = cv::imdecode(vec2, CV_LOAD_IMAGE_COLOR);
LOGD("asset file lenght:%d   mat: %d x %d  %d", fileLength,  mat2.cols, mat2.rows, mat2.channels());

AAsset_close(assetFile);

以FileDescriptor的方式來讀取

我們可以使用AAsset_openFileDescriptor來獲取FileDescriptor,然後再進行其他操作。需要註意的是,AAsset_openFileDescriptor返回當前fd的起始seek位置start以及文件長度length。在讀取內容之前記得要先seek到start,否則會發現文件內容不對。見代碼中的lseek.

AAsset *assetFile = AAssetManager_open(nativeasset, "sz.jpg", AASSET_MODE_BUFFER);
size_t fileLength = AAsset_getLength(assetFile);
LOGD("before fd fileLength:%d",fileLength);

off_t start = 0, length = 0;
int fd = AAsset_openFileDescriptor(assetFile, &start, &length);
LOGD("fd:%d  start:%d  length:%d", fd, start, length);
lseek(fd, start, SEEK_CUR); //NOTICE 

char *dataBuffer = (char*)malloc(fileLength);
memset(dataBuffer, 0, fileLength);
read(fd, dataBuffer, fileLength);
close(fd);  //close fd
LOGD("read_  %d %d %d",  dataBuffer[0], dataBuffer[1], dataBuffer[2]);

std::vector<char> vec2(dataBuffer, dataBuffer + fileLength);
cv::Mat mat2 = cv::imdecode(vec2, CV_LOAD_IMAGE_COLOR);
LOGD("use fd  mat:%d x %d  %d", mat2.cols, mat2.rows, mat2.channels());
AAsset_close(assetFile);

獲得fd之後,也可以通過他來獲得一個FILE:FILE * file = fdopen(fd, "rb");但是一定要記得fclose(file)。總的來說不如read方便。

open mode

AAssetManager_open需要傳入一個mode參數,各參數的含義如下,按需使用。

AASSET_MODE_UNKNOWN: Not known how the data is to be accessed
AASSET_MODE_RANDOM: Read chunks, and seek forward and backward
AASSET_MODE_STREAMING: Read sequentially, with an occasional
forward seek
AASSET_MODE_BUFFER: Attempt to load contents into memory, for fast
small reads

細節提示

  • AAsset是只讀的,比如上面獲得FILE之後,不能用來寫。

    AAsset provides access to a read-only asset.

  • 記得AAsset_close
  • 記得AAssetDir_close

關於壓縮文件

Android APK中有些文件是會進行壓縮的,而有些文件則因為本身就是已經壓縮過的,不再進行壓縮,具體有:

/* these formats are already compressed, or don't compress well */
static const char* kNoCompressExt[] = {
    ".jpg", ".jpeg", ".png", ".gif",
    ".wav", ".mp2", ".mp3", ".ogg", ".aac",
    ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
    ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
    ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
    ".amr", ".awb", ".wma", ".wmv"
};

那麼對於在APK中會被壓縮的文件,比如txt文件,就不能使用AAsset_openFileDescriptor來讀了,否則,會返回-1這樣的無效fd。對於會被壓縮的文件,那麼就只能使用AAsset_read或者AAsset_getBuffer來訪問了。

參考

Developers NDK
不壓縮文件尾碼
SOF


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

-Advertisement-
Play Games
更多相關文章
  • 備份的目的 能夠防止由於機械故障以及人為誤操作帶來的數據丟失,例如將資料庫文件保存在了其它地方。 備份的分類 以操作過程中服務的可用性分: 冷備份:cold backup mysql服務關閉,mysql離線 溫備份:warm backup mysql服務線上,但是不允許寫請求,例如 read loc ...
  • 連接時MySQL出現了下麵的錯誤: 解決的辦法是先停止MySQL服務,在my.ini文件的最下麵加入一行: 再啟動MySQL服務,就可以了。 ...
  • 編寫時間:15:07:02(2018年9月23日) 1.停止mysql服務。 “運行”——>“cmd”——>輸入“net stop mysql;” 看鏈接: https://blog.csdn.net/Ludwig_/article/details/52634587 2.將控制面板中的mysql正常 ...
  • 參考博文:Kafka消費組(consumer group) 參考博文:kafka 1.0 中文文檔(九):操作 參考博文:kafka集群管理工具kafka-manager部署安裝 以下操作可以在mini01、mini02、mini03任意一臺操作即可。 1. kafka通過網頁管理 參考博文:kaf ...
  • 1. 核心概念 StreamingContext Create StreamingContext StreamingContext的構造函數 batch interval 可以根據你的應用程式需求的延遲要求以及集群可用的資源狀況來設置 創建StreamingContext可以做什麼? 1. Defi ...
  • 參考博文:kafka 配置文件參數詳解 參考博文:Kafka【第一篇】Kafka集群搭建 參考博文:如何為Kafka集群選擇合適的Partitions數量 參考博文:Kafka Server.properties 參考博文:kafka常用配置【重要】 參考博文:kafka常用配置 1. 主機規劃 主 ...
  • 1. JMS是什麼 1.1. JMS的基礎 JMS是什麼:JMS是Java提供的一套技術規範 JMS乾什麼用:用來異構系統 集成通信,緩解系統瓶頸,提高系統的伸縮性增強系統用戶體驗,使得系統模塊化和組件化變得可行並更加靈活 通過什麼方式:生產消費者模式(生產者、伺服器、消費者) 1.2. JMS消息 ...
  • Kotlin is a good language,more playable than Java.So how do we deploy kotlin? First,install the kotlin plug-in in Plugins. If you are the latest Andro ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...