JNI-开发流程 so文件生成.c实现文件拆分,合并,jni中的线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liudao7994/article/details/81980344

使用so库和头文件开发

gradle指定开发平台,在defaultConfig目录里面

	ndk {
            abiFilters "armeabi","x86"
       	}

引入第三方库,在app.gralde中的android目录下

	sourceSets.main {
        	jniLibs.srcDirs = ['libs']
        		jni.srcDirs = []
    		}

c++中找不到外部函数,需要在头文件前加extern “C” {}

生成so文件 只需要 编译后在这里找到
mark

或者

https://www.cnblogs.com/jymblog/p/5526865.html
https://blog.csdn.net/xiejunna/article/details/70875064

c 文件拆分

在之前写的动态注册基础上写代码.

java

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    // Used to load the 'native-lib' library on application startup.
    private static String SD_CARD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

       // diff();
    }


    public void diff(View v){
        Log.d(TAG, "diff begin----");
        String path = SD_CARD_PATH + File.separatorChar + "myVideo.mp4";
        File file = new File(path);
        Log.d(TAG, "diff: 路径是否存在 path = "+file.exists());
        String pattern_Path = SD_CARD_PATH + File.separatorChar + "myVideo_%d.mp4";
        FileUtils.diff(path,pattern_Path,4);
        Log.d(TAG, "diff end----");
    }

}

c代码


//
// Created by liuml on 2018/8/15.
//

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <android/log.h>
#include <assert.h>
#include <malloc.h>
/* Header for class androidrn_myjni_FileUtils */

#define TAG "jni_LOG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define NELEM(x) ((int)(sizeof(x)/sizeof((x)[0])))
//int __android_log_print(int prio, const char* tag, const char* fmt, ...)
/*
 * Class:     androidrn_myjni_FileUtils
 * Method:    diff
 * Signature: (Ljava/lang/String;Ljava/lang/String;I)V
 */
//JNIEXPORT void JNICALL Java_androidrn_myjni_FileUtils_diff
//        (JNIEnv *env, jclass jclazz, jstring path, jstring pattern_Path, jint file_num) {
//    LOGI("jna log test");
////    LOGI("JNI 动态注册");
//}

//获取文件的大小
long get_file_size(const char *path) {

    FILE *fp = fopen(path, "rb");//打开一个文件, 文件必须存在,只读运行
    fseek(fp, 0, SEEK_END);//文件指针定位到文件末尾,偏移0个字节
    long ret = ftell(fp);//函数ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数
    fclose(fp);
    LOGI("获取文件的大小 %l",ret);
    return ret;
}

