mirror of
https://github.com/gedoor/legado.git
synced 2024-07-06 23:47:49 +08:00
在Android7.0 及以上使用jdk8的CompletableFuture替代旧的基于轮询的ConditionVariable
This commit is contained in:
parent
aad42a44a0
commit
793afdd7ab
@ -1,6 +1,5 @@
|
||||
package io.legado.app.help.http.cronet
|
||||
|
||||
import android.os.ConditionVariable
|
||||
import io.legado.app.help.http.okHttpClient
|
||||
import io.legado.app.utils.DebugLog
|
||||
import okhttp3.*
|
||||
@ -11,46 +10,28 @@ import okio.Buffer
|
||||
import org.chromium.net.CronetException
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
|
||||
class CronetRequestCallback @JvmOverloads internal constructor(
|
||||
private val originalRequest: Request,
|
||||
private val mCall: Call,
|
||||
eventListener: EventListener? = null,
|
||||
responseCallback: Callback? = null
|
||||
abstract class AbsCallBack(
|
||||
val originalRequest: Request,
|
||||
val mCall: Call,
|
||||
private val eventListener: EventListener? = null,
|
||||
private val responseCallback: Callback? = null
|
||||
|
||||
) : UrlRequest.Callback() {
|
||||
|
||||
private val eventListener: EventListener?
|
||||
private val responseCallback: Callback?
|
||||
val buffer = Buffer()
|
||||
|
||||
var mResponse: Response
|
||||
|
||||
var mException: IOException? = null
|
||||
private var followCount = 0
|
||||
private var mResponse: Response
|
||||
private var mException: IOException? = null
|
||||
private val mResponseCondition = ConditionVariable()
|
||||
private val mBuffer = Buffer()
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun waitForDone(urlRequest: UrlRequest): Response {
|
||||
//获取okhttp call的完整请求的超时时间
|
||||
val timeOutMs: Long = mCall.timeout().timeoutNanos() / 1000000
|
||||
if (timeOutMs > 0) {
|
||||
mResponseCondition.block(timeOutMs)
|
||||
} else {
|
||||
mResponseCondition.block()
|
||||
}
|
||||
//ConditionVariable 正常open或者超时open后,检查urlRequest是否完成
|
||||
if (!urlRequest.isDone) {
|
||||
urlRequest.cancel()
|
||||
mException = IOException("Cronet timeout after wait " + timeOutMs + "ms")
|
||||
}
|
||||
|
||||
if (mException != null) {
|
||||
throw mException as IOException
|
||||
}
|
||||
return this.mResponse
|
||||
}
|
||||
abstract fun waitForDone(urlRequest: UrlRequest): Response
|
||||
|
||||
|
||||
override fun onRedirectReceived(
|
||||
request: UrlRequest,
|
||||
@ -59,6 +40,10 @@ class CronetRequestCallback @JvmOverloads internal constructor(
|
||||
) {
|
||||
if (followCount > MAX_FOLLOW_COUNT) {
|
||||
request.cancel()
|
||||
mException = IOException("Too many redirect")
|
||||
}
|
||||
if (mCall.isCanceled()) {
|
||||
mException = IOException("Request Canceled")
|
||||
}
|
||||
followCount += 1
|
||||
val client = okHttpClient
|
||||
@ -66,26 +51,18 @@ class CronetRequestCallback @JvmOverloads internal constructor(
|
||||
request.followRedirect()
|
||||
} else if (!originalRequest.url.isHttps && newLocationUrl.startsWith("https://") && client.followSslRedirects) {
|
||||
request.followRedirect()
|
||||
} else if (client.followRedirects) {
|
||||
} else if (okHttpClient.followRedirects) {
|
||||
request.followRedirect()
|
||||
} else {
|
||||
mException = IOException("Too many redirect")
|
||||
request.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//UrlResponseInfo可能为null
|
||||
override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
|
||||
this.mResponse = responseFromResponse(this.mResponse, info)
|
||||
// 用于调试
|
||||
// val sb: StringBuilder = StringBuilder(info.url).append("\r\n")
|
||||
// sb.append("[Cached:").append(info.wasCached()).append("][StatusCode:")
|
||||
// .append(info.httpStatusCode).append("][StatusText:").append(info.httpStatusText)
|
||||
// .append("][Protocol:").append(info.negotiatedProtocol).append("][ByteCount:")
|
||||
// .append(info.receivedByteCount).append("]\r\n");
|
||||
// val httpHeaders=info.allHeadersAsList
|
||||
// httpHeaders.forEach { h ->
|
||||
// sb.append("[").append(h.key).append("]").append(h.value).append("\r\n");
|
||||
// }
|
||||
// Log.e("Cronet", sb.toString())
|
||||
//打印协议,用于调试
|
||||
DebugLog.i(javaClass.name, info.negotiatedProtocol)
|
||||
if (eventListener != null) {
|
||||
@ -95,6 +72,7 @@ class CronetRequestCallback @JvmOverloads internal constructor(
|
||||
request.read(ByteBuffer.allocateDirect(32 * 1024))
|
||||
}
|
||||
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun onReadCompleted(
|
||||
request: UrlRequest,
|
||||
@ -103,27 +81,34 @@ class CronetRequestCallback @JvmOverloads internal constructor(
|
||||
) {
|
||||
|
||||
|
||||
if (mCall.isCanceled()) {
|
||||
request.cancel()
|
||||
mException = IOException("Request Canceled")
|
||||
}
|
||||
|
||||
byteBuffer.flip()
|
||||
|
||||
try {
|
||||
mBuffer.write(byteBuffer)
|
||||
buffer.write(byteBuffer)
|
||||
} catch (e: IOException) {
|
||||
DebugLog.i(javaClass.name, "IOException during ByteBuffer read. Details: ", e)
|
||||
mException = IOException("IOException during ByteBuffer read. Details:", e)
|
||||
throw e
|
||||
}
|
||||
byteBuffer.clear()
|
||||
request.read(byteBuffer)
|
||||
}
|
||||
|
||||
|
||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
||||
eventListener?.responseBodyEnd(mCall, info.receivedByteCount)
|
||||
val contentType: MediaType? = (this.mResponse.header("content-type")
|
||||
?: "text/plain; charset=\"utf-8\"").toMediaTypeOrNull()
|
||||
val responseBody: ResponseBody =
|
||||
mBuffer.asResponseBody(contentType)
|
||||
buffer.asResponseBody(contentType)
|
||||
val newRequest = originalRequest.newBuilder().url(info.url).build()
|
||||
this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build()
|
||||
mResponseCondition.open()
|
||||
|
||||
eventListener?.callEnd(mCall)
|
||||
if (responseCallback != null) {
|
||||
try {
|
||||
@ -134,28 +119,34 @@ class CronetRequestCallback @JvmOverloads internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//UrlResponseInfo可能为null
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||
DebugLog.i(javaClass.name, error.message.toString())
|
||||
val msg = error.localizedMessage
|
||||
val e = IOException(msg?.substring(msg.indexOf("net::")), error)
|
||||
mException = e
|
||||
mResponseCondition.open()
|
||||
|
||||
this.eventListener?.callFailed(mCall, e)
|
||||
responseCallback?.onFailure(mCall, e)
|
||||
mException = IOException(error.message, error)
|
||||
this.eventListener?.callFailed(mCall, error)
|
||||
responseCallback?.onFailure(mCall, error)
|
||||
}
|
||||
|
||||
override fun onCanceled(request: UrlRequest, info: UrlResponseInfo?) {
|
||||
mResponseCondition.open()
|
||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||
super.onCanceled(request, info)
|
||||
this.eventListener?.callEnd(mCall)
|
||||
mException = IOException("Cronet Request Canceled")
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
mResponse = Response.Builder()
|
||||
.sentRequestAtMillis(System.currentTimeMillis())
|
||||
.request(originalRequest)
|
||||
.protocol(Protocol.HTTP_1_0)
|
||||
.code(0)
|
||||
.message("")
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_FOLLOW_COUNT = 20
|
||||
|
||||
const val MAX_FOLLOW_COUNT = 20
|
||||
private fun protocolFromNegotiatedProtocol(responseInfo: UrlResponseInfo): Protocol {
|
||||
val negotiatedProtocol = responseInfo.negotiatedProtocol.lowercase(Locale.getDefault())
|
||||
return when {
|
||||
@ -217,16 +208,4 @@ class CronetRequestCallback @JvmOverloads internal constructor(
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
this.mResponse = Response.Builder()
|
||||
.sentRequestAtMillis(System.currentTimeMillis())
|
||||
.request(originalRequest)
|
||||
.protocol(Protocol.HTTP_1_0)
|
||||
.code(0)
|
||||
.message("")
|
||||
.build()
|
||||
this.responseCallback = responseCallback
|
||||
this.eventListener = eventListener
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package io.legado.app.help.http.cronet
|
||||
|
||||
import android.os.Build
|
||||
import io.legado.app.help.http.CookieStore
|
||||
import io.legado.app.utils.printOnDebug
|
||||
import okhttp3.*
|
||||
@ -42,10 +43,14 @@ class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
|
||||
}
|
||||
|
||||
private fun proceedWithCronet(request: Request, call: Call): Response? {
|
||||
val callback = CronetRequestCallback(request, call)
|
||||
buildRequest(request, callback)?.let {
|
||||
val callBack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
NewCallBack(request, call)
|
||||
} else {
|
||||
OldCallback(request, call)
|
||||
}
|
||||
buildRequest(request, callBack)?.let {
|
||||
it.start()
|
||||
return callback.waitForDone(it)
|
||||
return callBack.waitForDone(it)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
|
||||
private var cpuAbi: String? = null
|
||||
private var md5: String
|
||||
var download = false
|
||||
@Volatile
|
||||
private var cacheInstall = false
|
||||
|
||||
init {
|
||||
|
@ -0,0 +1,66 @@
|
||||
package io.legado.app.help.http.cronet
|
||||
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.chromium.net.CronetException
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
class NewCallBack(originalRequest: Request, mCall: Call) : AbsCallBack(originalRequest, mCall) {
|
||||
private val responseFuture = CompletableFuture<Response>()
|
||||
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun waitForDone(urlRequest: UrlRequest): Response {
|
||||
return responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS)
|
||||
}
|
||||
|
||||
override fun onRedirectReceived(
|
||||
request: UrlRequest,
|
||||
info: UrlResponseInfo,
|
||||
newLocationUrl: String
|
||||
) {
|
||||
super.onRedirectReceived(request, info, newLocationUrl)
|
||||
if (mException != null) {
|
||||
responseFuture.completeExceptionally(mException)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onReadCompleted(
|
||||
request: UrlRequest,
|
||||
info: UrlResponseInfo,
|
||||
byteBuffer: ByteBuffer
|
||||
) {
|
||||
super.onReadCompleted(request, info, byteBuffer)
|
||||
if (mException != null) {
|
||||
responseFuture.completeExceptionally(mException)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
||||
super.onSucceeded(request, info)
|
||||
responseFuture.complete(mResponse)
|
||||
}
|
||||
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||
super.onFailed(request, info, error)
|
||||
responseFuture.completeExceptionally(mException)
|
||||
}
|
||||
|
||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||
super.onCanceled(request, info)
|
||||
responseFuture.completeExceptionally(mException)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package io.legado.app.help.http.cronet
|
||||
|
||||
import android.os.ConditionVariable
|
||||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.chromium.net.CronetException
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalRequest, mCall) {
|
||||
|
||||
private val mResponseCondition = ConditionVariable()
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun waitForDone(urlRequest: UrlRequest): Response {
|
||||
//获取okhttp call的完整请求的超时时间
|
||||
val timeOutMs: Long = mCall.timeout().timeoutNanos() / 1000000
|
||||
if (timeOutMs > 0) {
|
||||
mResponseCondition.block(timeOutMs)
|
||||
} else {
|
||||
mResponseCondition.block()
|
||||
}
|
||||
//ConditionVariable 正常open或者超时open后,检查urlRequest是否完成
|
||||
if (!urlRequest.isDone) {
|
||||
urlRequest.cancel()
|
||||
mException = IOException("Cronet timeout after wait " + timeOutMs + "ms")
|
||||
}
|
||||
|
||||
if (mException != null) {
|
||||
throw mException as IOException
|
||||
}
|
||||
return this.mResponse
|
||||
}
|
||||
|
||||
|
||||
override fun onReadCompleted(
|
||||
request: UrlRequest,
|
||||
info: UrlResponseInfo,
|
||||
byteBuffer: ByteBuffer
|
||||
) {
|
||||
super.onReadCompleted(request, info, byteBuffer)
|
||||
if (mException != null) {
|
||||
mResponseCondition.open()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
||||
super.onSucceeded(request, info)
|
||||
mResponseCondition.open()
|
||||
}
|
||||
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||
super.onFailed(request, info, error)
|
||||
mResponseCondition.open()
|
||||
}
|
||||
|
||||
|
||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||
super.onCanceled(request, info)
|
||||
mResponseCondition.open()
|
||||
}
|
||||
|
||||
}
|
@ -16,7 +16,7 @@ buildscript {
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.4'
|
||||
classpath 'de.undercouch:gradle-download-task:4.1.2'
|
||||
classpath 'de.undercouch:gradle-download-task:5.0.1'
|
||||
classpath 'com.google.gms:google-services:4.3.10'
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user