一饺鹃、 需求驅(qū)動(dòng)功能開發(fā),項(xiàng)目的需求是文件先經(jīng)過(guò)服務(wù)端加密,客戶端下載完成后再進(jìn)行解密后使用,最終確認(rèn)的方案是對(duì)文件分段加密,加密后再拼接成新的文件报嵌,最后在實(shí)現(xiàn)的時(shí)候遇到過(guò)一些坑<內(nèi)存問(wèn)題>凶伙。該篇只是介紹我們項(xiàng)目中使用的文件加密方案,并不代表移動(dòng)端開發(fā)主流文件加密的方案
</br>
二、實(shí)現(xiàn):對(duì)大文件進(jìn)行分段切割
首先想到的就是OC自帶的文件處理類NSFileHandle修档,不過(guò)在使用的時(shí)候遇到了一個(gè)運(yùn)存無(wú)法消除的問(wèn)題,雖然可以用
NSFilehandle
的readDataOfLength
和seekToFileOffset
的兩個(gè)方法達(dá)到分段讀取文件的功能,但是每次調(diào)用的時(shí)候程序的內(nèi)存是疊加的,文件多大需要消耗的內(nèi)存就有多大,當(dāng)需要的內(nèi)存大于程序的上限的時(shí)候赌髓,就會(huì)造成程序的閃退。
OC的方法雖然能實(shí)現(xiàn)功能,但是性能有不滿足要求冷冗,因?yàn)镃語(yǔ)言自帶標(biāo)準(zhǔn)I/O庫(kù),就試著用C語(yǔ)言寫了個(gè)文件分段讀取的方法,并實(shí)現(xiàn)了內(nèi)存的即時(shí)的釋放,保證了App的性能需求守屉。調(diào)用的方法解釋可以參考:C語(yǔ)言文件操作函數(shù)|c語(yǔ)言文件讀寫函數(shù),fopen方法參數(shù)說(shuō)明參見百科,具體方法實(shí)現(xiàn)如下:
/**
* 解密視文件,主要思路是先找到待解密的文件,將解密的文件存放與臨時(shí)文件下,
* 解密完成將加密的文件刪除再將解密完成的文件替換成原文件的名字
*
* @param mediaPath 文件路徑
* @param index 文件路徑下標(biāo)
*
* @return 是否解密成功
*/
- (BOOL)addDecryptFileWithMediaPath:(NSString *)mediaPath index:(NSInteger)index header:(NSInteger)header key:(NSInteger)key{
NSFileManager *manager = [NSFileManager defaultManager];
NSString *subPath = mediaPath;
// 文件類型
NSString *pathExtension = [subPath pathExtension];
// 文件路徑的前路徑
NSString *prePath = [subPath stringByDeletingLastPathComponent];
// 當(dāng)前文件名 主要采用的是創(chuàng)建一個(gè)
NSString *tempfilePath = [NSString stringWithFormat:@"%@/ios_temp_%ld.%@",prePath,index,pathExtension];
[manager removeItemAtPath:tempfilePath error:nil];
[manager createFileAtPath:tempfilePath contents:nil attributes:nil];
// 創(chuàng)建讀寫FILE
FILE *readFile;
FILE *writeFile;
//打開發(fā)文件
readFile = fopen([subPath cStringUsingEncoding:NSUTF8StringEncoding], "rb+");
writeFile = fopen([tempfilePath cStringUsingEncoding:NSUTF8StringEncoding], "a+");
//獲取文件屬性信息
NSDictionary *attributes = [manager attributesOfItemAtPath:subPath error:nil];
//獲取文件字節(jié)總長(zhǎng)度
self.fileLength = ((NSNumber *)[attributes objectForKey:@"NSFileSize"]).integerValue;
int decodeKey = (int)key;
//根據(jù)我們的加密頭和加密參數(shù)判斷是否需要解密
if (self.fileLength > header) {
//如果符合解密條件將當(dāng)前初始解密文件長(zhǎng)度設(shè)置成頭的長(zhǎng)度<header是加密的時(shí)候添加上的在解密的時(shí)候需要
先將header的長(zhǎng)度去除再進(jìn)行解密>
self.currnentLength = header;
//調(diào)用解密方法
BOOL isSuc = decodeFile(readFile, self.fileLength, self.currnentLength, writeFile,decodeKey);
if (isSuc) {
if([manager removeItemAtPath:mediaPath error:nil]){
isSuc = [manager moveItemAtPath:tempfilePath toPath:mediaPath error:nil];
}else{
isSuc = NO;
}
}
return isSuc;
}else{
return NO;
}
}
=============================我是分割線============================
/**
* C函數(shù)解密文件的方法
*
* @param file 待解密的文件File
* @param fileLength 文件長(zhǎng)度
* @param currentLegth 當(dāng)前解密的文件長(zhǎng)度
* @param direcFile 目標(biāo)存儲(chǔ)目錄
*/
BOOL decodeFile(FILE *file,long fileLength,long currentLegth,FILE *direcFile,int key){
BOOL isSucessed;
//分段最大讀取長(zhǎng)度 1MB
NSInteger readLength = 1024 * 1024 * 1;
// 如果文件長(zhǎng)度減去當(dāng)前解密到的文件長(zhǎng)度還大于分段最大讀取長(zhǎng)度則采用遞歸調(diào)用解密
if (fileLength - currentLegth > readLength) {
// 指針指向首字節(jié)
//讀取文件
// 成功蒿辙,返回0拇泛,失敗返回-1
int set = fseek(file, currentLegth, SEEK_SET);
if (set == 0) {
//設(shè)置buffer保存分段讀取文件的Data值
Byte *buffer = malloc(sizeof(Byte) * readLength);
fread(buffer, readLength, sizeof(Byte), file);
//讀取成功后設(shè)置當(dāng)前解密長(zhǎng)度
currentLegth += readLength;
//設(shè)置讀取位置
// 解密方法 可以在此基礎(chǔ)上采用其他的加解密<RSA/AES>
for (int i = 0 ; i < readLength; i++) {
*(buffer + i ) -= key;
}
// 將解密后的文件流寫入解密目標(biāo)文件
size_t writedData = fwrite(buffer, sizeof(Byte), readLength, direcFile);
if (writedData == readLength) {
isSucessed = YES;
}else{
isSucessed = NO;
}
// 釋放內(nèi)存
free(buffer);
if (isSucessed) {
// 遞歸調(diào)用
return decodeFile(file, fileLength, currentLegth,direcFile,key);
}else{
return isSucessed;
}}}else{//如果文件長(zhǎng)度減去當(dāng)前長(zhǎng)度小于最小分段讀取長(zhǎng)度 則跳出遞歸完成解密
// 大于0的話繼續(xù)解密
if(fileLength - currentLegth > 0){
long length = fileLength - currentLegth;
int set = fseek(file, currentLegth, SEEK_SET);
if (set == 0) {
Byte *buffer = malloc(sizeof(Byte) * length);
fread(buffer, length, sizeof(Byte), file);
currentLegth += length;
//設(shè)置讀取位置
for (int i = 0 ; i < length; i++) {
*(buffer + i ) -= key;
}
size_t writedData = fwrite(buffer, sizeof(Byte), length, direcFile);
printf("最終-fseek--%d",set);
if (writedData == length) {
isSucessed = YES;
}else{
isSucessed = NO;
}
// 釋放內(nèi)存
free(buffer);
// 關(guān)閉文件
fclose(file);
fclose(direcFile);
}
}
}
return isSucessed;
}