JNI-开发流程 so文件生成.c实现文件拆分,合并,jni中的线程
程序员文章站
2022-06-06 20:39:27
...
使用so库和头文件开发
gradle指定开发平台,在defaultConfig目录里面
ndk {
abiFilters "armeabi","x86"
}
引入第三方库,在app.gralde中的android目录下
sourceSets.main {
jniLibs.srcDirs = ['libs']
jni.srcDirs = []
}
c++中找不到外部函数,需要在头文件前加extern “C” {}
生成so文件 只需要 编译后在这里找到
或者
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;
}
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;
}
最终效果:
JNI 中的线程
概念:
JavaVM *g_jvm 一个进程有一个
env —>一个线程有一个
引入头文件
#include <pthread.h>
创建线程的函数
pthread_create
默认的jvm 会给创建一个env 但是自己创建的没有 所以必须调用一次
if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != JNI_OK) {
线程 直接看这里把
上一篇: php基于登陆时间判断实现一天多次登录只积分一次功能示例
下一篇: python集合类型用法分析