JNIEXPORT void JNICALL native_diff
        (JNIEnv *env, jclass jclazz, jstring path, jstring pattern_Path, jint file_num) {
    LOGI("JNI native_diff begin");
    //这里是 将传入的字符串转化c的指针. path_Str 为文件地址的路径  pattern_Path_str为 分割出来的文件名字
    const char *path_Str = (*env)->GetStringUTFChars(env, path, NULL);
    const char *pattern_Path_str = (*env)->GetStringUTFChars(env, pattern_Path, NULL);

    //申请二维字符数据, 存放子文件名
    char **patches = (char **) malloc(sizeof(char *) * file_num);

    int i = 0;
    for (; i < file_num; i++) {
        //给每个文件名申请地址
        LOGI("char 和char 指针占用的字节 char = %d char * = %d", sizeof(char), sizeof(char *));
        patches[i] = (char *) malloc(sizeof(char) * 100);
        // 需要分割的文件 myVideo.mp4
        // 每个子文件名称 myVideo_n.mp4
        //sprintf 函数 格式化打印
        sprintf(patches[i], pattern_Path_str, i);//格式化为 myVideo_n.mp4 传递进来是带%d 的 所以能够格式化
        LOGI("patch path : %s", patches[i]);
    }
    //获取文件的大小
    int fileSize = get_file_size(path_Str);
    //打开文件
    LOGI("打开文件 %d",path_Str);
    FILE *fpr = fopen(path_Str, "rb");
    /*
    * 1.判断文件大小能够被 file_num整除
    * 2.能整除就平分
    * 3.不能整除就先分 file_num -1
    * */

    if (fileSize % file_num == 0) {//如果可以被传入的file_num 整除
        LOGI("刚好被整除fileSize fileSize= %d ", fileSize);
        int part = fileSize / file_num;//每个被拆分的文件大小
        for (int i = 0; i < file_num; ++i) {
            //wb 文件如果已经存在就删除,只写运行. 不存在则创建
            LOGI("外层循环 i = %d", i);
            FILE *fpw = fopen(patches[i], "wb");
            for (int j = 0; j < part; ++j) {
                //fputc 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动
                //fgetc 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。
                // LOGI("开始读取stream j = %d", j);
                fputc(fgetc(fpr), fpw);
            }
            fclose(fpw);
        }
    } else {//不能整除,先分 file_num -1
        LOGI("不能整除 fileSize= %d ", fileSize);
        int part = fileSize / (file_num - 1);
        for (int i = 0; i < file_num - 1; i++) {
            LOGI("外层循环 i = %d", i);
            FILE *fpw = fopen(patches[i], "wb");
            for (int i = 0; i < part; i++) {
                fputc(fgetc(fpr), fpw);
            }
            fclose(fpw);
        }
        //处理最后一个
        LOGI("处理最后一个");
        FILE *fpw = fopen(patches[file_num - 1], "wb");

        for (int i = 0; i < fileSize % (file_num - 1); i++) {
            fputc(fgetc(fpr), fpw);
        }

        fclose(fpw);
    }
    LOGI("释放指针");
    fclose(fpr);

    //释放指针  记住每次malloc后 必须释放
    for (int i = 0; i < file_num; i++) {
        free(patches[i]);
    }
    free(patches);
    //同样释放
    (*env)->ReleaseStringUTFChars(env, path, path_Str);
    (*env)->ReleaseStringUTFChars(env, pattern_Path, pattern_Path_str);

}


static const JNINativeMethod gMethods[] = {
        {
                "diff", "(Ljava/lang/String;Ljava/lang/String;I)V", (void *) native_diff
        }
};

//注册本地方法
static int registerNatives(JNIEnv *env) {

    jclass clazz;
    clazz = (*env)->FindClass(env, "androidrn/myjni/FileUtils");
    if (clazz == NULL) {
        LOGI("clazz == NULL");
        return JNI_FALSE;
    }
    LOGI("clazz != NULL");
    if ((*env)->RegisterNatives(env, clazz, gMethods, NELEM(gMethods)) < 0) {
        LOGI("RegisterNatives JNI_FALSE");
        return JNI_FALSE;
    }
    return JNI_TRUE;
}


JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGI("jni onload begin");
    /* 这段代码直接拷贝系统的源码 在系统的onload里面  不过系统的是cpp 这里改造成c的*/
    JNIEnv *env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGI("ERROR: GetEnv failed\n");
        return -1;
    }
    assert(env != NULL);
    /*结束*/

    //注册
    registerNatives(env);

    return JNI_VERSION_1_4;

}

mark
TODU:
给拆分加上进度

解释:

--------- beginning of crash

android 读写权限 用个工具类动态获取下

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • 二维数组 用于存放文件名字
0 1 2 3
myVideo_0.mp4 myVideo_1.mp4 myVideo_2.mp4 myVideo_3.mp4
  • fopen 的mode
  • r 以只读方式打开文件,该文件必须存在。
  • r+ 以可读写方式打开文件,该文件必须存在。
  • rb+ 读写打开一个二进制文件,允许读数据。
  • rw+ 读写打开一个文本文件,允许读和写。
  • w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
  • w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
    a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
  • a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
  • wb 只写打开或新建一个二进制文件;只允许写数据。
  • wb+ 读写打开或建立一个二进制文件,允许读和写。
  • ab+ 读写打开一个二进制文件,允许读或在文件末追加数据。
  • at+ 打开一个叫string的文件,a表示append,就是说写入处理的时候是接着原来文件已有内容写入,不是从头写入覆盖掉,t表示打开文件的类型是文本文件,+号表示对文件既可以读也可以写。

