前言
這里再次啰嗦一下痘番,我們?yōu)槭裁匆獙W習NDK開發(fā)呢捉片?因為很多大公司平痰,為了節(jié)省開發(fā)資源,很多時候核心技術(shù)都是用C/C++去實現(xiàn)的伍纫,一套代碼宗雇,可以給Android、IOS莹规、后端調(diào)用赔蒲,這也是一種跨平臺的實現(xiàn)方案,大大節(jié)省了人力成本良漱,但是對開發(fā)者的要求就提高了舞虱。這也是為什么像QQ、淘寶等眾多大型APP都是采用了很多動態(tài)庫文件的原因母市。所以說很多時候矾兜,大公司的筆試考的都是C/C++。
如果你也有志氣想進大公司患久,那就學習吧椅寺,勇往直前!
文件案例與拆分
先說說為什么要做文件拆分和合并蒋失。舉一些例子返帕,我們做多媒體開發(fā)的時候,尤其是大視頻上傳篙挽,我們就需要把視頻拆分成多個分別上傳闺骚,減輕服務端壓力的同時挂滓,也防止了上傳失敗導致全盤重新上傳毒嫡。而且這次的文章是為了下一次的增量更新做鋪墊谎仲。
上一篇博客介紹了Android Studio2.3的NDK開發(fā)流程,下面我們通過一個實際案例來進一步加強算行、熟悉這個開發(fā)流程梧油。
首先我們創(chuàng)建項目,配置我們的CMake構(gòu)建腳本:
cmake_minimum_required(VERSION 3.4.1)
add_library(
testJni
SHARED
src/main/jni/testJni.c )
find_library( log-lib
log )
target_link_libraries(
testJni
${log-lib} )
由于我們在NDK中不能夠使用printf輸出logcat州邢,所以將會使用NDK自帶的log-lib來進行日志打印儡陨。
其中:
- find_library命令是找到log-lib這個庫。
- target_link_libraries是把我們自己的庫跟log-lib關(guān)聯(lián)起來量淌。相當于我們的庫依賴log-lib庫骗村。
如果你使用ndk-build的方式來構(gòu)建項目的話,那么請在Android.mk文件中添加LOCAL_LDLIBS配置呀枢,完整的配置如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := testJni
LOCAL_SRC_FILES := testJni.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
其它的就不贅述了胚股,參考上一篇文章,下面我們創(chuàng)建一個類裙秋,專門用于文件的拆分與合并:
package com.nan.testjni;
public class FileUtil {
//文件拆分
public static native void diff(String path, String pattern, int count);
//文件合并
public static native void patch(String path, String pattern, int count);
}
下面我們在Application里面加載我們的動態(tài)庫琅拌,因為Application是全局的缨伊,一般來說Application只會初始化一次(單進程的情況下)。
public class App extends Application {
static {
//加載動態(tài)庫.so文件进宝,注意不用寫lib前綴刻坊,系統(tǒng)會默認添加
System.loadLibrary("testJni");
}
@Override
public void onCreate() {
super.onCreate();
}
}
記得在清單文件中配置一下:
<application
android:name=".App"
</application>
文件拆分
先舉個例子,比如說我們的文件大小是110個單位(字節(jié))党晋,需要被分成9個小文件:
- 如果110能夠被9整除的話谭胚,那么每個文件大小是:110/9
- 很顯然,這里不能夠整除未玻,那么我們前面8(9-1)個文件需要大一些灾而,大小為:110/(9-1)。最后一個文件的大小稍微小一些深胳,大小為:110%(9-1)绰疤。
示例代碼如下:
JNIEXPORT void JNICALL
Java_com_nan_testjni_FileUtil_diff(JNIEnv *env, jclass type, jstring path_, jstring pattern_,
jint count) {
//需要分割的文件路徑
const char *path = (*env)->GetStringUTFChars(env, path_, NULL);
//分割之后的小文件命名法則
const char *pattern = (*env)->GetStringUTFChars(env, pattern_, NULL);
//得到分割之后的小文件的命名列表(字符串數(shù)組)铜犬,分配內(nèi)存
char **patches = malloc(sizeof(char *) * count);
int i = 0;
for (; i < count; i++) {
//為字符數(shù)組的每一個條目分配內(nèi)存
patches[i] = malloc(sizeof(char) * 100);
sprintf(patches[i], pattern, i);
//打印日志
//__android_log_print(ANDROID_LOG_DEBUG, "TAG_NDK", "File Name : %s\n", patches[i]);
LOGE("name:%s\n", patches[i]);
}
FILE *fTotal = fopen(path, "rb");
int fileSize = getFileSize(path);
if (fileSize / count == 0) {
//可以整除
int partSize = fileSize / count;
i = 0;
for (; i < count; i++) {
FILE *fPart = fopen(patches[i], "wb");
int j = 0;
for (; j < partSize; j++) {
fputc(fgetc(fTotal), fPart);
}
fclose(fPart);
}
} else {
//不可以整除
int partSize = fileSize / (count - 1);
i = 0;
for (; i < count - 1; i++) {
FILE *fPart = fopen(patches[i], "wb");
int j = 0;
for (; j < partSize; j++) {
fputc(fgetc(fTotal), fPart);
}
fclose(fPart);
}
FILE *fLast = fopen(patches[count - 1], "wb");
i = 0;
for (; i < fileSize % (count - 1); i++) {
fputc(fgetc(fTotal), fLast);
}
fclose(fLast);
}
//釋放資源
fclose(fTotal);
i = 0;
for (; i < count; i++) {
free(patches[i]);
}
free(patches);
(*env)->ReleaseStringUTFChars(env, path_, path);
(*env)->ReleaseStringUTFChars(env, pattern_, pattern);
}
解釋一下上面的代碼:
- 首先我們需要一個分割之后的字符串數(shù)組(也就是char二級指針)舞终,用于存放分割之后的文件路徑。
- 然后我分整除和不整除兩種情況來分別處理癣猾,不斷讀取被分割的文件敛劝,循環(huán)寫入每個子文件中。
- 釋放資源纷宇,尤其注意的是夸盟,malloc分配的堆內(nèi)存需要及時釋放。
這里我們封裝了一個獲取文件大小的函數(shù)像捶,之前的C語音的文件有介紹上陕,不在贅述:
//獲取文件大小
long get_file_size(char *path){
FILE *fp = fopen(path,"rb");
fseek(fp,0,SEEK_END);
return ftell(fp);
}
NDK日志輸出
在上面配置了我們的CMake構(gòu)建腳本之后(否則找不到打印日志函數(shù)),我們可以使用__android_log_print來輸出日志拓春,參數(shù)分別為日志級別释簿、TAG,剩下的兩個參數(shù)跟printf一樣硼莽。使用的例子如下:
先通過include包含文件:
#include <android/log.h>
然后調(diào)用__android_log_print輸出日志:
__android_log_print(ANDROID_LOG_DEBUG, "TAG_NDK", "File Name : %s\n", patches[i]);
這里為了簡化庶溶,我們通過一些宏定義來簡化操作,在C語音的預編譯的那篇文章有介紹懂鸵,這里不再贅述:
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO, "huannan",FORMAT,__VA_ARGS__)
#define LOGD(FORMAT, ...) __android_log_print(ANDROID_LOG_DEBUG,"huannan",FORMAT,__VA_ARGS__)
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"huannan",FORMAT,__VA_ARGS__)
文件合并
下面來看看文件合并的代碼偏螺,相對來說比較簡單,就是通過一個循環(huán)匆光,不斷讀取每個子文件套像,然后合并到總文件中。代碼如下:
JNIEXPORT void JNICALL
Java_com_nan_testjni_FileUtil_patch(JNIEnv *env, jclass type, jstring path_, jstring pattern_,
jint count) {
//合并之后的文件路徑
const char *path = (*env)->GetStringUTFChars(env, path_, NULL);
//分割之后的小文件命名法則
const char *pattern = (*env)->GetStringUTFChars(env, pattern_, NULL);
//得到被分割的小文件的命名列表(字符串數(shù)組)终息,分配內(nèi)存
char **patches = malloc(sizeof(char *) * count);
int i = 0;
for (; i < count; i++) {
//為字符數(shù)組的每一個條目分配內(nèi)存
patches[i] = malloc(sizeof(char) * 100);
sprintf(patches[i], pattern, i);
//打印日志
//__android_log_print(ANDROID_LOG_DEBUG, "TAG_NDK", "File Name : %s\n", patches[i]);
LOGE("name:%s\n", patches[i]);
}
//合并的文件路徑
FILE *fTotal = fopen(path, "wb");
i = 0;
for (; i < count; i++) {
int patchSize = getFileSize(patches[i]);
FILE *fPart = fopen(patches[i], "rb");
int j = 0;
for (; j < patchSize; j++) {
fputc(fgetc(fPart), fTotal);
}
fclose(fPart);
}
//釋放資源
fclose(fTotal);
i = 0;
for (; i < count; i++) {
free(patches[i]);
}
free(patches);
(*env)->ReleaseStringUTFChars(env, path_, path);
(*env)->ReleaseStringUTFChars(env, pattern_, pattern);
}
如果覺得我的文字對你有所幫助的話夺巩,歡迎關(guān)注我的公眾號:
我的群歡迎大家進來探討各種技術(shù)與非技術(shù)的話題货葬,有興趣的朋友們加我私人微信huannan88,我拉你進群交(♂)流(♀)劲够。