(创建于2017/12/6)
1.工具类PatchUtils
package com.ren.ndk_file_patch;
public class PatchUtils {
static{
System.loadLibrary("ndk_file_patch");
}
public native static void diff(String path,String path_pattern,int count);
public native static void patch(String path_pattern,String merge_path,int count);
}
2.生成的头文件 com_ren_ndk_file_patch_PatchUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ren_ndk_file_patch_PatchUtils */
#ifndef _Included_com_ren_ndk_file_patch_PatchUtils
#define _Included_com_ren_ndk_file_patch_PatchUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_ren_ndk_file_patch_PatchUtils
* Method: diff
* Signature: (Ljava/lang/String;Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_ren_ndk_1file_1patch_PatchUtils_diff
(JNIEnv *, jclass, jstring, jstring, jint);
/*
* Class: com_ren_ndk_file_patch_PatchUtils
* Method: patch
* Signature: (Ljava/lang/String;Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_ren_ndk_1file_1patch_PatchUtils_patch
(JNIEnv *, jclass, jstring, jstring, jint);
#ifdef __cplusplus
}
#endif
#endif
3.编写的c文件 ndk_file_patch.c
#include "com_ren_ndk_file_patch_PatchUtils.h"
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include<android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"renzhenming",FORMAT,__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR),"renzhenming",FORMAT,__VA_ARGS__);
long get_file_size(const char *path){
FILE *fl = fopen(path,"rb");
//把与fp有关的文件位置指针放到一个指定位置。
//文件指针定位到文件末尾,偏移0个字节
fseek(fl,0,SEEK_END);
//函数用来获取文件读写指针的当前位置,对于二进制文件,则返回从文件开头到结尾的字节数。
return ftell(fl);
}
JNIEXPORT void JNICALL Java_com_ren_ndk_1file_1patch_PatchUtils_diff
(JNIEnv *env, jclass jclz, jstring path_jstr, jstring path_pattern_jstr, jint file_num){
//转换文件路径
const char *path = (*env)->GetStringUTFChars(env,path_jstr,NULL);
const char *path_pattern = (*env)->GetStringUTFChars(env,path_pattern_jstr,NULL);
//得到分割之后的所有文件路径
//申请一段连续的内存空间(一个数组)保存所有分割的文件地址
char **patches = malloc(sizeof(char*)*file_num);
int i = 0;
for(;i<file_num;i++){
patches[i]=malloc(sizeof(char)*100);
//元素赋值
//需要分割的文件:C://jason/liuyan.png
//子文件:C://jason/liuyan_%d.png(path_pattern的格式)
sprintf(patches[i],path_pattern,(i+1));
LOGI("patch path:%s",patches[i]);
}
//不断读取path文件,循环写入file_num个文件中
// 整除
// 文件大小:90,分成9个文件,每个文件10
// 不整除
// 文件大小:110,分成9个文件,
// 前(9-1)个文件为(110/(9-1))=13
// 最后一个文件(110%(9-1))=6
//获取文件大小
int file_size = get_file_size(path);
//打开这个文件
FILE *fpr = fopen(path,"rb");
//整除
if(file_size % file_num == 0){
//单个文件大小
int part = file_size/file_num;
int i =0;
//逐一写入设置好的子文件路径中
for(;i<file_num;i++){
//从子文件路径打开一个FILE
FILE *fpw = fopen(patches[i],"wb");
int j = 0;
for(;j<part;j++){
//边读边写
//fgetc函数功能:从流中读取字符,即从fp所指定的文件中取得下一个字符。这里需要注意,在每取完一个字符时fp会自动向下移动一个字节。这样编成时,程序员就不用再对fp //控制了。这种功能在许多读写函数中都有体现。
fputc(fgetc(fpr),fpw);
}
fclose(fpw);
}
}else{
//无法整除
int part = file_size/(file_num-1);
int i = 0;
for(;i<file_num-1;i++){
FILE *fpw = fopen(patches[i],"wb");
int j=0;
for(;j<part;j++){
fputc(fgetc(fpr),fpw);
}
fclose(fpw);
}
//最后一个子文件
FILE *fpw = fopen(patches[file_num-1],"wb");
i = 0;
for(;i<file_size%(file_num-1);i++){
fputc(fgetc(fpr),fpw);
}
fclose(fpw);
}
//释放
i = 0;
for(;i<file_num;i++){
free(patches[i]);
}
free(patches);
(*env)->ReleaseStringUTFChars(env,path_jstr,path);
(*env)->ReleaseStringUTFChars(env,path_pattern_jstr,path_pattern);
}
/*
* Class: com_ren_ndk_file_patch_PatchUtils
* Method: patch
* Signature: (Ljava/lang/String;Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL Java_com_ren_ndk_1file_1patch_PatchUtils_patch
(JNIEnv *env, jclass jclz, jstring path_pattern_jstr, jstring merge_path_jstr, jint file_num){
//字符串转换
const char *merge_path = (*env)->GetStringUTFChars(env,merge_path_jstr,NULL);
const char *path_pattern = (*env)->GetStringUTFChars(env,path_pattern_jstr,NULL);
//得到分割后的子文件路径列表
char **patches = malloc(sizeof(char*)*file_num);
int i = 0;
for(;i<file_num;i++){
patches[i]=malloc(sizeof(char)*100);
//元素赋值
//需要分割的文件:C://jason/liuyan.png
//子文件:C://jason/liuyan_%d.png
sprintf(patches[i],path_pattern,i+1);
LOGI("patch path:%s",patches[i]);
}
//打开要merge_path为一个FILE
FILE *fpw = fopen(merge_path,"wb");
//把所有分割的文件读取一遍,写入到这个总的文件中
i = 0;
for(;i<file_num;i++){
//得到每个子文件的大小
int file_size = get_file_size(patches[i]);
//打开每个子文件路径为一个FILE
FILE *fpr = fopen(patches[i],"rb");
int j =0;
for(;j<file_size;j++){
fputc(fgetc(fpr),fpw);
}
fclose(fpr);
}
fclose(fpw);
//释放内存
i = 0;
for(;i<file_num;i++){
free(patches[i]);
}
free(patches);
(*env)->ReleaseStringUTFChars(env,merge_path_jstr,merge_path);
(*env)->ReleaseStringUTFChars(env,path_pattern_jstr,path_pattern);
}
4.Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ndk_file_patch
LOCAL_SRC_FILES := ndk_file_patch.c
LOCAL_LDLIBS := -llog //使用了log,需要引入
include $(BUILD_SHARED_LIBRARY)
5.开始拆分合并
public class MainActivity extends Activity {
private String SD_CARD_PATH;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SD_CARD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath();
}
public void diff(View view){
String path = SD_CARD_PATH +File.separatorChar+ "2.mp4";
String path_pattern = SD_CARD_PATH +File.separatorChar+ "2_%d.mp4";
PatchUtils.diff(path, path_pattern, 2);
System.out.println("拆分完成");
}
public void patch(View view){
String merge_path = SD_CARD_PATH +File.separatorChar+ "2_new.mp4";
String path_pattern = SD_CARD_PATH +File.separatorChar+ "2_%d.mp4";
PatchUtils.patch(path_pattern, merge_path, 2);
System.out.println("合并完成");
}
}
6.注意添加权限,6.0动态申请
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>