上述的形态字符串都可以再加一个b字符,如rb、w+b或ab+等组合,加入b 字符用来告诉函数库以二进制模式打开文件。如果不加b,表示默认加了t,即rt,wt,其中t表示以文本模式打开文件。由fopen()所建立的新文件会具有S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)权限,此文件权限也会参考umask 值。

有些C编译系统可能不完全提供所有这些功能,有的C版本不用"r+",“w+”,“a+”,而用"rw",“wr”,"ar"等,读者注意所用系统的规定。

二进制和文本模式的区别

1.在windows系统中,文本模式下,文件以"\r\n"代表换行。若以文本模式打开文件,并用fputs等函数写入换行符"\n"时,函数会自动在"\n"前面加上"\r"。即实际写入文件的是"\r\n" 。

2.在类Unix/Linux系统中文本模式下,文件以"\n"代表换行。所以Linux系统中在文本模式和二进制模式下并无区别

出自:
http://www.runoob.com/cprogramming/c-function-fopen.html

c 文件合并

基本上是同一个道理

# 文件的合并
---
java 代码

package androidrn.myjni;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.io.File;

public class MainActivity extends AppCompatActivity {
private static final String TAG = “MainActivity”;

// Used to load the 'native-lib' library on application startup.
private static String SD_CARD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

   // diff();
}


public void diff(View v){
    Log.d(TAG, "diff begin----");
    String path = SD_CARD_PATH + File.separatorChar + "myVideo.mp4";
    File file = new File(path);
    Log.d(TAG, "diff: 路径是否存在 path = "+file.exists());
    String pattern_Path = SD_CARD_PATH + File.separatorChar + "myVideo_%d.mp4";
    FileUtils.diff(path,pattern_Path,4);
    Log.d(TAG, "diff end----");
}

public void patch(View v){
    Log.d(TAG, "patch: begin---");
    String path = SD_CARD_PATH + File.separatorChar + "myVideo_new.mp4";
    File file = new File(path);
    Log.d(TAG, "patch: 路径是否存在 path = "+file.exists());
    String pattern_Path = SD_CARD_PATH + File.separatorChar + "myVideo_%d.mp4";
    FileUtils.patch(path,pattern_Path,4);
}

}

package androidrn.myjni;

/**

  • @author liuml
  • @explain
  • @time 2018/8/15 20:35
    */
    public class FileUtils {
    static {
    System.loadLibrary(“native-lib”);
    }

// public static native void diff(String path, String pattern_path, int file_num);
public static native void diff(String path, String pattern_path, int file_num);

public static native void patch(String merger_path, String pattern_Path, int file_num);

}

---

c 代码

/* DO NOT EDIT THIS FILE - it is machine generated /
#include <jni.h>
#include <android/log.h>
#include <assert.h>
#include <malloc.h>
/
Header for class androidrn_myjni_FileUtils */

#define TAG “jni_LOG”
#define LOGI(…) __android_log_print(ANDROID_LOG_INFO, TAG, VA_ARGS)
#define NELEM(x) ((int)(sizeof(x)/sizeof((x)[0])))
//int __android_log_print(int prio, const char* tag, const char* fmt, …)
/*

  • Class: androidrn_myjni_FileUtils
  • Method: diff
  • Signature: (Ljava/lang/String;Ljava/lang/String;I)V
    */
    //JNIEXPORT void JNICALL Java_androidrn_myjni_FileUtils_diff
    // (JNIEnv *env, jclass jclazz, jstring path, jstring pattern_Path, jint file_num) {
    // LOGI(“jna log test”);
    //// LOGI(“JNI 动态注册”);
    //}

