Просмотр исходного кода

Merge pull request #2731 from ag2s20150909/master

Cronet支持上传大文件
kunfei 3 лет назад
Родитель
Сommit
814b8590d6

+ 2 - 0
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 {

+ 15 - 2
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()
     }
 

+ 4 - 1
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()
+            }
 
 
         }

+ 11 - 4
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)
+            }
 
         }
 

+ 75 - 0
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()
+    }
+}

+ 1 - 1
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()