Merge pull request #1705 from ag2s20150909/master

在Android7.0 及以上使用jdk8的CompletableFuture替代旧的基于轮询的ConditionVariable
pull/1710/head
kunfei 3 years ago committed by GitHub
commit e2c13c92ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 121
      app/src/main/java/io/legado/app/help/http/cronet/AbsCallBack.kt
  2. 11
      app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt
  3. 1
      app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt
  4. 66
      app/src/main/java/io/legado/app/help/http/cronet/NewCallBack.kt
  5. 66
      app/src/main/java/io/legado/app/help/http/cronet/OldCallback.kt
  6. 2
      build.gradle

@ -1,6 +1,5 @@
package io.legado.app.help.http.cronet package io.legado.app.help.http.cronet
import android.os.ConditionVariable
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
import io.legado.app.utils.DebugLog import io.legado.app.utils.DebugLog
import okhttp3.* import okhttp3.*
@ -11,46 +10,28 @@ import okio.Buffer
import org.chromium.net.CronetException import org.chromium.net.CronetException
import org.chromium.net.UrlRequest import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo import org.chromium.net.UrlResponseInfo
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.* import java.util.*
class CronetRequestCallback @JvmOverloads internal constructor( abstract class AbsCallBack(
private val originalRequest: Request, val originalRequest: Request,
private val mCall: Call, val mCall: Call,
eventListener: EventListener? = null, private val eventListener: EventListener? = null,
responseCallback: Callback? = null private val responseCallback: Callback? = null
) : UrlRequest.Callback() { ) : UrlRequest.Callback() {
private val eventListener: EventListener? val buffer = Buffer()
private val responseCallback: Callback?
var mResponse: Response
var mException: IOException? = null
private var followCount = 0 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 abstract fun waitForDone(urlRequest: UrlRequest): Response
}
return this.mResponse
}
override fun onRedirectReceived( override fun onRedirectReceived(
request: UrlRequest, request: UrlRequest,
@ -59,6 +40,10 @@ class CronetRequestCallback @JvmOverloads internal constructor(
) { ) {
if (followCount > MAX_FOLLOW_COUNT) { if (followCount > MAX_FOLLOW_COUNT) {
request.cancel() request.cancel()
mException = IOException("Too many redirect")
}
if (mCall.isCanceled()) {
mException = IOException("Request Canceled")
} }
followCount += 1 followCount += 1
val client = okHttpClient val client = okHttpClient
@ -66,26 +51,18 @@ class CronetRequestCallback @JvmOverloads internal constructor(
request.followRedirect() request.followRedirect()
} else if (!originalRequest.url.isHttps && newLocationUrl.startsWith("https://") && client.followSslRedirects) { } else if (!originalRequest.url.isHttps && newLocationUrl.startsWith("https://") && client.followSslRedirects) {
request.followRedirect() request.followRedirect()
} else if (client.followRedirects) { } else if (okHttpClient.followRedirects) {
request.followRedirect() request.followRedirect()
} else { } else {
mException = IOException("Too many redirect")
request.cancel() request.cancel()
} }
} }
//UrlResponseInfo可能为null
override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) { override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
this.mResponse = responseFromResponse(this.mResponse, info) 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) DebugLog.i(javaClass.name, info.negotiatedProtocol)
if (eventListener != null) { if (eventListener != null) {
@ -95,6 +72,7 @@ class CronetRequestCallback @JvmOverloads internal constructor(
request.read(ByteBuffer.allocateDirect(32 * 1024)) request.read(ByteBuffer.allocateDirect(32 * 1024))
} }
@Throws(Exception::class) @Throws(Exception::class)
override fun onReadCompleted( override fun onReadCompleted(
request: UrlRequest, request: UrlRequest,
@ -103,27 +81,34 @@ class CronetRequestCallback @JvmOverloads internal constructor(
) { ) {
if (mCall.isCanceled()) {
request.cancel()
mException = IOException("Request Canceled")
}
byteBuffer.flip() byteBuffer.flip()
try { try {
mBuffer.write(byteBuffer) buffer.write(byteBuffer)
} catch (e: IOException) { } catch (e: IOException) {
DebugLog.i(javaClass.name, "IOException during ByteBuffer read. Details: ", e) DebugLog.i(javaClass.name, "IOException during ByteBuffer read. Details: ", e)
mException = IOException("IOException during ByteBuffer read. Details:", e)
throw e throw e
} }
byteBuffer.clear() byteBuffer.clear()
request.read(byteBuffer) request.read(byteBuffer)
} }
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) { override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
eventListener?.responseBodyEnd(mCall, info.receivedByteCount) eventListener?.responseBodyEnd(mCall, info.receivedByteCount)
val contentType: MediaType? = (this.mResponse.header("content-type") val contentType: MediaType? = (this.mResponse.header("content-type")
?: "text/plain; charset=\"utf-8\"").toMediaTypeOrNull() ?: "text/plain; charset=\"utf-8\"").toMediaTypeOrNull()
val responseBody: ResponseBody = val responseBody: ResponseBody =
mBuffer.asResponseBody(contentType) buffer.asResponseBody(contentType)
val newRequest = originalRequest.newBuilder().url(info.url).build() val newRequest = originalRequest.newBuilder().url(info.url).build()
this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build() this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build()
mResponseCondition.open()
eventListener?.callEnd(mCall) eventListener?.callEnd(mCall)
if (responseCallback != null) { if (responseCallback != null) {
try { try {
@ -134,28 +119,34 @@ class CronetRequestCallback @JvmOverloads internal constructor(
} }
} }
//UrlResponseInfo可能为null //UrlResponseInfo可能为null
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) { override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
DebugLog.i(javaClass.name, error.message.toString()) DebugLog.i(javaClass.name, error.message.toString())
val msg = error.localizedMessage mException = IOException(error.message, error)
val e = IOException(msg?.substring(msg.indexOf("net::")), error) this.eventListener?.callFailed(mCall, error)
mException = e responseCallback?.onFailure(mCall, error)
mResponseCondition.open()
this.eventListener?.callFailed(mCall, e)
responseCallback?.onFailure(mCall, e)
} }
override fun onCanceled(request: UrlRequest, info: UrlResponseInfo?) { override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
mResponseCondition.open() super.onCanceled(request, info)
this.eventListener?.callEnd(mCall) 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 { companion object {
private const val MAX_FOLLOW_COUNT = 20 const val MAX_FOLLOW_COUNT = 20
private fun protocolFromNegotiatedProtocol(responseInfo: UrlResponseInfo): Protocol { private fun protocolFromNegotiatedProtocol(responseInfo: UrlResponseInfo): Protocol {
val negotiatedProtocol = responseInfo.negotiatedProtocol.lowercase(Locale.getDefault()) val negotiatedProtocol = responseInfo.negotiatedProtocol.lowercase(Locale.getDefault())
return when { return when {
@ -217,16 +208,4 @@ class CronetRequestCallback @JvmOverloads internal constructor(
.build() .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 package io.legado.app.help.http.cronet
import android.os.Build
import io.legado.app.help.http.CookieStore import io.legado.app.help.http.CookieStore
import io.legado.app.utils.printOnDebug import io.legado.app.utils.printOnDebug
import okhttp3.* import okhttp3.*
@ -42,10 +43,14 @@ class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
} }
private fun proceedWithCronet(request: Request, call: Call): Response? { private fun proceedWithCronet(request: Request, call: Call): Response? {
val callback = CronetRequestCallback(request, call) val callBack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
buildRequest(request, callback)?.let { NewCallBack(request, call)
} else {
OldCallback(request, call)
}
buildRequest(request, callBack)?.let {
it.start() it.start()
return callback.waitForDone(it) return callBack.waitForDone(it)
} }
return null return null
} }

@ -34,6 +34,7 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
private var cpuAbi: String? = null private var cpuAbi: String? = null
private var md5: String private var md5: String
var download = false var download = false
@Volatile
private var cacheInstall = false private var cacheInstall = false
init { 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 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.4' 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' classpath 'com.google.gms:google-services:4.3.10'
} }
} }

Loading…
Cancel
Save