pull/1352/head
parent
1a5f94e43a
commit
995c9098db
@ -1,236 +0,0 @@ |
||||
package io.legado.app.help.http |
||||
|
||||
import android.annotation.SuppressLint |
||||
import android.os.Handler |
||||
import android.os.Looper |
||||
import android.os.Message |
||||
import android.text.TextUtils |
||||
import android.webkit.CookieManager |
||||
import android.webkit.WebSettings |
||||
import android.webkit.WebView |
||||
import android.webkit.WebViewClient |
||||
import io.legado.app.constant.AppConst |
||||
import org.apache.commons.text.StringEscapeUtils |
||||
import splitties.init.appCtx |
||||
import java.lang.ref.WeakReference |
||||
|
||||
|
||||
class AjaxWebView { |
||||
var callback: Callback? = null |
||||
private var mHandler: AjaxHandler = AjaxHandler(this) |
||||
|
||||
class AjaxHandler(private val ajaxWebView: AjaxWebView) : Handler(Looper.getMainLooper()) { |
||||
|
||||
private var mWebView: WebView? = null |
||||
|
||||
override fun handleMessage(msg: Message) { |
||||
val params: AjaxParams |
||||
when (msg.what) { |
||||
MSG_AJAX_START -> { |
||||
params = msg.obj as AjaxParams |
||||
mWebView = createAjaxWebView(params, this) |
||||
} |
||||
MSG_SNIFF_START -> { |
||||
params = msg.obj as AjaxParams |
||||
mWebView = createAjaxWebView(params, this) |
||||
} |
||||
MSG_SUCCESS -> { |
||||
ajaxWebView.callback?.onResult(msg.obj as StrResponse) |
||||
destroyWebView() |
||||
} |
||||
MSG_ERROR -> { |
||||
ajaxWebView.callback?.onError(msg.obj as Throwable) |
||||
destroyWebView() |
||||
} |
||||
} |
||||
} |
||||
|
||||
@SuppressLint("SetJavaScriptEnabled", "JavascriptInterface") |
||||
fun createAjaxWebView(params: AjaxParams, handler: Handler): WebView { |
||||
val webView = WebView(appCtx) |
||||
val settings = webView.settings |
||||
settings.javaScriptEnabled = true |
||||
settings.domStorageEnabled = true |
||||
settings.blockNetworkImage = true |
||||
settings.userAgentString = params.userAgent |
||||
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW |
||||
if (params.isSniff) { |
||||
webView.webViewClient = SnifferWebClient(params, handler) |
||||
} else { |
||||
webView.webViewClient = HtmlWebViewClient(params, handler) |
||||
} |
||||
when (params.requestMethod) { |
||||
RequestMethod.POST -> params.postData?.let { |
||||
webView.postUrl(params.url, it) |
||||
} |
||||
RequestMethod.GET -> params.headerMap?.let { |
||||
webView.loadUrl(params.url, it) |
||||
} |
||||
} |
||||
return webView |
||||
} |
||||
|
||||
private fun destroyWebView() { |
||||
mWebView?.destroy() |
||||
mWebView = null |
||||
} |
||||
} |
||||
|
||||
fun load(params: AjaxParams) { |
||||
if (params.sourceRegex != "") { |
||||
mHandler.obtainMessage(MSG_SNIFF_START, params) |
||||
.sendToTarget() |
||||
} else { |
||||
mHandler.obtainMessage(MSG_AJAX_START, params) |
||||
.sendToTarget() |
||||
} |
||||
} |
||||
|
||||
fun destroyWebView() { |
||||
mHandler.obtainMessage(DESTROY_WEB_VIEW) |
||||
} |
||||
|
||||
class AjaxParams(val url: String) { |
||||
var tag: String? = null |
||||
var requestMethod = RequestMethod.GET |
||||
var postData: ByteArray? = null |
||||
var headerMap: Map<String, String>? = null |
||||
var sourceRegex: String? = null |
||||
var javaScript: String? = null |
||||
|
||||
fun getJs(): String { |
||||
javaScript?.let { |
||||
if (it.isNotEmpty()) { |
||||
return it |
||||
} |
||||
} |
||||
return JS |
||||
} |
||||
|
||||
val userAgent: String? |
||||
get() = this.headerMap?.get(AppConst.UA_NAME) |
||||
|
||||
val isSniff: Boolean |
||||
get() = !TextUtils.isEmpty(sourceRegex) |
||||
|
||||
fun setCookie(url: String) { |
||||
tag?.let { |
||||
val cookie = CookieManager.getInstance().getCookie(url) |
||||
CookieStore.setCookie(it, cookie) |
||||
} |
||||
} |
||||
|
||||
fun hasJavaScript(): Boolean { |
||||
return !TextUtils.isEmpty(javaScript) |
||||
} |
||||
|
||||
fun clearJavaScript() { |
||||
javaScript = null |
||||
} |
||||
|
||||
} |
||||
|
||||
private class HtmlWebViewClient( |
||||
private val params: AjaxParams, |
||||
private val handler: Handler |
||||
) : WebViewClient() { |
||||
|
||||
override fun onPageFinished(view: WebView, url: String) { |
||||
params.setCookie(url) |
||||
val runnable = EvalJsRunnable(view, url, params.getJs(), handler) |
||||
handler.postDelayed(runnable, 1000) |
||||
} |
||||
|
||||
} |
||||
|
||||
private class EvalJsRunnable( |
||||
webView: WebView, |
||||
private val url: String, |
||||
private val mJavaScript: String, |
||||
private val handler: Handler |
||||
) : Runnable { |
||||
var retry = 0 |
||||
private val mWebView: WeakReference<WebView> = WeakReference(webView) |
||||
override fun run() { |
||||
mWebView.get()?.evaluateJavascript(mJavaScript) { |
||||
if (it.isNotEmpty() && it != "null") { |
||||
val content = StringEscapeUtils.unescapeJson(it) |
||||
.replace("^\"|\"$".toRegex(), "") |
||||
try { |
||||
val response = StrResponse(url, content) |
||||
handler.obtainMessage(MSG_SUCCESS, response).sendToTarget() |
||||
} catch (e: Exception) { |
||||
handler.obtainMessage(MSG_ERROR, e).sendToTarget() |
||||
} |
||||
handler.removeCallbacks(this) |
||||
return@evaluateJavascript |
||||
} |
||||
if (retry > 30) { |
||||
handler.obtainMessage(MSG_ERROR, Exception("js执行超时")) |
||||
.sendToTarget() |
||||
handler.removeCallbacks(this) |
||||
return@evaluateJavascript |
||||
} |
||||
retry++ |
||||
handler.removeCallbacks(this) |
||||
handler.postDelayed(this, 1000) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private class SnifferWebClient( |
||||
private val params: AjaxParams, |
||||
private val handler: Handler |
||||
) : WebViewClient() { |
||||
|
||||
override fun onLoadResource(view: WebView, url: String) { |
||||
params.sourceRegex?.let { |
||||
if (url.matches(it.toRegex())) { |
||||
try { |
||||
val response = StrResponse(params.url, url) |
||||
handler.obtainMessage(MSG_SUCCESS, response).sendToTarget() |
||||
} catch (e: Exception) { |
||||
handler.obtainMessage(MSG_ERROR, e).sendToTarget() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onPageFinished(view: WebView, url: String) { |
||||
params.setCookie(url) |
||||
if (params.hasJavaScript()) { |
||||
evaluateJavascript(view, params.javaScript) |
||||
params.clearJavaScript() |
||||
} |
||||
} |
||||
|
||||
private fun evaluateJavascript(webView: WebView, javaScript: String?) { |
||||
val runnable = LoadJsRunnable(webView, javaScript) |
||||
handler.postDelayed(runnable, 1000L) |
||||
} |
||||
} |
||||
|
||||
private class LoadJsRunnable( |
||||
webView: WebView, |
||||
private val mJavaScript: String? |
||||
) : Runnable { |
||||
private val mWebView: WeakReference<WebView> = WeakReference(webView) |
||||
override fun run() { |
||||
mWebView.get()?.loadUrl("javascript:${mJavaScript ?: ""}") |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
const val MSG_AJAX_START = 0 |
||||
const val MSG_SNIFF_START = 1 |
||||
const val MSG_SUCCESS = 2 |
||||
const val MSG_ERROR = 3 |
||||
const val DESTROY_WEB_VIEW = 4 |
||||
const val JS = "document.documentElement.outerHTML" |
||||
} |
||||
|
||||
abstract class Callback { |
||||
abstract fun onResult(response: StrResponse) |
||||
abstract fun onError(error: Throwable) |
||||
} |
||||
} |
@ -0,0 +1,208 @@ |
||||
package io.legado.app.help.http |
||||
|
||||
import android.annotation.SuppressLint |
||||
import android.os.Handler |
||||
import android.os.Looper |
||||
import android.webkit.CookieManager |
||||
import android.webkit.WebSettings |
||||
import android.webkit.WebView |
||||
import android.webkit.WebViewClient |
||||
import io.legado.app.constant.AppConst |
||||
import io.legado.app.model.NoStackTraceException |
||||
import io.legado.app.utils.runOnUI |
||||
import kotlinx.coroutines.* |
||||
import org.apache.commons.text.StringEscapeUtils |
||||
import splitties.init.appCtx |
||||
import java.lang.ref.WeakReference |
||||
import kotlin.coroutines.CoroutineContext |
||||
import kotlin.coroutines.resume |
||||
|
||||
|
||||
class BackstageWebView( |
||||
private val url: String? = null, |
||||
private val html: String? = null, |
||||
private val encode: String? = null, |
||||
private val tag: String? = null, |
||||
private val headerMap: Map<String, String>? = null, |
||||
private val sourceRegex: String? = null, |
||||
private val javaScript: String? = null, |
||||
) : CoroutineScope { |
||||
private lateinit var job: Job |
||||
private val mHandler = Handler(Looper.getMainLooper()) |
||||
private var callback: Callback? = null |
||||
private var mWebView: WebView? = null |
||||
|
||||
override val coroutineContext: CoroutineContext |
||||
get() = Dispatchers.Main + job |
||||
|
||||
suspend fun getStrResponse(): StrResponse = suspendCancellableCoroutine { block -> |
||||
job = Job() |
||||
block.invokeOnCancellation { |
||||
destroy() |
||||
} |
||||
callback = object : BackstageWebView.Callback() { |
||||
override fun onResult(response: StrResponse) { |
||||
if (!block.isCompleted) |
||||
block.resume(response) |
||||
} |
||||
|
||||
override fun onError(error: Throwable) { |
||||
if (!block.isCompleted) |
||||
block.cancel(error) |
||||
} |
||||
} |
||||
runOnUI { |
||||
load() |
||||
} |
||||
} |
||||
|
||||
private fun getEncoding(): String { |
||||
return encode ?: "utf-8" |
||||
} |
||||
|
||||
private fun load() { |
||||
mWebView = createWebView().apply { |
||||
when { |
||||
!html.isNullOrEmpty() -> if (url.isNullOrEmpty()) { |
||||
loadData(html, "text/html", getEncoding()) |
||||
} else { |
||||
loadDataWithBaseURL(url, html, "text/html", getEncoding(), url) |
||||
} |
||||
else -> if (headerMap == null) { |
||||
loadUrl(url!!) |
||||
} else { |
||||
loadUrl(url!!, headerMap) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@SuppressLint("SetJavaScriptEnabled", "JavascriptInterface") |
||||
private fun createWebView(): WebView { |
||||
val webView = WebView(appCtx) |
||||
val settings = webView.settings |
||||
settings.javaScriptEnabled = true |
||||
settings.domStorageEnabled = true |
||||
settings.blockNetworkImage = true |
||||
settings.userAgentString = headerMap?.get(AppConst.UA_NAME) |
||||
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW |
||||
if (sourceRegex.isNullOrEmpty()) { |
||||
webView.webViewClient = HtmlWebViewClient() |
||||
} else { |
||||
webView.webViewClient = SnifferWebClient() |
||||
} |
||||
return webView |
||||
} |
||||
|
||||
private fun destroy() { |
||||
launch { |
||||
mWebView?.destroy() |
||||
mWebView = null |
||||
} |
||||
job.cancel() |
||||
} |
||||
|
||||
private fun getJs(): String { |
||||
javaScript?.let { |
||||
if (it.isNotEmpty()) { |
||||
return it |
||||
} |
||||
} |
||||
return JS |
||||
} |
||||
|
||||
private fun setCookie(url: String) { |
||||
tag?.let { |
||||
val cookie = CookieManager.getInstance().getCookie(url) |
||||
CookieStore.setCookie(it, cookie) |
||||
} |
||||
} |
||||
|
||||
private inner class HtmlWebViewClient : WebViewClient() { |
||||
|
||||
override fun onPageFinished(view: WebView, url: String) { |
||||
setCookie(url) |
||||
val runnable = EvalJsRunnable(view, url, getJs()) |
||||
mHandler.postDelayed(runnable, 1000) |
||||
} |
||||
|
||||
} |
||||
|
||||
private inner class EvalJsRunnable( |
||||
webView: WebView, |
||||
private val url: String, |
||||
private val mJavaScript: String |
||||
) : Runnable { |
||||
var retry = 0 |
||||
private val mWebView: WeakReference<WebView> = WeakReference(webView) |
||||
override fun run() { |
||||
mWebView.get()?.evaluateJavascript(mJavaScript) { |
||||
if (it.isNotEmpty() && it != "null") { |
||||
val content = StringEscapeUtils.unescapeJson(it) |
||||
.replace("^\"|\"$".toRegex(), "") |
||||
try { |
||||
val response = StrResponse(url, content) |
||||
callback?.onResult(response) |
||||
} catch (e: Exception) { |
||||
callback?.onError(e) |
||||
} |
||||
mHandler.removeCallbacks(this) |
||||
return@evaluateJavascript |
||||
} |
||||
if (retry > 30) { |
||||
callback?.onError(NoStackTraceException("js执行超时")) |
||||
mHandler.removeCallbacks(this) |
||||
return@evaluateJavascript |
||||
} |
||||
retry++ |
||||
mHandler.removeCallbacks(this) |
||||
mHandler.postDelayed(this, 1000) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private inner class SnifferWebClient : WebViewClient() { |
||||
|
||||
override fun onLoadResource(view: WebView, resUrl: String) { |
||||
sourceRegex?.let { |
||||
if (resUrl.matches(it.toRegex())) { |
||||
try { |
||||
val response = StrResponse(url!!, resUrl) |
||||
callback?.onResult(response) |
||||
} catch (e: Exception) { |
||||
callback?.onError(e) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onPageFinished(webView: WebView, url: String) { |
||||
setCookie(url) |
||||
val js = javaScript |
||||
if (!js.isNullOrEmpty()) { |
||||
val runnable = LoadJsRunnable(webView, javaScript) |
||||
mHandler.postDelayed(runnable, 1000L) |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
private class LoadJsRunnable( |
||||
webView: WebView, |
||||
private val mJavaScript: String? |
||||
) : Runnable { |
||||
private val mWebView: WeakReference<WebView> = WeakReference(webView) |
||||
override fun run() { |
||||
mWebView.get()?.loadUrl("javascript:${mJavaScript ?: ""}") |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
const val JS = "document.documentElement.outerHTML" |
||||
} |
||||
|
||||
abstract class Callback { |
||||
abstract fun onResult(response: StrResponse) |
||||
abstract fun onError(error: Throwable) |
||||
} |
||||
} |
Loading…
Reference in new issue