//获取文件的大小
long get_file_size(const char *path) {

FILE *fp = fopen(path, "rb");//打开一个文件, 文件必须存在,只读运行
fseek(fp, 0, SEEK_END);//文件指针定位到文件末尾,偏移0个字节
long ret = ftell(fp);//函数ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数
fclose(fp);
return ret;

}

//拆分函数
JNIEXPORT void JNICALL native_diff
(JNIEnv *env, jclass jclazz, jstring path, jstring pattern_Path, jint file_num) {
LOGI(“JNI native_diff begin”);
//这里是 将传入的字符串转化c的指针. path_Str 为文件地址的路径 pattern_Path_str为 分割出来的文件名字
const char *path_Str = (*env)->GetStringUTFChars(env, path, NULL);
const char *pattern_Path_str = (*env)->GetStringUTFChars(env, pattern_Path, NULL);

//申请二维字符数据, 存放子文件名
char **patches = (char **) malloc(sizeof(char *) * file_num);

int i = 0;
for (; i < file_num; i++) {
    //给每个文件名申请地址
    LOGI("char 和char 指针占用的字节 char = %d char * = %d", sizeof(char), sizeof(char *));
    patches[i] = (char *) malloc(sizeof(char) * 100);
    // 需要分割的文件 myVideo.mp4
    // 每个子文件名称 myVideo_n.mp4
    //sprintf 函数 格式化打印
    sprintf(patches[i], pattern_Path_str, i);//格式化为 myVideo_n.mp4 传递进来是带%d 的 所以能够格式化
    LOGI("patch path : %s", patches[i]);
}
//获取文件的大小
int fileSize = get_file_size(path_Str);
//打开文件
LOGI("打开文件 %d", path_Str);
FILE *fpr = fopen(path_Str, "rb");
/*
* 1.判断文件大小能够被 file_num整除
* 2.能整除就平分
* 3.不能整除就先分 file_num -1
* */

if (fileSize % file_num == 0) {//如果可以被传入的file_num 整除
    LOGI("刚好被整除fileSize fileSize= %d ", fileSize);
    int part = fileSize / file_num;//每个被拆分的文件大小
    for (int i = 0; i < file_num; ++i) {
        //wb 文件如果已经存在就删除,只写运行. 不存在则创建
        LOGI("外层循环 i = %d", i);
        FILE *fpw = fopen(patches[i], "wb");
        for (int j = 0; j < part; ++j) {
            //fputc 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动
            //fgetc 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。
            // LOGI("开始读取stream j = %d", j);
            fputc(fgetc(fpr), fpw);
        }
        fclose(fpw);
    }
} else {//不能整除,先分 file_num -1
    LOGI("不能整除 fileSize= %d ", fileSize);
    int part = fileSize / (file_num - 1);
    for (int i = 0; i < file_num - 1; i++) {
        LOGI("外层循环 i = %d", i);
        FILE *fpw = fopen(patches[i], "wb");
        for (int i = 0; i < part; i++) {
            fputc(fgetc(fpr), fpw);
        }
        fclose(fpw);
    }
    //处理最后一个
    LOGI("处理最后一个");
    FILE *fpw = fopen(patches[file_num - 1], "wb");

    for (int i = 0; i < fileSize % (file_num - 1); i++) {
        fputc(fgetc(fpr), fpw);
    }

    fclose(fpw);
}
LOGI("释放指针");
fclose(fpr);

//释放指针  记住每次malloc后 必须释放
for (int i = 0; i < file_num; i++) {
    free(patches[i]);
}
free(patches);
//同样释放
(*env)->ReleaseStringUTFChars(env, path, path_Str);
(*env)->ReleaseStringUTFChars(env, pattern_Path, pattern_Path_str);

}

