我尝试做的是将文件的下载方式与我将其保存到存储区域的方式分开。
运行正常的代码:
以下代码从远程下载文件,最后将其保存到存储中。
private fun downloadFile(url: URL): Observable<Int> {
return Observable.create(fun(emitter)
{
var input: InputStream? = null
var output: OutputStream? = null
var connection: HttpURLConnection? = null
val path = Environment.getExternalStorageDirectory().path
try
{
connection = url.openConnection() as HttpURLConnection
if (connection != null)
{
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK)
{
emitter.onError(IllegalStateException("<HTTP Error> ${connection.responseMessage}. Status code: ${connection.responseCode}."))
return
}
if (connection.contentType == null)
{
emitter.onError(IllegalStateException("<HTTP Error> Unsupported content-type."))
return
}
val fileLength = connection.contentLength
var fileext = MimeTypeMap.getSingleton().getExtensionFromMimeType(connection.contentType)
input = connection.inputStream
output = FileOutputStream("/$path/SmartTVMediaTest.$fileext")
val data = ByteArray(4096)
var totalBytesReceived: Int = 0
while (!emitter.isDisposed)
{
val receivedBytes: Int = input.read(data)
if (receivedBytes < 0)
{
break
}
totalBytesReceived += receivedBytes
if (fileLength > 0)
{
val portion = totalBytesReceived / fileLength.toFloat()
val percentage = portion * 100
emitter.onNext(percentage.toInt())
Log.d(TAG, "<downloadFile> $fileLength, $totalBytesReceived, $percentage")
}
output.write(data, 0, receivedBytes)
}
emitter.onComplete()
}
else {
emitter.onError(IllegalStateException("<HTTP Connection Error> Unsupported connection type."))
}
}
catch(ex: InterruptedException) {
Log.d(TAG, "<Thread> Download cancelled.")
}
catch (ex: IOException)
{
emitter.onError(ex)
}
finally
{
input?.close()
output?.close()
connection?.disconnect()
}
})
}
无效的代码:
所以我开始创建一个表示从远程读取的数据块的类,如下所示:
public data class DownloadChunk(val data: ByteArray, val length: Int, val totalLength: Int)
然后我创建了下载文件的observable,如下所示:
public class DownloadServiceObservable
{
public fun download(url: URL): Observable<DownloadChunk>
{
return Observable.create(fun(emitter)
{
var input: InputStream? = null
var connection: HttpURLConnection? = null
try
{
connection = url.openConnection() as HttpURLConnection
if (connection != null)
{
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK)
{
emitter.onError(IllegalStateException("<HTTP Error> ${connection.responseMessage}. Status code: ${connection.responseCode}."))
return
}
if (connection.contentType == null)
{
emitter.onError(IllegalStateException("<HTTP Error> Unsupported content-type."))
return
}
val totalLength = connection.contentLength
input = connection.inputStream
val data = ByteArray(4096)
while (!emitter.isDisposed)
{
val length: Int = input.read(data)
if (length < 0)
{
break
}
emitter.onNext(DownloadChunk(data, length, totalLength))
}
emitter.onComplete()
}
else
{
emitter.onError(IllegalStateException("<HTTP Connection Error> Unsupported connection type."))
}
}
catch(ex: InterruptedException)
{
emitter.onError(ex)
}
finally
{
input?.close()
connection?.disconnect()
}
})
}
}
最后,我创建了一个Observer
,将文件保存到设备中,如下所示:
public class DownloadFileObserver: Observer<DownloadChunk>
{
private val _file: OutputStream
public override fun onSubscribe(d: Disposable)
{
}
public constructor(path: String)
{
_file = FileOutputStream(path)
}
public override fun onNext(chunk: DownloadChunk)
{
_file.write(chunk.data, 0, chunk.length)
}
public override fun onError(e: Throwable)
{
_file.close()
}
public override fun onComplete()
{
_file.close()
}
}
问题:
由DownloadFileObserver
创建的文件已损坏,因此我的第一个假设是在推送和接收时间项之间无法保证onNext的顺序,但据我所知,我是通过在观察者中添加一个计数器来测试它并在观察者中打印计数器。
我认为我错过了一些东西。
答案 0 :(得分:1)
你需要测试你的代码,因为你知道,试图检查代码以查看错误只是一种挫败感。
首先,测试你的观察者(用Java编写的代码,JUnit 4,道歉):
@Test
public void testObserver() {
DownloadFileObserver uut = new DownloadFileObserver("testpath.txt");
bytes[] testData = "Test String".getBytes();
uut.onNext( new DownloadChunk( testData, testData.length, testData.length );
uut.onComplete();
// inspect the result
bytes[] resultData = Files.readAllBytes( Paths.get( "testpath.txt" );
assertTrue( Arrays.equals( testData, resultData );
}
然后开始添加用于处理多个块的测试,然后是错误。
最后,为DownloadServiceObservable
编写单元测试。这些将更加困难,因为您需要模拟实际的网络呼叫。
答案 1 :(得分:0)
我设法使用测试重现了这个问题并且这是一个线程问题,我订阅并观察了两个不同的线程,因此我读取和写入数据的顺序不同步所以我最终重构{{1现在它需要一个DownloadServiceObservable
的实例来处理从给定源获取流以及负责将数据写入给定目标的StreamFactory
实例,所以现在所有这项工作是在后台完成的。
只是澄清StreamWriter
和StreamFactory
是我创建的两个界面。
现在,StreamWriter
看起来像这样:
DownloadServiceObservable
最后,这是我写的测试:
public class DownloadServiceObservable
{
private val _source: StreamFactory
private val _destination: StreamWriter
public constructor(source: StreamFactory, destination: StreamWriter)
{
_source = source
_destination = destination
}
public fun download(): Observable<DownloadProgress>
{
return Observable.create(fun(emitter)
{
var input: InputStream? = null
try {
input = _source.create()
if (input != null) {
val data = ByteArray(4096)
var totalBytesReceived = 0
while (!emitter.isDisposed) {
val receivedBytes: Int = input.read(data)
if (receivedBytes < 0) {
break
}
totalBytesReceived += receivedBytes
emitter.onNext(DownloadProgress(totalBytesReceived, _source.length))
_destination.write(data, receivedBytes)
}
emitter.onComplete()
}
else {
emitter.onError(IllegalStateException("<${DownloadServiceObservable::class.java}> Source returned with a null value."))
}
}
catch(ex: InterruptedException) {
emitter.onError(ex)
}
finally {
input?.close()
}
})
}
public data class DownloadProgress(val receivedLength: Int, val totalLength: Int)
}