美文网首页APP开发经验总结Android开发终端研发部
Android Studio NDK开发(八):文件的拆分与合并

Android Studio NDK开发(八):文件的拆分与合并

作者: zhang_pan | 来源:发表于2017-11-30 14:58 被阅读54次

    前言

    通过前面的学习,相信大家对基本的JNI流程已经有不错的认知了。如果还是有问题,可以看之前的博客。
    Android Studio NDK开发(一):准备工作(CMake方式)
    Android Studio NDK开发(二):Welcome to JNI
    Android Studio NDK开发(三):属性访问
    Android Studio NDK开发(四):方法访问
    Android Studio NDK开发(五):数组的同步问题
    Android Studio NDK开发(六):JNI引用
    Android Studio NDK开发(七):文件的加密与解密
    本篇博客将介绍文件的拆分与合并。

    准备工作

    1.创建FilePatchActivity,别忘记在清单文件中配置,布局文件如下,简单的两个按钮,分别控制拆分与合并

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center_vertical">
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="拆分"
            android:onClick="mSplit"/>
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="合并"
            android:onClick="mMerge"/>
    </LinearLayout>
    

    2.在cpp文件夹下创建一个C的文件file_patch.c,然后在CMakeLists.txt中添加库并绑定库,如果对此不明白的,可以参考我之前的博客。
    3.创建一个Java类FilePatchUtil,里面定义两个方法,分别为splitmerge,同时写上同步代码块,加载file_patch库。

    public class FilePatchUtil {
        static {
            System.loadLibrary("file_patch");
        }
    
        //拆分
        public native static void split(String path, String pathPattern, int count);
        //合并
        public native static void merge(String pathMerge, String pathPattern, int count);
    }
    

    4.此时我们可以Alt + Enter一键生成C的函数,此时如果你的项目cpp文件夹下有多个C/C++文件,生成的函数可能不在你所希望的文件下,这时候直接复制粘贴即可。(暂时我还没有更好的处理方式)

    拆分

    首先添加头文件:

    #include "jni.h"
    #include <stdlib.h>
    #include <stdio.h>
    #include <android/log.h>
    

    添加公共的函数:

    //根据文件的路径,获得文件的大小
    long get_file_size(char* path) {
        FILE* fp = fopen(path, "rb");
        fseek(fp, 0, SEEK_END);
        return ftell(fp);
    }
    

    接下来就是文件拆分函数:

    JNIEXPORT void JNICALL
    Java_com_zhangpan_myjnicmake_FilePatchUtil_split(JNIEnv *env, jclass jcla, jstring path_,
                                                    jstring path_pattern_, jint file_count) {
        //需要分割的文件路径
        const char* path = (*env)->GetStringUTFChars(env, path_, NULL);
        //分割后的子文件路径模板
        const char* path_pattern = (*env)->GetStringUTFChars(env, path_pattern_, NULL);
    
        //得到分割后的子文件的路径列表
        char** patches = malloc(sizeof(char*) * file_count);
        //也可以这样去动态分配内存
        //char **patches = malloc(sizeof(char) * get_file_size(path));
        int i = 0;
        for (; i < file_count; i++) {
            //元素赋值
            patches[i] = malloc(sizeof(char) * 100);
            //也可以这样动态分配内存
            //patches[i] = malloc(sizeof(char) * (get_file_size(path) / file_count));
    
            //path_pattern /storage/emulated/0/AS_%d.avi
            // /storage/emulated/0/AS_1.avi
            // /storage/emulated/0/AS_2.avi
            // /storage/emulated/0/AS_3.avi
            // /storage/emulated/0/AS_4.avi
            sprintf(patches[i], path_pattern, (i+1));
            LOGD("%s", patches[i]);
        }
    
        //不断读取path文件,循环写入file_count个文件中
        //整除:文件大小为90,分成9个文件,每个文件为10
        //不整除:文件大小为110,分成9个文件,前(9-1)个文件为(110 / (9-1)) = 13,最后一个文件为(110 % (9-1)) = 6
        //前(9-1)个文件为110/9 + 1 = 13,最后一个文件为 110 - 13*8 = 6
        int fileSize = get_file_size(path);
        FILE* fpr = fopen(path, "rb");
        if (fileSize % file_count == 0) {       //整除
            int partSize = fileSize / file_count;
            int i = 0;
            for (; i < file_count; i++) {
                int j = 0;
                FILE* fpw = fopen(patches[i], "wb");
                for(; j < partSize; j++) {
                    fputc(fgetc(fpr), fpw);
                }
                fclose(fpw);
            }
        } else {                                //非整除
            int prePartSize = fileSize / file_count + 1;
            int lastPartSize = fileSize - (file_count - 1) * prePartSize;
            //写入前(file_count - 1)个文件
            int i = 0;
            for(; i < file_count - 1; i++) {
                FILE* fpw = fopen(patches[i], "wb");
                int j = 0;
                for (; j < prePartSize; j++) {
                    fputc(fgetc(fpr), fpw);
                }
                fclose(fpw);
            }
    
            //写入最后一个文件
            int j = 0;
            FILE* fpw = fopen(patches[file_count - 1], "wb");
            for (; j < lastPartSize; j++) {
                fputc(fgetc(fpr), fpw);
            }
            fclose(fpw);
        }
        fclose(fpr);
    
        //释放
        i = 0;
        for (; i < file_count; i++) {
            free(patches[i]);
        }
        free(patches);
    
        (*env)->ReleaseStringUTFChars(env, path_, path);
        (*env)->ReleaseStringUTFChars(env, path_pattern_, path_pattern);
    }
    
    

    分配原理图如下:

    1.png

    首先将分割后的子文件路径作为二维数组的成员,分别动态给这个二维数组和成员分配内存。
    定义一个path_pattern字符串:/storage/emulated/0/AS_%d.avi%d通过sprintf函数动态替换成1, 2, 3, 4,从而将其作为分割后的子文件路径名称。
    当不能整除时,有两种方法可做处理,我所采用的是第二种分配方法,第一种分配方法可能导致最后一个文件没有任何内容(如文件大小为104,分成9个文件),但是仍然可以合并。

    合并

    JNIEXPORT void JNICALL
    Java_com_zhangpan_myjnicmake_FilePatchUtil_merge(JNIEnv *env, jclass jcla, jstring path_merge_,
                                                    jstring path_pattern_, jint file_count) {
        const char *path_merge = (*env)->GetStringUTFChars(env, path_merge_, NULL);
        const char *path_pattern = (*env)->GetStringUTFChars(env, path_pattern_, NULL);
    
        char** patches = malloc(sizeof(char*) * file_count);
        FILE* fpw = fopen(path_merge, "wb");
        int i = 0;
        for (; i < file_count; i++) {
            patches[i] = malloc(sizeof(char) * 100);
            sprintf(patches[i], path_pattern, (i+1));
    //        __android_log_print(ANDROID_LOG_DEBUG, "zp", "%s", patches[i]);
            LOGD("%s", patches[i]);
            //获取每个分割后的子文件大小
            int patchFileSize = get_file_size(patches[i]);
            FILE* fpr = fopen(patches[i], "rb");
    
            //边读边写
            int j = 0;
            for (; j < patchFileSize; j++) {
                fputc(fgetc(fpr), fpw);
            }
            fclose(fpr);
        }
        fclose(fpw);
        //释放
        i = 0;
        for(; i < file_count; i++) {
            free(patches[i]);
        }
        free(patches);
    
        (*env)->ReleaseStringUTFChars(env, path_merge_, path_merge);
        (*env)->ReleaseStringUTFChars(env, path_pattern_, path_pattern);
    }
    

    文件合并的过程,其实就是文件拆分过程的逆过程,相对来说更加简单,不需要考虑是否整除。
    由于需要在Android Studio中打印log日志,添加了头文件

    #include <android/log.h>
    

    比如代码中的log:

     __android_log_print(ANDROID_LOG_DEBUG, "zp", "%s", patches[i]);
    

    此时就可以打log日志并且可以在Android Studio的Logcat日志中查看。但是每次这样写过于繁琐,我们可以通过宏定义来简化代码:

    #define LOGD(FORMAT, ...) __android_log_print(ANDROID_LOG_DEBUG, "zp", FORMAT, ##__VA_ARGS__)
    

    此时我们可以这样调用:

    LOGD("%s", patches[i]);
    

    FilePatchActivity中添加按钮点击方法,分别开启线程处理:

    public class FilePatchActivity extends AppCompatActivity {
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_file_patch);
        }
    
        public void mSplit(View view) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "AS.avi";
                    String pathPattern = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "AS_%d.avi";
                    FilePatchUtil.split(path, pathPattern, 4);
                    Log.d("zp", "拆分完成...");
                }
            }).start();
        }
    
        public void mMerge(View view) {
            new Thread((new Runnable() {
                @Override
                public void run() {
                    String pathMerge = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "AS_merge.avi";
                    String pathPattern = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "AS_%d.avi";
                    FilePatchUtil.merge(pathMerge, pathPattern, 4);
                    Log.d("zp", "合并完成...");
                }
            })).start();
        }
    }
    

    运行

    清单文件添加读写权限,如果在6.0以上,需要动态申请权限,也可以手动手机中对应apk的权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    

    点击运行,点击拆分按钮,可在Logcat中查看

    2.png

    由于我的Android Studio是3.0的,新增了Device File Explorer(默认在Android Studio的右下角),可以很轻松地查看程序执行后的文件目录,如下图:

    3.png

    可以看到这里生成了四个文件,我Debug发现,这里并不是整除,只不过最后一个文件大小和其他的相差不大,换算成MB,也就近似相等。

    再点击合并按钮,可在Logcat中查看

    4.png

    验证

    打开手机,找到对应的AS_merge.avi,点击播放,发现可以完整观看,说明合并完成。

    项目地址

    https://github.com/fsrmeng/MyJniCmake-Master

    展望

    关于JNI流程系列文章即将结束,接下来我们将介绍NDK开发的技术,敬请期待!
    喜欢本篇博客的简友们,就请来一波点赞,您的每一次关注,将成为我前进的动力,谢谢!

    相关文章

      网友评论

        本文标题:Android Studio NDK开发(八):文件的拆分与合并

        本文链接:https://www.haomeiwen.com/subject/cllopxtx.html