douzifly's blog

InputStream wrapper

今天在开发Android局域网文件传输的时候,遇到这么一个需求,当客户端向HTTP SVR请求文件时,HTTP SVR应答的数据流来自多个文件。比如,客户端发起a.rmvb文件下载请求,而在服务端上,a.rmvb并不真实存在,而是存在a1.rmvb, a2.rmvb, a3.rmvb 等。

服务器端采用的时开源的简单HTTP Server NanoHttpd, 但是NanoHttpd暴露的接口是一个InputStream对象。

方案一: 服务器端将文件合并后发送。这样就可以new FileInputStream(mergedFilePath)给NanoHttpd了。 这样做虽然简单,但是缺点非常明显,服务器并不需要一个完整的文件,这么做效率底下,还占用而外的磁盘空间,数据会进行多次拷贝。

方案二: 修改NanoHttpd让其支持接收InputStream[ ]数组这样的数据结构. 缺点:对现有代码修改,在项目周期紧的情况下,风险较高。

方案三: 写一个InputStream的包装,接收多个InputStream。 直接上代码:

/**
 * Author:Xiaoyuan
 * Date: Oct 21, 2013
 */
package com.qvod.tuitui.network;

import java.io.IOException;
import java.io.InputStream;

import android.util.Log;

public class MultiInputStream extends InputStream{
	
	final static String TAG = "MulitInputStream";

	private InputStream[] mStreams;
	private int mReadStreamIndex;
	private int mAvai = 0;
	
	public MultiInputStream(InputStream[] streams) throws IOException {
		if(streams == null || streams.length == 0) {
			throw new IllegalArgumentException();
		} 
		mStreams = streams;
		calcAvai();
	}
	
	public void calcAvai() throws IOException {
		for(int i = 0; i < mStreams.length; i++) {
			mAvai += mStreams[i].available();
		}
		Log.d(TAG, "calc:" + mAvai);
	}
	
	@Override
	public int available() throws IOException {
		return mAvai;
	}
	
	private void close(int index) {
		Log.d(TAG, "close index:" + index);
		try {
			mStreams[index].close();
		} catch(Exception e) {
			Log.e(TAG, "close exp:" + e.getMessage());
			e.printStackTrace();
		}
	}
	
	@Override
	public int read() throws IOException {
		int value = -1;
		int streamIndex = mReadStreamIndex;
		InputStream s = mStreams[streamIndex];
		value = s.read();
		if(value == -1) {
			Log.d(TAG, "read -1, check next");
			// check have next stream
			if(mReadStreamIndex < mStreams.length) {
				close(mReadStreamIndex);
				mReadStreamIndex++;
				Log.d(TAG, "read next :" + mReadStreamIndex);
				// read next byte from next stream
				return read();
			}
		}
//		Log.d(TAG, "read return:" + value + " stream index:" + mReadStreamIndex);
		return value;
	}
	
	@Override
	public int read(byte[] buffer, int offset, int length) throws IOException {
		int readCount = -1;
		int streamIndex = mReadStreamIndex;
		InputStream s = mStreams[streamIndex];
		readCount = s.read(buffer, offset, length);
		if(readCount == -1) {
			Log.d(TAG, "read -1, check next");
			// check have next stream
			if(mReadStreamIndex < mStreams.length) {
				close(mReadStreamIndex);
				mReadStreamIndex++;
				s = mStreams[mReadStreamIndex];
				Log.d(TAG, "read next :" + mReadStreamIndex);
				// read next byte from next stream
				return s.read(buffer, offset, length);
			}
		}
//		Log.d(TAG, "read return:" + value + " stream index:" + mReadStreamIndex);
		return readCount;
	}
	
	@Override
	public synchronized void reset() throws IOException {
		Log.d(TAG, "reset");
		mReadStreamIndex = 0;
		for(int i = 0; i < mStreams.length; i++) {
			try {
				mStreams[i].reset();
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	@Override
	public void close() throws IOException {
		Log.d(TAG, "close");
		for(int i = 0; i < mStreams.length; i++) {
			try {
				mStreams[i].close();
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
	}

}

需要注意的是,InputStream的read是一个抽象函数,你必须重写它,但是read(buf, offset, len) 却有一个默认的实现,如果不重写的话,效率低的可怜。因为内部实现是循环调用read来进行buf填充的。