//合并函数
JNIEXPORT void JNICALL native_patch
(JNIEnv *env, jclass jclazz, jstring merge_path, jstring pattern_Path, jint file_num) {
LOGI(“JNI native patch begin”);
//这里是 将传入的字符串转化c的指针. path_Str 为文件地址的路径 pattern_Path_str为 分割出来的文件名字
const char *path_Str = (*env)->GetStringUTFChars(env, merge_path, NULL);
const char *pattern_Path_str = (*env)->GetStringUTFChars(env, pattern_Path, NULL);

//申请二维字符数据, 存放子文件名
char **patches = (char **) malloc(sizeof(char *) * file_num);
LOGI("分割几部分 %d", file_num);

int i = 0;
for (; i < file_num; i++) {
    patches[i] = (char *) malloc(sizeof(char *) * 100);
    // 需要分割的文件 myVideo.mp4
    // 每个子文件名称 myVideo_n.mp4
    //sprintf 函数 格式化打印
    sprintf(patches[i], pattern_Path_str, i);//格式化为 myVideo_n.mp4 传递进来是带%d 的 所以能够格式化
    LOGI("patch path : %s", patches[i]);
}
FILE *fpw = fopen(path_Str,"wb");

for (int i = 0; i < file_num; i++) {
    //获取拆分后的每个文件大小
    int filesize = get_file_size(patches[i]);
    FILE *fpr = fopen(patches[i],"rb");//从二维数组里面读取文件的名字
    for (int j = 0; j < filesize; j++) {
        //合并文件
        fputc(fgetc(fpr),fpw);
    }
    fclose(fpr);
}
fclose(fpw);

//释放申请的空间
for (int i = 0; i < file_num; i++) {
    free(patches[i]);//每一个malloc 对应一个free
}
free(patches);
(*env)->ReleaseStringUTFChars(env, merge_path, path_Str);
(*env)->ReleaseStringUTFChars(env, pattern_Path, pattern_Path_str);

}

static const JNINativeMethod gMethods[] = {
{
“diff”, “(Ljava/lang/String;Ljava/lang/String;I)V”, (void *) native_diff
},
{
“patch”, “(Ljava/lang/String;Ljava/lang/String;I)V”, (void *) native_patch
}
};

//注册本地方法
static int registerNatives(JNIEnv *env) {

jclass clazz;
clazz = (*env)->FindClass(env, "androidrn/myjni/FileUtils");
if (clazz == NULL) {
    LOGI("clazz == NULL");
    return JNI_FALSE;
}
LOGI("clazz != NULL");
if ((*env)->RegisterNatives(env, clazz, gMethods, NELEM(gMethods)) < 0) {
    LOGI("RegisterNatives JNI_FALSE");
    return JNI_FALSE;
}
return JNI_TRUE;

}

JNIEXPORT jint JNI_OnLoad(JavaVM vm, void reserved) {
LOGI(“jni onload begin”);
/
这段代码直接拷贝系统的源码 在系统的onload里面 不过系统的是cpp 这里改造成c的
/
JNIEnv *env = NULL;
jint result = -1;

if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) {
    LOGI("ERROR: GetEnv failed\n");
    return -1;
}
assert(env != NULL);
/*结束*/

//注册
registerNatives(env);

return JNI_VERSION_1_4;

}


最终效果:

![mark](http://ovji4jgcd.bkt.clouddn.com/blog/180820/F8d6CFH3eG.png?imageslim)


# JNI 中的线程

概念: 
JavaVM *g_jvm   一个进程有一个  
env --->一个线程有一个

引入头文件 

#include <pthread.h>

创建线程的函数

pthread_create


默认的jvm 会给创建一个env 但是自己创建的没有 所以必须调用一次

if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != JNI_OK) {


## 线程 直接看这里把
https://blog.csdn.net/shaohuazuo/article/details/43149193 

猜你喜欢

转载自blog.csdn.net/liudao7994/article/details/81980344
今日推荐