Merge pull request #1202 from ag2s20150909/master

Cronet:优化。
pull/1203/head
kunfei 3 years ago committed by GitHub
commit 374a57b390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  2. 36
      app/src/main/java/io/legado/app/help/http/cronet/CronetHelper.kt
  3. 51
      app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt
  4. 18
      app/src/main/java/io/legado/app/help/http/cronet/CronetUrlRequestCallback.kt
  5. 2
      app/src/main/java/io/legado/app/ui/dict/DictViewModel.kt

@ -45,7 +45,8 @@ val okHttpClient: OkHttpClient by lazy {
chain.proceed(request) chain.proceed(request)
}) })
if (AppConfig.isCronet && CronetLoader.install()) { if (AppConfig.isCronet && CronetLoader.install()) {
builder.addInterceptor(CronetInterceptor()) //提供CookieJar 用于同步Cookie
builder.addInterceptor(CronetInterceptor(null))
} }

@ -1,6 +1,6 @@
package io.legado.app.help.http.cronet package io.legado.app.help.http.cronet
import io.legado.app.help.http.CookieStore import android.util.Log
import okhttp3.Headers import okhttp3.Headers
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.Request import okhttp3.Request
@ -9,29 +9,22 @@ import org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISK
import org.chromium.net.ExperimentalCronetEngine import org.chromium.net.ExperimentalCronetEngine
import org.chromium.net.UploadDataProviders import org.chromium.net.UploadDataProviders
import org.chromium.net.UrlRequest import org.chromium.net.UrlRequest
import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory
import splitties.init.appCtx import splitties.init.appCtx
import java.net.URL
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.concurrent.Executors import java.util.concurrent.Executors
val executor: Executor by lazy { Executors.newSingleThreadExecutor() } val executor: Executor by lazy { Executors.newCachedThreadPool() }
val cronetEngine: ExperimentalCronetEngine by lazy { val cronetEngine: ExperimentalCronetEngine by lazy {
CronetLoader.preDownload() CronetLoader.preDownload()
val builder = ExperimentalCronetEngine.Builder(appCtx) val builder = ExperimentalCronetEngine.Builder(appCtx)
//设置自定义so库加载 .setLibraryLoader(CronetLoader)//设置自定义so库加载
.setLibraryLoader(CronetLoader) .setStoragePath(appCtx.externalCacheDir?.absolutePath)//设置缓存路径
//设置缓存路径 .enableHttpCache(HTTP_CACHE_DISK, (1024 * 1024 * 50))//设置缓存模式
.setStoragePath(appCtx.externalCacheDir?.absolutePath) .enableQuic(true)//设置支持http/3
//设置缓存模式 .enableHttp2(true) //设置支持http/2
.enableHttpCache(HTTP_CACHE_DISK, (1024 * 1024 * 50))
//设置支持http/3
.enableQuic(true)
//设置支持http/2
.enableHttp2(true)
.enablePublicKeyPinningBypassForLocalTrustAnchors(true) .enablePublicKeyPinningBypassForLocalTrustAnchors(true)
//.enableNetworkQualityEstimator(true) //.enableNetworkQualityEstimator(true)
@ -39,7 +32,9 @@ val cronetEngine: ExperimentalCronetEngine by lazy {
builder.enableBrotli(true) builder.enableBrotli(true)
//builder.setExperimentalOptions("{\"quic_version\": \"h3-29\"}") //builder.setExperimentalOptions("{\"quic_version\": \"h3-29\"}")
val engine = builder.build() val engine = builder.build()
URL.setURLStreamHandlerFactory(CronetURLStreamHandlerFactory(engine)) Log.d("Cronet", "Cronet Version:" + engine.versionString)
//这会导致Jsoup的网络请求出现问题,暂时不接管系统URL
//URL.setURLStreamHandlerFactory(CronetURLStreamHandlerFactory(engine))
return@lazy engine return@lazy engine
} }
@ -49,17 +44,10 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest {
val url = request.url.toString() val url = request.url.toString()
val requestBuilder = cronetEngine.newUrlRequestBuilder(url, callback, executor) val requestBuilder = cronetEngine.newUrlRequestBuilder(url, callback, executor)
requestBuilder.setHttpMethod(request.method) requestBuilder.setHttpMethod(request.method)
val cookie = CookieStore.getCookie(url)
if (cookie.length > 1) {
requestBuilder.addHeader("Cookie", cookie)
}
val headers: Headers = request.headers val headers: Headers = request.headers
headers.forEachIndexed { index, _ -> headers.forEachIndexed { index, _ ->
val name = headers.name(index) requestBuilder.addHeader(headers.name(index), headers.value(index))
if (!name.equals("Keep-Alive", true)) {
requestBuilder.addHeader(name, headers.value(index))
}
} }
val requestBody = request.body val requestBody = request.body

@ -1,23 +1,62 @@
package io.legado.app.help.http.cronet package io.legado.app.help.http.cronet
import okhttp3.Call import io.legado.app.help.http.CookieStore
import okhttp3.Interceptor import okhttp3.*
import okhttp3.Request
import okhttp3.Response
import java.io.IOException import java.io.IOException
class CronetInterceptor : Interceptor { class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
@Throws(IOException::class) @Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
return proceedWithCronet(chain.request(), chain.call()) val original: Request = chain.request()
val builder: Request.Builder = original.newBuilder()
//Cronet未初始化
return if (!CronetLoader.install()) {
chain.proceed(original)
} else try {
//移除Keep-Alive,手动设置会导致400 BadRequest
builder.removeHeader("Keep-Alive")
val cookieStr = getCookie(original.url)
//设置Cookie
if (cookieStr.length > 3) {
builder.header("Cookie", cookieStr)
}
val new = builder.build()
val response: Response = proceedWithCronet(new, chain.call())
//从Response 中保存Cookie到CookieJar
cookieJar?.saveFromResponse(new.url, Cookie.parseAll(new.url, response.headers))
response
} catch (e: Exception) {
//遇到Cronet处理有问题时的情况,如证书过期等等,回退到okhttp处理
chain.proceed(original)
}
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun proceedWithCronet(request: Request, call: Call): Response { private fun proceedWithCronet(request: Request, call: Call): Response {
val callback = CronetUrlRequestCallback(request, call) val callback = CronetUrlRequestCallback(request, call)
val urlRequest = buildRequest(request, callback) val urlRequest = buildRequest(request, callback)
urlRequest.start() urlRequest.start()
return callback.waitForDone() return callback.waitForDone()
} }
private fun getCookie(url: HttpUrl): String {
val sb = StringBuilder()
//处理从 Cookjar 获取到的Cookies
if (cookieJar != null) {
val cookies = cookieJar.loadForRequest(url)
for (cookie in cookies) {
sb.append(cookie.name).append("=").append(cookie.value).append("; ")
}
}
//处理自定义的Cookie
val cookie = CookieStore.getCookie(url.toString())
if (cookie.length > 3) {
sb.append(cookie)
}
return sb.toString()
}
} }

@ -6,14 +6,13 @@ import io.legado.app.help.http.okHttpClient
import okhttp3.* import okhttp3.*
import okhttp3.EventListener import okhttp3.EventListener
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.asResponseBody
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.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.channels.Channels
import java.util.* import java.util.*
class CronetUrlRequestCallback @JvmOverloads internal constructor( class CronetUrlRequestCallback @JvmOverloads internal constructor(
@ -29,8 +28,7 @@ class CronetUrlRequestCallback @JvmOverloads internal constructor(
private var mResponse: Response private var mResponse: Response
private var mException: IOException? = null private var mException: IOException? = null
private val mResponseCondition = ConditionVariable() private val mResponseCondition = ConditionVariable()
private val mBytesReceived = ByteArrayOutputStream() private val mBuffer = Buffer()
private val mReceiveChannel = Channels.newChannel(mBytesReceived)
@Throws(IOException::class) @Throws(IOException::class)
fun waitForDone(): Response { fun waitForDone(): Response {
@ -64,6 +62,7 @@ class CronetUrlRequestCallback @JvmOverloads internal constructor(
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") // val sb: StringBuilder = StringBuilder(info.url).append("\r\n")
// sb.append("[Cached:").append(info.wasCached()).append("][StatusCode:") // sb.append("[Cached:").append(info.wasCached()).append("][StatusCode:")
// .append(info.httpStatusCode).append("][StatusText:").append(info.httpStatusText) // .append(info.httpStatusCode).append("][StatusText:").append(info.httpStatusText)
@ -74,6 +73,8 @@ class CronetUrlRequestCallback @JvmOverloads internal constructor(
// sb.append("[").append(h.key).append("]").append(h.value).append("\r\n"); // sb.append("[").append(h.key).append("]").append(h.value).append("\r\n");
// } // }
// Log.e("Cronet", sb.toString()) // Log.e("Cronet", sb.toString())
//打印协议,用于调试
Log.e("Cronet", info.negotiatedProtocol)
if (eventListener != null) { if (eventListener != null) {
eventListener.responseHeadersEnd(mCall, this.mResponse) eventListener.responseHeadersEnd(mCall, this.mResponse)
eventListener.responseBodyStart(mCall) eventListener.responseBodyStart(mCall)
@ -89,7 +90,8 @@ class CronetUrlRequestCallback @JvmOverloads internal constructor(
) { ) {
byteBuffer.flip() byteBuffer.flip()
try { try {
mReceiveChannel.write(byteBuffer) //mReceiveChannel.write(byteBuffer)
mBuffer.write(byteBuffer)
} catch (e: IOException) { } catch (e: IOException) {
Log.i(TAG, "IOException during ByteBuffer read. Details: ", e) Log.i(TAG, "IOException during ByteBuffer read. Details: ", e)
throw e throw e
@ -102,8 +104,10 @@ class CronetUrlRequestCallback @JvmOverloads internal constructor(
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 =
// mBytesReceived.toByteArray().toResponseBody(contentType)
val responseBody: ResponseBody = val responseBody: ResponseBody =
mBytesReceived.toByteArray().toResponseBody(contentType) mBuffer.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() mResponseCondition.open()

@ -16,7 +16,7 @@ class DictViewModel(application: Application) : BaseViewModel(application) {
fun dict(word: String) { fun dict(word: String) {
execute { execute {
val body = okHttpClient.newCallStrResponse { val body = okHttpClient.newCallStrResponse {
get("http://apii.dict.cn/mini.php", mapOf(Pair("q", word))) get("https://apii.dict.cn/mini.php", mapOf(Pair("q", word)))
}.body }.body
val jsoup = Jsoup.parse(body!!) val jsoup = Jsoup.parse(body!!)
jsoup.body() jsoup.body()

Loading…
Cancel
Save