“知识就是力量,人才就是未来”,“千秋基业,人才为先。实现中华民族伟大复兴,人才越多越好,本事越大越好”。
本篇继续学习android studio下的ndk开发。关于cmake还不了解的可以回头看看“android
studio使用cmake进行ndk开发”这篇文章;关于java与c数据类型转换还不懂的可以回头看看“android
dk开发之原生代码创建java实例与java实例转c结构体,原生代码抛出java异常”。
本篇介绍的是如何使用原生代码将java对象保存到文件中以及如何使用原生代码从文件中读取java对象,简单来说就是c语言的二进制文件读写操作。
文件写操作
文件读操作
01
c/c++文件操作函数
前面写过一篇“Linux下c编程之文件读写操作,自定义终端下的拷贝命令”的文章,该文章介绍的是如何使用Linux下的系统调用函数来进行文件读写操作。而本篇使用的是与平台无关的c封装基于流的文件操作函数。那么这里就有必须介绍一下fopen,fclose,fseek,fwrite,fread这几个函数的使用了。
必须包含头文件:
#include
一、fopen和fclose
fopen函数的定义:
FILE * fopen(const char* path,const char* mode);
fopen函数用于以流方式打开指定的一个文件,第一个参数path指定文件的路径,第二个参数mode用于指定文件流的打开方式。如果打开成功则返回一个FILE结构体的指针。
第二个参数mode的可取值如下表:
mode描述
r以只读方式打开文件,该文件必须存在。
r+以可读写方式打开文件,该文件必须存在。
rb+读写打开一个二进制文件,允许读数据。
w打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
w+打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
a+以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
(原来的EOF符不保留)
wb只写打开或新建一个二进制文件;只允许写数据。
wb+读写打开或建立一个二进制文件,允许读和写。
at+读写打开一个文本文件,允许读或在文本末追加数据。
ab+读写打开一个二进制文件,允许读或在文件末追加数据。
fclose函数的定义:
int fclose(FILE* fp);
当完成对一个流的操作后,需要调用fclose函数将其关闭。fclose函数只有一个参数,就是指向需要关闭的流的指针。当调用成功时返回0,否则返回EOF(定义为-1的宏)。
二、二进制读写,fwrite和fread
fwrite函数的定义:
size_t fwrite(const void * __restrict, size_t, size_t, FILE *
__restrict);
fwrite函数用于执行写入操作,第一个参数是指向存放将要输入数据的缓冲区的指针,第二个参数是写入对象的大小,第三个参数是欲写入的对象的个数,第四个参数是指向要写入的流的FILE结构体指针。返回值为写入的对象的数量,如果返回值少于第三个参数指定的数量,则写入出错。
fread函数的定义:
size_t fread(void * __restrict, size_t, size_t, FILE * __restrict);
fread函数用于读取操作,第一个参数是指向读取数据的缓冲区指针(也就是指定读取的数据存在到哪里),第二个参数是读取对象的大小,第三个参数是欲读取的对象的个数,第四个参数是指向要读取的流的FILE结构体指针。返回值为读取的对象数,如果读取出错后读到了文件尾,则返回值会少于第三个参数指定的欲读取对象的个数。
三、扩展知识,feof和ferror
feof函数的定义:
int feof(FILE *);
feof函数用于判断打开的文件流当前的偏移量是否到了文件尾,在读取文件的时候可以用于判断是否读取到了文件尾(如果到了文件尾则FILE结构体的文件结束标志被置为真【非0值】,否则为假【0】)。该函数只有一个参数,就是指向要判断的流的FILE结构体指针。返回值为非零则表示到了文件尾。
ferror函数的定义:
int ferror(FILE *);
ferror函数用于判断当前对文件流的操作是否出错(FILE结构体的出错标志位:出错【非0值】,没有错误【0】)。该函数只有一个参数,就是指向要判断的流的FILE结构体指针。返回值为非零则表示对文件的操作出错了。
02
一个以java对象为单位的文件读写操作实例
以Student对象的读写为例,利用原生代码实现Student对象的保存与读取。首先分析实现思路:
在android 端实例数据的输入,将输入的数据转为 java对象(Student实例);
通过调用java类的本地方法来调用原生代码的写操作将Studetn实例保存到文件中;
通过调用java类的本地方法来调用原生代码的读操作读取文件中保存的所有Student实例;
android 端将读取的所有Student实例以ListView显示到页面上。
03
定义Student类和Student结构体
java中定义的Student类的属性必须与c代码中定义的结构体的字段一一对应。
java中的Student.java
c中的Student结构体的定义,在Student.h头文件中
04
IOStream.c 中实现对Student结构的读写操作
IOStream.h头文件中声明文件读写的方法供外部引入调用。
IOStream.c中实现IOStream.h头文件中声明的方法。
文件打开关闭方法的实现
保存一个Student结构体对象到文件中的实现。以在文件尾追加的方式将Student对象保存到文件中,保存成功返回1,保存失败返回0。
从文件中读取一个Student结构体对象的实现。读取成功后文件偏移量会自动移动到下一个Student结构体对象在文件中的开始位置。如果读取到文件尾或读取过程中出错则返回NULL,否则返回读取的结构体对象。
05
创建一个java类,声明本地方法
创件一个CIOStream.java文件,在CIOStream类的静态代码块中实现加载原生代码编译后的.so库文件。声名一个打开文件流方法,一个关闭文件流方法,一个保存Student对象到文件中的方法和一个中文件流中读取一个Student对象的方法。
在调用保存Student对象到文件中和从文件中读取Student对象的方法之前必须先调用打开文件的方法获取打开的文件流的FIEL结构体指针,在读写操作完成后还要记得调用关闭文件流方法来关闭打开的文件流。
CIOStream.java
使用javah指令获取自动生成的头文件com_wujiuye_cmackndktest_ndk_CIOStream.h
,将其拷贝到/src/main/cpp目录下。
在src/main/cpp目录下新建一个IOFileDemo.c文件,引入javah生成的头文件实现头文件中声明的方法,也就是实现java类中对应的native方法。
引入前面定义的IOSteram.h头文件,调用IOStream.c中实现的打开文件流方法打开文件流,获取文件流的FIEL结构体指针返回。
调用IOStream.c中实现的关闭文件流方法关闭文件流。
实现将java对象(Student)转换为c结构体(Student)对象,调用IOStream.c中实现的保存Student结构体对象方法来将对象保存到文件中
调用IOStream.c中实现的从文件中读取一个Student结构体对象的方法获取读到的Student结构体对象,将Student结构体对象转换为与Java的Student类对应的jni的jobject对象并返回
06
输入Student信息页面的实现
新建一个Activity,命名为CClassWriteActivity。
CClassWriteActivity的页面布局:
当点击“将学生信息保存到文件中”按钮时,获取输入的学生信息创建一个Student对象,在子线程中调用CIOStream对象中的本地方法将Student对象保存到指定的文件中。
07
读取文件中的所有Student对象显示到页面
当在CClassWriteActivity页面中点击“读取文件中的学生信息”按钮时,跳转到读取学生信息显示的页面CClassReadActivity。
CClassReadActivity的页面布局:
在页面的onCreate方法中调用onInit方法,在onInit方法创建一个子线程从指定文件中循环读取Student对象,将读取到的对象保存到列表中,当读取到的对象为空时说明读取到了文件尾或读取出错则跳出循环并关闭文件流。
只贴了核心代码,其它的就不贴了。
08
实例运行效果