Я пытаюсь загрузить PDF-файлы с помощью Retrofit 2 в приложении для Android, написанном на Kotlin.Фрагмент ниже - это в основном весь мой код.Судя по моим выводам в журнале, файл успешно загружается и сохраняется в нужном месте.
Однако загруженный файл больше ожидаемого и поврежден.Я могу открыть его в PDF-ридере, но PDF-файл пуст.В приведенном ниже примере я попытался загрузить https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf. Если я скачаю этот файл через браузер, в результате получится PDF размером 13 264 байта.Однако, загруженный с этим кодом, он составляет 22 503 байта, что примерно на 70% больше, чем ожидалось.Я получаю аналогичный результат для других двоичных файлов, таких как JPEG.Тем не менее, загрузка TXT на самом деле работает нормально, даже большой.Похоже, проблема связана с двоичными файлами.
package com.ebelinski.RetrofitTestApp
import android.app.Application
import android.content.Context
import android.os.Build
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.ResponseBody
import org.jetbrains.anko.doAsync
import retrofit2.Call
import retrofit2.http.*
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
interface FileService {
@Streaming
@GET
@Headers("Content-Type: application/pdf", "Accept: application/pdf")
fun fileFromUrl(@Url url: String,
@Header("Authorization") tokenTypeWithAuthorization: String): Call<ResponseBody>
}
class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
val RETROFIT_CONNECT_TIMEOUT_SECONDS = 60
private val RETROFIT_READ_TIMEOUT_SECONDS = 60
private val RETROFIT_WRITE_TIMEOUT_SECONDS = 60
private val retrofit: Retrofit
get() {
val gson = GsonBuilder()
.setDateFormat("yyyyMMdd")
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
val converterFactory = GsonConverterFactory.create(gson)
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(RETROFIT_CONNECT_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)
.readTimeout(RETROFIT_READ_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)
.writeTimeout(RETROFIT_WRITE_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)
.addInterceptor { chain ->
val userAgentValue = "doesn't matter"
val originalRequest = chain.request().newBuilder().addHeader("User-Agent", userAgentValue).build()
var response = chain.proceed(originalRequest)
if (BuildConfig.DEBUG) {
val bodyString = response.body()!!.string()
Log.d(TAG, String.format("Sending request %s with headers %s ", originalRequest.url(), originalRequest.headers()))
Log.d(TAG, String.format("Got response HTTP %s %s \n\n with body %s \n\n with headers %s ", response.code(), response.message(), bodyString, response.headers()))
response = response.newBuilder().body(ResponseBody.create(response.body()!!.contentType(), bodyString)).build()
}
response
}
.build()
return Retrofit.Builder()
.callbackExecutor(Executors.newCachedThreadPool())
.baseUrl("https://example.com")
.addConverterFactory(converterFactory)
.client(okHttpClient)
.build()
}
private val fileService: FileService = retrofit.create(FileService::class.java)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
doAsync { downloadFile() }
}
fun downloadFile() {
val uri = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
val auth = "doesn't matter"
val response = fileService.fileFromUrl(
uri,
auth
).execute()
if (!response.isSuccessful) {
Log.e(TAG, "response was not successful: " +
response.code() + " -- " + response.message())
throw Throwable(response.message())
}
Log.d(TAG, "Server has file for ${uri}")
saveFileFromResponseBody(response.body()!!)
}
// Returns the name of what the file should be, whether or not it exists locally
private fun getFileName(): String? {
return "dummy.pdf"
}
fun saveFileFromResponseBody(body: ResponseBody): Boolean {
val fileName = getFileName()
val localFullFilePath = File(getFileFullDirectoryPath(), fileName)
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
Log.d(TAG, "Attempting to download $fileName")
try {
val fileReader = ByteArray(4096)
val fileSize = body.contentLength()
var fileSizeDownloaded: Long = 0
inputStream = body.byteStream()
outputStream = FileOutputStream(localFullFilePath)
while (true) {
val read = inputStream.read(fileReader)
if (read == -1) break
outputStream.write(fileReader, 0, read)
fileSizeDownloaded += read.toLong()
Log.d(TAG, "$fileName download progress: $fileSizeDownloaded of $fileSize")
}
outputStream.flush()
Log.d(TAG, "$fileName downloaded successfully")
return true
} catch (e: IOException) {
Log.d(TAG, "$fileName downloaded attempt failed")
return false
} finally {
inputStream?.close()
outputStream?.close()
}
}
fun getFileFullDirectoryPath(): String {
val directory = getDir("test_dir", Context.MODE_PRIVATE)
return directory.absolutePath
}
}
Если это поможет, вот мой build.gradle
файл:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.ebelinski.RetrofitTestApp"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation "org.jetbrains.anko:anko-commons:0.10.1"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}