在Android7.0 及以上使用jdk8的CompletableFuture替代旧的基于轮询的ConditionVariable

This commit is contained in:
ag2s20150909 2022-03-14 17:35:01 +08:00
parent aad42a44a0
commit 793afdd7ab
6 changed files with 191 additions and 74 deletions

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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'
}
}