From 2bd68eb1a3894cd5fc0736b65f3a81258fde62cf Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Fri, 27 Jan 2023 15:58:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 + .../app/lib/cronet/BodyUploadProvider.kt | 17 ++++- .../lib/cronet/CronetCoroutineInterceptor.kt | 5 +- .../io/legado/app/lib/cronet/CronetHelper.kt | 15 +++- .../app/lib/cronet/LargeBodyUploadProvider.kt | 75 +++++++++++++++++++ .../io/legado/app/help/config/AppConfig.kt | 2 +- 6 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 app/src/app/java/io/legado/app/lib/cronet/LargeBodyUploadProvider.kt diff --git a/app/build.gradle b/app/build.gradle index 132d143b3..85cecae92 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,6 +49,7 @@ android { buildConfigField "String", "Cronet_Version", "\"$CronetVersion\"" buildConfigField "String", "Cronet_Main_Version", "\"$CronetMainVersion\"" + buildConfigField "boolean", "isGoogle", "false" javaCompileOptions { annotationProcessorOptions { @@ -104,6 +105,7 @@ android { dimension "mode" applicationId "io.legado.play" manifestPlaceholders.put("APP_CHANNEL_VALUE", "google") + buildConfigField "boolean", "isGoogle", "true" } } compileOptions { diff --git a/app/src/app/java/io/legado/app/lib/cronet/BodyUploadProvider.kt b/app/src/app/java/io/legado/app/lib/cronet/BodyUploadProvider.kt index a9d91c10d..4f3d2831c 100644 --- a/app/src/app/java/io/legado/app/lib/cronet/BodyUploadProvider.kt +++ b/app/src/app/java/io/legado/app/lib/cronet/BodyUploadProvider.kt @@ -13,9 +13,18 @@ class BodyUploadProvider(private val body: RequestBody) : UploadDataProvider(), private val buffer = Buffer() + @Volatile + private var filled: Boolean = false + init { + fillBuffer() + } + + private fun fillBuffer() { try { + buffer.clear() body.writeTo(buffer) + buffer.flush() } catch (e: IOException) { e.printStackTrace() } @@ -28,6 +37,9 @@ class BodyUploadProvider(private val body: RequestBody) : UploadDataProvider(), @Throws(IOException::class) override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) { + if (!filled) { + fillBuffer() + } check(byteBuffer.hasRemaining()) { "Cronet passed a buffer with no bytes remaining" } var read: Int var bytesRead = 0 @@ -40,8 +52,9 @@ class BodyUploadProvider(private val body: RequestBody) : UploadDataProvider(), @Throws(IOException::class) override fun rewind(uploadDataSink: UploadDataSink) { - buffer.clear() - body.writeTo(buffer) + check(body.isOneShot()) { "Okhttp RequestBody is oneShot" } + filled = false + fillBuffer() uploadDataSink.onRewindSucceeded() } diff --git a/app/src/app/java/io/legado/app/lib/cronet/CronetCoroutineInterceptor.kt b/app/src/app/java/io/legado/app/lib/cronet/CronetCoroutineInterceptor.kt index 477396467..fb3d357f4 100644 --- a/app/src/app/java/io/legado/app/lib/cronet/CronetCoroutineInterceptor.kt +++ b/app/src/app/java/io/legado/app/lib/cronet/CronetCoroutineInterceptor.kt @@ -93,7 +93,10 @@ class CronetCoroutineInterceptor : Interceptor { } - buildRequest(request, callBack)?.start() + val req = buildRequest(request, callBack)?.also { it.start() } + coroutine.invokeOnCancellation { + req?.cancel() + } } diff --git a/app/src/app/java/io/legado/app/lib/cronet/CronetHelper.kt b/app/src/app/java/io/legado/app/lib/cronet/CronetHelper.kt index b66b1347f..922fd441e 100644 --- a/app/src/app/java/io/legado/app/lib/cronet/CronetHelper.kt +++ b/app/src/app/java/io/legado/app/lib/cronet/CronetHelper.kt @@ -12,10 +12,13 @@ import okhttp3.MediaType import okhttp3.Request import org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISK import org.chromium.net.ExperimentalCronetEngine +import org.chromium.net.UploadDataProvider import org.chromium.net.UrlRequest import org.json.JSONObject import splitties.init.appCtx +internal const val BUFFER_SIZE = 32 * 1024 + val cronetEngine: ExperimentalCronetEngine? by lazy { if (!AppConfig.isGooglePlay) { CronetLoader.preDownload() @@ -85,10 +88,14 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? { } else { addHeader("Content-Type", "text/plain") } - setUploadDataProvider( - BodyUploadProvider(requestBody), - okHttpClient.dispatcher.executorService - ) + val provider: UploadDataProvider = if (requestBody.contentLength() > BUFFER_SIZE) { + LargeBodyUploadProvider(requestBody, okHttpClient.dispatcher.executorService) + } else { + BodyUploadProvider(requestBody) + } + provider.use { + this.setUploadDataProvider(it, okHttpClient.dispatcher.executorService) + } } diff --git a/app/src/app/java/io/legado/app/lib/cronet/LargeBodyUploadProvider.kt b/app/src/app/java/io/legado/app/lib/cronet/LargeBodyUploadProvider.kt new file mode 100644 index 000000000..058abfade --- /dev/null +++ b/app/src/app/java/io/legado/app/lib/cronet/LargeBodyUploadProvider.kt @@ -0,0 +1,75 @@ +package io.legado.app.lib.cronet + +import androidx.annotation.Keep +import okhttp3.RequestBody +import okio.BufferedSource +import okio.Pipe +import okio.buffer +import org.chromium.net.UploadDataProvider +import org.chromium.net.UploadDataSink +import java.io.IOException +import java.nio.ByteBuffer +import java.util.concurrent.ExecutorService + +/** + * 用于上传大型文件 + * + * @property body + * @property executorService + */ +@Keep +class LargeBodyUploadProvider( + private val body: RequestBody, + private val executorService: ExecutorService +) : UploadDataProvider(), AutoCloseable { + private val pipe = Pipe(BUFFER_SIZE.toLong()) + private var source: BufferedSource = pipe.source.buffer() + + @Volatile + private var filled: Boolean = false + override fun getLength(): Long { + return body.contentLength() + } + + override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) { + if (!filled) { + fillBuffer() + } + check(byteBuffer.hasRemaining()) { "Cronet passed a buffer with no bytes remaining" } + var read: Int + var bytesRead = 0 + while (bytesRead <= 0) { + read = source.read(byteBuffer) + bytesRead += read + } + uploadDataSink.onReadSucceeded(false) + } + + @Synchronized + private fun fillBuffer() { + executorService.submit { + try { + val writeSink = pipe.sink.buffer() + filled = true + body.writeTo(writeSink) + writeSink.flush() + } catch (e: IOException) { + e.printStackTrace() + } + + } + + } + + override fun rewind(p0: UploadDataSink?) { + check(body.isOneShot()) { "Okhttp RequestBody is OneShot" } + filled = false + fillBuffer() + } + + override fun close() { + pipe.cancel() + source.close() + super.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index dd281fae7..fec8f47c7 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -12,7 +12,7 @@ import splitties.init.appCtx @Suppress("MemberVisibilityCanBePrivate") object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { - val isGooglePlay = appCtx.channel == "google" + const val isGooglePlay = BuildConfig.isGoogle//appCtx.channel == "google" val isCronet = appCtx.getPrefBoolean(PreferKey.cronet) val useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias) var userAgent: String = getPrefUserAgent()