为了偷懒,使用了nio中的FileChannel进行流的读取,代码如下:
1 | long transferSize = toChannel.transferFrom(fromChannel, 0, Long.MAX_VALUE); |
解释一下为什么这里第三个参数用Long.MAX_VALUE,因为代码上下文中,这个值具备不确定性,自己得到的值可能是-1,也可能是正确的值,为了简单起见,传递一个最大值进去,transferFrom内部会有一个Math.min操作(7.0以上的代码),取这个值和真正的大小中小的那个。
写个单元测试测一下,完美,没有任何问题。
某一天,同事跑过来说这段读取逻辑有问题,报了一个异常
1 | java.lang.IllegalArgumentException: position=0 count=9223372036854775807 |
然后我又测了下,在自己的手机上跑一下,没问题,再在pc上跑一下单元测试,也没问题。但是同事那边是有问题的,于是怀疑和系统版本有关系,仔细排查了一下,确实是这样,Android 7.0以上没问题,Android 7.0以下全部阵亡。
翻下AOSP的代码
- Android 6.0的实现
那么实现有什么差异呢,先看7.0以下的实现,这里以6.0为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
checkOpen();
if (!src.isOpen()) {
throw new ClosedChannelException();
}
checkWritable();
if (position < 0 || count < 0 || count > Integer.MAX_VALUE) {
throw new IllegalArgumentException("position=" + position + " count=" + count);
}
if (position > size()) {
return 0;
}
// Although sendfile(2) originally supported writing to a regular file.
// In Linux 2.6 and later, it only supports writing to sockets.
// If our source is a regular file, mmap(2) rather than reading.
// Callers should only be using transferFrom for large transfers,
// so the mmap(2) overhead isn't a concern.
if (src instanceof FileChannel) {
FileChannel fileSrc = (FileChannel) src;
long size = fileSrc.size();
long filePosition = fileSrc.position();
count = Math.min(count, size - filePosition);
ByteBuffer buffer = fileSrc.map(MapMode.READ_ONLY, filePosition, count);
try {
fileSrc.position(filePosition + count);
return write(buffer, position);
} finally {
NioUtils.freeDirectBuffer(buffer);
}
}
// For non-file channels, all we can do is read and write via userspace.
ByteBuffer buffer = ByteBuffer.allocate((int) count);
src.read(buffer);
buffer.flip();
return write(buffer, position);
}
擦,入参count是Long类型,竟然校验的时候用int的最大值去校验,元凶就在这里了
1 | if (position < 0 || count < 0 || count > Integer.MAX_VALUE) { |
那么看看7.0以上为什么没有报错
1 | public long transferFrom(ReadableByteChannel src, |
没毛病,没有对count的最大值进行校验,自然不会抛异常。
那么PC上的单元测试为什么没有报错呢,因为自己电脑上的JDK是1.8的版本,PC上的实现和Android 7.0的实现是一样的,都是基于Java8实现的,自然也测不出问题。
最后这个问题怎么解决呢,不能偷懒,改成buffer读取
1 | ByteBuffer buffer = ByteBuffer.allocate(4096); |
还有一种解决方法就是传递具体的大小进去,前提是你知道具体的大小
1 | long transferSize = toChannel.transferFrom(fromChannel, 0, 具体的大小); |
