Cronet:一系列优化。

1,使用okio.Buffer 替代ByteArrayOuputStream 和 WritableByteChannel
2,优化Cookie处理,CronetInterceptor提供OkHttp的CookieJar 接口
3,Cronet处理失败时尝试回退到okhttp处理。
pull/1202/head
ag2s20150909 3 years ago
parent ad71cfac7a
commit 9784032806
  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. 14
      app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt
  5. 18
      app/src/main/java/io/legado/app/help/http/cronet/CronetUrlRequestCallback.kt
  6. 2
      app/src/main/java/io/legado/app/ui/dict/DictViewModel.kt

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

@ -1,23 +1,62 @@
package io.legado.app.help.http.cronet
import okhttp3.Call
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import io.legado.app.help.http.CookieStore
import okhttp3.*
import java.io.IOException
class CronetInterceptor : Interceptor {
class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
@Throws(IOException::class)
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)
private fun proceedWithCronet(request: Request, call: Call): Response {
val callback = CronetUrlRequestCallback(request, call)
val urlRequest = buildRequest(request, callback)
urlRequest.start()
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()
}
}

@ -142,9 +142,9 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
@Suppress("SameParameterValue")
private fun getUrlMd5(url: String): String? {
//这样在下载成功后,遇到无网条件下,只要版本未发生变化也能获取md5
if (md5 != null && md5!!.length == 32&& version==ImplVersion.getCronetVersion()) {
appCtx.putPrefString("soMd5",md5)
appCtx.putPrefString("soVersion",ImplVersion.getCronetVersion())
if (md5 != null && md5!!.length == 32 && version == ImplVersion.getCronetVersion()) {
appCtx.putPrefString("soMd5", md5)
appCtx.putPrefString("soVersion", ImplVersion.getCronetVersion())
return md5
}
val inputStream: InputStream
@ -159,11 +159,11 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
outputStream.write(buffer, 0, read)
outputStream.flush()
}
val tmd5=outputStream.toString()
val tmd5 = outputStream.toString()
//成功获取到md5后保存md5和版本
if(tmd5.length==32){
appCtx.putPrefString("soMd5",tmd5)
appCtx.putPrefString("soVersion",ImplVersion.getCronetVersion())
if (tmd5.length == 32) {
appCtx.putPrefString("soMd5", tmd5)
appCtx.putPrefString("soVersion", ImplVersion.getCronetVersion())
}
return tmd5

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

@ -16,7 +16,7 @@ class DictViewModel(application: Application) : BaseViewModel(application) {
fun dict(word: String) {
execute {
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
val jsoup = Jsoup.parse(body!!)
jsoup.body()

Loading…
Cancel
Save