JNI下C读写大小端和权限问题

最近搞项目期间需要频繁的操作数据文件,之前都是用C进行操作,转到java的时候出现了一点小问题:
在Android平台上进行wave的特征重构,重构的wave文件希望使用C直接输入,因为这部分代码在PC上已经验证通过了。Android的话,Application的Context本身提供了openFileInputopenFileOutput,用于创建,写入存在目录/data/data/package_name/files/里面的文件。所以如果希望java能够读取C的输出文件的话,C在JNI部分的输出目录也应该是上面这个。

经过测试,C端的创建,写入均没有问题,java部分的读取也没有报错,只是第一次读取就失败。问题最终抽象成java读取二进制文件的问题。C端使用如下代码生成一个二进制文件

1
2
for (short i = 0; i < 400; ++i)
fwrite(&i, sizeof(short), 1, wav);

java上原先打算使用DataInputStream来操作的,毕竟他拥有一系列读取各种类型数据的函数,但是上例中使用readShort()并不成功。
什么情况下成功呢?如果使用DataOutputStream,写出来的文件可以被上述方式正确读取,而且也是二进制格式,于是我认为该方法可以普遍适用于二进制文件的读取。
后来也是受到了java读取wave文件的启发,先读取整块byte的buffer,之后转成相应的数据类型,如下:

1
2
3
4
5
6
wavWriter.read(wav);
for (int i = 0; i < wav.length / 2; i++) {
short s0 = (short) (wav[2 * i] & 0xff);
short s1 = (short) ((wav[2 * i + 1] & 0xff) << 8);
dat[i] = (short) (s1 | s0);
}

莫非这是传说中的大端小端问题?C直接读Java写的二进制文件也有问题,以short为例,转换代码如下,其实和上面是互逆的。

1
2
3
4
5
6
for (int i = 0; i < FRAME_LEN; ++i)
{
short s0 = val[i * 2] & 0xff
short s1 = val[i * 2 + 1] & 0xff;
wav[i] = (s0 << 8) | s1);
}

总而言之,四个关系中,交叉读取文件是需要进行格式转换的,java中的低位是C中的高位。如果java想直接读取C的二进制文件,在写C的时候,提前做好格式转换也是可以的,比如:

1
2
3
4
5
short s0 = data[i] & 0x00ff;
short s1 = data[i] & 0xff00;
s0 = (s0 << 8);
s1 = (s1 >> 8);
data[i] = (s0 | s1);

另外,如果将文件写在应用的私有包目录之下,应用访问没用问题,但是想pull出来的时候,提示没有权限,所以想换个公有的目录访问。

现在的手机内部存储空间已经能满足大部分用户的需求了,所以少有支持外插SD卡的了,故基本认为存储空间为internal space,我想在/sdcard/目录下操作文件, errno依旧是permission denied

但是其实adb shell进该目录之下,依旧可以创建删除文件。最终很奇怪,赋予了SD卡读写的权限之后

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

问题解决。
补充:后来发现在某些6.0的机器上的权限需要添加动态申请
1
2
3
4
5
6
7
8
9
10
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
};
int permission = ActivityCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
}