提交 d07ceb32 编写于 作者: J jackjintai

android:新增tencent x5 WebView的 网络拦截

上级 a5574650
......@@ -146,6 +146,8 @@ dependencies {
// debugImplementation rootProject.ext.dependencies["leakcanary-android"]
//百度地图定位
implementation files('libs/BaiduLBS_Android.jar')
//腾讯x5
implementation rootProject.ext.dependencies["tbs"]
// implementation 'com.aliyun.ams:alicloud-android-hotfix:3.2.12'
}
......
......@@ -101,6 +101,7 @@ class MainDebugActivity : BaseActivity(), View.OnClickListener {
tvEnv.text = "${getString(R.string.app_build_types)}:Debug"
btn_jump.setOnClickListener(this)
btn_webview.setOnClickListener(this)
btn_x5_webview.setOnClickListener(this)
findViewById<View>(R.id.btn_method_cost).setOnClickListener(this)
findViewById<View>(R.id.btn_jump_leak).setOnClickListener(this)
findViewById<View>(R.id.btn_app_launch_stack).setOnClickListener(this)
......@@ -298,7 +299,8 @@ class MainDebugActivity : BaseActivity(), View.OnClickListener {
R.id.btn_show_tool_panel -> //直接调起工具面板
DoraemonKit.showToolPanel()
R.id.btn_jump -> startActivity(Intent(this, SecondActivity::class.java))
R.id.btn_webview -> startActivity(Intent(this, WebViewActivity::class.java))
R.id.btn_webview -> startActivity(Intent(this, WebViewNormalActivity::class.java))
R.id.btn_x5_webview -> startActivity(Intent(this, WebViewX5Activity::class.java))
R.id.btn_jump_leak -> startActivity(Intent(this, LeakActivity::class.java))
R.id.btn_app_launch_stack -> {
//MethodStackUtil.getInstance().toJson()
......
......@@ -43,7 +43,8 @@
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".WebViewActivity" />
<activity android:name=".WebViewNormalActivity" />
<activity android:name=".WebViewX5Activity" />
<activity android:name=".SecondActivity" />
<activity android:name=".LeakActivity" />
......
......@@ -10,20 +10,20 @@ import androidx.appcompat.app.AppCompatActivity
/**
* Created by wanglikun on 2018/11/13.
*/
class WebViewActivity : AppCompatActivity() {
class WebViewNormalActivity : AppCompatActivity() {
val TAG = "WebViewActivity"
// val url = "file:///android_asset/dokit_index.html"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_webview)
val webView = findViewById<WebView>(R.id.web_view)
setContentView(R.layout.activity_normal_webview)
val webView = findViewById<WebView>(R.id.normal_web_view)
initWebView(webView)
// webView.loadUrl("https://page-daily.kuaidadi.com/m/ddPage_0sTyVhyq.html")
// WebViewHook.inject(webView)
//webView.loadUrl(url)
webView.loadUrl("file:///android_asset/dokit_index.html")
// webView.loadUrl("https://www.dokit.cn")
// webView.loadUrl("file:///android_asset/dokit_index.html")
webView.loadUrl("https://www.dokit.cn")
// webView.loadUrl("http://xingyun.xiaojukeji.com/docs/dokit/#/intro")
}
......
package com.didichuxing.doraemondemo
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.tencent.smtt.export.external.interfaces.ConsoleMessage
import com.tencent.smtt.export.external.interfaces.WebResourceRequest
import com.tencent.smtt.export.external.interfaces.WebResourceResponse
import com.tencent.smtt.sdk.WebChromeClient
import com.tencent.smtt.sdk.WebSettings
import com.tencent.smtt.sdk.WebView
import com.tencent.smtt.sdk.WebViewClient
/**
* Created by wanglikun on 2018/11/13.
*/
class WebViewX5Activity : AppCompatActivity() {
val TAG = "WebViewActivity"
// val url = "file:///android_asset/dokit_index.html"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_x5_webview)
val webView = findViewById<WebView>(R.id.x5_web_view)
initWebView(webView)
// webView.loadUrl("https://page-daily.kuaidadi.com/m/ddPage_0sTyVhyq.html")
// WebViewHook.inject(webView)
//webView.loadUrl(url)
// webView.loadUrl("file:///android_asset/dokit_index.html")
webView.loadUrl("https://www.dokit.cn")
// webView.loadUrl("http://xingyun.xiaojukeji.com/docs/dokit/#/intro")
}
@SuppressLint("JavascriptInterface")
private fun initWebView(webView: WebView) {
val webSettings: WebSettings = webView.settings
webSettings.pluginState = WebSettings.PluginState.ON
webSettings.javaScriptEnabled = true
webSettings.allowFileAccess = false
webSettings.loadsImagesAutomatically = true
webSettings.useWideViewPort = true
webSettings.builtInZoomControls = false
webSettings.defaultTextEncodingName = "UTF-8"
webSettings.domStorageEnabled = true
webSettings.cacheMode = WebSettings.LOAD_DEFAULT
webSettings.javaScriptCanOpenWindowsAutomatically = false
webSettings.setAllowFileAccessFromFileURLs(true)
webSettings.setAllowUniversalAccessFromFileURLs(true)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true)
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
webView.removeJavascriptInterface("searchBoxJavaBridge_")
webView.removeJavascriptInterface("accessibilityTraversal")
webView.removeJavascriptInterface("accessibility")
}
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
view.loadUrl(url)
return true
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
return super.shouldInterceptRequest(view, request)
}
}
webView.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
//LogHelper.i(TAG, "consoleMessage===>${consoleMessage?.message()}")
return super.onConsoleMessage(consoleMessage)
}
}
}
}
\ No newline at end of file
......@@ -45,7 +45,14 @@
android:id="@+id/btn_webview"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="WebView"
android:text="NormalWebView"
android:textAllCaps="false" />
<Button
android:id="@+id/btn_x5_webview"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="X5WebView"
android:textAllCaps="false" />
<Button
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.didichuxing.doraemondemo.WebViewNormalActivity">
<com.didichuxing.doraemonkit.widget.webview.MyWebView
android:id="@+id/normal_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
\ No newline at end of file
......@@ -4,10 +4,11 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.didichuxing.doraemondemo.WebViewActivity">
android:orientation="vertical"
tools:context="com.didichuxing.doraemondemo.WebViewX5Activity">
<WebView
android:id="@+id/web_view"
<com.tencent.smtt.sdk.WebView
android:id="@+id/x5_web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
......
......@@ -28,6 +28,11 @@ object DoKitExtUtil {
*/
var STACK_METHOD_LEVEL = 5
/**
* 自定义webview全限定名
*/
var WEBVIEW_CLASS_NAME: String = ""
/**
* 慢函数默认关闭
*/
......@@ -149,8 +154,8 @@ object DoKitExtUtil {
* 白名单
*/
private val whitePackageNames = arrayOf(
"com.didichuxing.doraemonkit.DoraemonKit",
"com.didichuxing.doraemonkit.DoraemonKitReal"
"com.didichuxing.doraemonkit.DoraemonKit",
"com.didichuxing.doraemonkit.DoraemonKitReal"
)
......@@ -158,14 +163,22 @@ object DoKitExtUtil {
* 黑名单
*/
private val blackPackageNames = arrayOf(
"com.didichuxing.doraemonkit.",
"kotlin.",
"java.",
"android.",
"androidx."
"com.didichuxing.doraemonkit.",
"kotlin.",
"java.",
"android.",
"androidx."
)
fun log(tag: String, className: String, methodName: String, access: Int, desc: String, signature: String, thresholdTime: Int) {
fun log(
tag: String,
className: String,
methodName: String,
access: Int,
desc: String,
signature: String,
thresholdTime: Int
) {
if (DOKIT_LOG_SWITCH) {
println("$tag===matched====> className===$className methodName===$methodName access===$access desc===$desc signature===$signature thresholdTime===$thresholdTime")
}
......
......@@ -76,11 +76,13 @@ class DoKitPlugin : Plugin<Project> {
val slowMethodSwitch = project.getProperty("DOKIT_METHOD_SWITCH", false)
val slowMethodStrategy = project.getProperty("DOKIT_METHOD_STRATEGY", 0)
val methodStackLevel = project.getProperty("DOKIT_METHOD_STACK_LEVEL", 5)
val webViewClassName = project.getProperty("DOKIT_WEBVIEW_CLASS_NAME", "")
DoKitExtUtil.DOKIT_PLUGIN_SWITCH = pluginSwitch
DoKitExtUtil.DOKIT_LOG_SWITCH = logSwitch
DoKitExtUtil.SLOW_METHOD_SWITCH = slowMethodSwitch
DoKitExtUtil.SLOW_METHOD_STRATEGY = slowMethodStrategy
DoKitExtUtil.STACK_METHOD_LEVEL = methodStackLevel
DoKitExtUtil.WEBVIEW_CLASS_NAME = webViewClassName
"application module ${project.name} is executing...".println()
......
......@@ -155,15 +155,16 @@ class CommTransformer : ClassTransformer {
//webView 字节码操作
if (DoKitExtUtil.commExt.webViewSwitch) {
//普通的webview
klass.methods.forEach { method ->
method.instructions?.iterator()?.asIterable()
?.filterIsInstance(MethodInsnNode::class.java)?.filter {
it.opcode == INVOKEVIRTUAL &&
it.owner == "android/webkit/WebView" &&
it.name == "loadUrl" &&
it.desc == "(Ljava/lang/String;)V"
it.desc == "(Ljava/lang/String;)V" &&
isWebViewOwnerNameMatched(it.owner)
}?.forEach {
"${context.projectDir.lastPath()}->hook WebView#loadurl method succeed in : ${className}_${method.name}_${method.desc}".println()
"${context.projectDir.lastPath()}->hook WebView#loadurl method succeed in : ${className}_${method.name}_${method.desc} | ${it.owner}".println()
method.instructions.insertBefore(
it,
createWebViewInsnList()
......@@ -172,7 +173,6 @@ class CommTransformer : ClassTransformer {
}
}
// url connection
klass.methods.forEach { method ->
method.instructions?.iterator()?.asIterable()
......@@ -195,6 +195,13 @@ class CommTransformer : ClassTransformer {
return klass
}
private fun isWebViewOwnerNameMatched(ownerName: String): Boolean {
return ownerName == "android/webkit/WebView" ||
ownerName == "com/tencent/smtt/sdk/WebView" ||
ownerName.contentEquals("WebView") ||
ownerName == DoKitExtUtil.WEBVIEW_CLASS_NAME
}
/**
* 创建pluginConfig代码指令
......@@ -647,7 +654,7 @@ class CommTransformer : ClassTransformer {
INVOKESTATIC,
"com/didichuxing/doraemonkit/aop/WebViewHook",
"inject",
"(Landroid/webkit/WebView;)V",
"(Ljava/lang/Object;)V",
false
)
)
......
......@@ -38,7 +38,7 @@ ext {
"design" : 'com.google.android.material:material:1.1.0',
"kotlin" : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${android["kotlin_version"]}",
"core-ktx" : "androidx.core:core-ktx:1.3.0",
"webview" : "androidx.webkit:webkit:1.1.0",
"webkit" : "androidx.webkit:webkit:1.3.0",
//constraintLayout
"constraintLayout" : 'androidx.constraintlayout:constraintlayout:1.1.3',
"coroutines-core" : "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7",
......@@ -105,7 +105,9 @@ ext {
//跨进程通信框架
"abridge" : "com.sjtu.yifei:abridge:1.0.1",
"jsoup" : "org.jsoup:jsoup:1.13.1",
"mimecraft" : "com.squareup.mimecraft:mimecraft:1.1.1"
"mimecraft" : "com.squareup.mimecraft:mimecraft:1.1.1",
//tencent x5 浏览器 https://x5.tencent.com/
"tbs" : "com.tencent.tbs.tbssdk:sdk:43903",
// "swipeback" : "me.imid.swipebacklayout.lib:library:1.1.0"
]
......
......@@ -28,6 +28,11 @@ object DoKitExtUtil {
*/
var STACK_METHOD_LEVEL = 5
/**
* webview 的全限定名
*/
var WEBVIEW_CLASS_NAME = ""
/**
* 慢函数默认关闭
*/
......@@ -149,8 +154,8 @@ object DoKitExtUtil {
* 白名单
*/
private val whitePackageNames = arrayOf(
"com.didichuxing.doraemonkit.DoraemonKit",
"com.didichuxing.doraemonkit.DoraemonKitReal"
"com.didichuxing.doraemonkit.DoraemonKit",
"com.didichuxing.doraemonkit.DoraemonKitReal"
)
......@@ -158,14 +163,22 @@ object DoKitExtUtil {
* 黑名单
*/
private val blackPackageNames = arrayOf(
"com.didichuxing.doraemonkit.",
"kotlin.",
"java.",
"android.",
"androidx."
"com.didichuxing.doraemonkit.",
"kotlin.",
"java.",
"android.",
"androidx."
)
fun log(tag: String, className: String, methodName: String, access: Int, desc: String, signature: String, thresholdTime: Int) {
fun log(
tag: String,
className: String,
methodName: String,
access: Int,
desc: String,
signature: String,
thresholdTime: Int
) {
if (DOKIT_LOG_SWITCH) {
println("$tag===matched====> className===$className methodName===$methodName access===$access desc===$desc signature===$signature thresholdTime===$thresholdTime")
}
......
......@@ -76,11 +76,13 @@ class DoKitPlugin : Plugin<Project> {
val slowMethodSwitch = project.getProperty("DOKIT_METHOD_SWITCH", false)
val slowMethodStrategy = project.getProperty("DOKIT_METHOD_STRATEGY", 0)
val methodStackLevel = project.getProperty("DOKIT_METHOD_STACK_LEVEL", 5)
val webviewClassName = project.getProperty("DOKIT_WEBVIEW_CLASS_NAME", "")
DoKitExtUtil.DOKIT_PLUGIN_SWITCH = pluginSwitch
DoKitExtUtil.DOKIT_LOG_SWITCH = logSwitch
DoKitExtUtil.SLOW_METHOD_SWITCH = slowMethodSwitch
DoKitExtUtil.SLOW_METHOD_STRATEGY = slowMethodStrategy
DoKitExtUtil.STACK_METHOD_LEVEL = methodStackLevel
DoKitExtUtil.WEBVIEW_CLASS_NAME = webviewClassName
"application module ${project.name} is executing...".println()
......
......@@ -66,7 +66,7 @@ dependencies {
implementation rootProject.ext.dependencies["recyclerview"]
implementation rootProject.ext.dependencies["kotlin"]
implementation rootProject.ext.dependencies["core-ktx"]
implementation rootProject.ext.dependencies["webview"]
implementation rootProject.ext.dependencies["webkit"]
implementation rootProject.ext.dependencies["gson"]
implementation rootProject.ext.dependencies["zxing"]
......@@ -98,6 +98,8 @@ dependencies {
compileOnly rootProject.ext.dependencies["picasso"]
compileOnly rootProject.ext.dependencies["fresco"]
compileOnly rootProject.ext.dependencies["image-loader"]
//腾讯x5
compileOnly rootProject.ext.dependencies["tbs"]
//高德地图定位
compileOnly rootProject.ext.dependencies["amap_location"]
......@@ -105,6 +107,7 @@ dependencies {
compileOnly rootProject.ext.dependencies["tencent_location"]
//百度地图定位
compileOnly files('libs/BaiduLBS_Android.jar')
}
configurations.all {
......
......@@ -7,9 +7,9 @@ import android.webkit.WebView;
import androidx.webkit.WebViewCompat;
import com.didichuxing.doraemonkit.constant.DokitConstant;
import com.didichuxing.doraemonkit.kit.h5_help.DokitJSI;
import com.didichuxing.doraemonkit.kit.h5_help.DokitWebViewClient;
import com.didichuxing.doraemonkit.kit.h5_help.DokitX5WebViewClient;
import com.didichuxing.doraemonkit.util.LogHelper;
/**
......@@ -27,21 +27,45 @@ public class WebViewHook {
/**
* webview inject java object
*/
@SuppressLint({"AddJavascriptInterface", "RequiresFeature", "SetJavaScriptEnabled"})
public static void inject(WebView webView) {
public static void inject(Object webView) {
LogHelper.i(TAG, "====inject====");
if (webView != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!(WebViewCompat.getWebViewClient(webView) instanceof DokitWebViewClient)) {
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAllowUniversalAccessFromFileURLs(true);
webView.addJavascriptInterface(new DokitJSI(), "dokitJsi");
webView.setWebViewClient(new DokitWebViewClient(WebViewCompat.getWebViewClient(webView)));
}
if (webView instanceof WebView) {
injectNormal((WebView) webView);
} else if (webView instanceof com.tencent.smtt.sdk.WebView) {
injectX5((com.tencent.smtt.sdk.WebView) webView);
}
}
}
@SuppressLint({"AddJavascriptInterface", "RequiresFeature", "SetJavaScriptEnabled"})
private static void injectNormal(WebView webView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!(WebViewCompat.getWebViewClient(webView) instanceof DokitWebViewClient)) {
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAllowUniversalAccessFromFileURLs(true);
webView.addJavascriptInterface(new DokitJSI(), "dokitJsi");
webView.setWebViewClient(new DokitWebViewClient(WebViewCompat.getWebViewClient(webView)));
}
}
}
@SuppressLint("SetJavaScriptEnabled")
private static void injectX5(com.tencent.smtt.sdk.WebView webView) {
LogHelper.i(TAG, "====injectX5====");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!(webView.getWebViewClient() instanceof DokitX5WebViewClient)) {
com.tencent.smtt.sdk.WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAllowUniversalAccessFromFileURLs(true);
webView.addJavascriptInterface(new DokitJSI(), "dokitJsi");
webView.setWebViewClient(new DokitX5WebViewClient(webView.getWebViewClient()));
}
}
}
}
......@@ -68,8 +68,8 @@ class DokitWebViewClient(webViewClient: WebViewClient?) : WebViewClient() {
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
//是否允许js抓包和数据mock
if (!DokitConstant.H5_JS_INJECT) {
//开关均被关闭则不进行拦截
if (!DokitConstant.H5_JS_INJECT && !DokitConstant.H5_VCONSOLE_INJECT) {
return super.shouldInterceptRequest(view, request)
}
request?.let { webRequest ->
......@@ -88,8 +88,13 @@ class DokitWebViewClient(webViewClient: WebViewClient?) : WebViewClient() {
}
val response = DokitOkGo.get<String>(url).execute()
//注入本地网络拦截js
var newHtml = injectJsHook(response.body()?.string())
var newHtml = if (DokitConstant.H5_JS_INJECT) {
injectJsHook(response.body()?.string())
} else {
response.body()?.string()
}
//注入vConsole的代码
if (DokitConstant.H5_VCONSOLE_INJECT) {
newHtml = injectVConsoleHook(newHtml)
......@@ -187,7 +192,7 @@ class DokitWebViewClient(webViewClient: WebViewClient?) : WebViewClient() {
//是否命中拦截规则
if (!interceptMatchedId.isNullOrBlank()) {
JsHookDataManager.jsRequestMap.remove(requestBean.requestId)
return JsHttpUtil.matchedInterceptRule(
return JsHttpUtil.matchedNormalInterceptRule(
httpUrl,
path,
interceptMatchedId,
......
package com.didichuxing.doraemonkit.kit.h5_help
import android.app.Activity
import android.graphics.Bitmap
import android.os.Build
import android.os.Message
import android.view.KeyEvent
import androidx.annotation.RequiresApi
import com.blankj.utilcode.util.ConvertUtils
import com.blankj.utilcode.util.ResourceUtils
import com.didichuxing.doraemonkit.constant.DokitConstant
import com.didichuxing.doraemonkit.kit.core.AbsDokitView
import com.didichuxing.doraemonkit.kit.core.DokitViewManager
import com.didichuxing.doraemonkit.kit.h5_help.bean.JsRequestBean
import com.didichuxing.doraemonkit.kit.network.NetworkManager
import com.didichuxing.doraemonkit.kit.network.room_db.DokitDbManager
import com.didichuxing.doraemonkit.okgo.DokitOkGo
import com.didichuxing.doraemonkit.util.LogHelper
import com.tencent.smtt.export.external.interfaces.*
import com.tencent.smtt.sdk.MimeTypeMap
import com.tencent.smtt.sdk.WebView
import com.tencent.smtt.sdk.WebViewClient
import okhttp3.*
import org.jsoup.Jsoup
import java.net.URLDecoder
/**
* ================================================
* 作 者:jint(金台)
* 版 本:1.0
* 创建日期:2020/8/28-17:22
* 描 述:切面dokit
* 修订历史:
* ================================================
*/
class DokitX5WebViewClient(webViewClient: WebViewClient?) : WebViewClient() {
private val TAG = "DokitWebViewClient"
private val mWebViewClient: WebViewClient? = webViewClient
private val mOkHttpClient = OkHttpClient()
/**
* 更新悬浮窗上的链接
*/
private fun updateH5DokitUrl(view: WebView?, url: String?) {
view?.let { it ->
if (it.context is Activity) {
val activity = it.context as Activity
val absDokitView: AbsDokitView? = DokitViewManager.getInstance()
.getDokitView(activity, H5DokitView::class.java.simpleName)
absDokitView?.let { h5DokitView ->
(h5DokitView as H5DokitView).updateUrl(url)
}
}
}
}
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
updateH5DokitUrl(view, url)
if (mWebViewClient != null) {
return mWebViewClient.shouldOverrideUrlLoading(view, url)
}
return super.shouldOverrideUrlLoading(view, url)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
//开关均被关闭则不进行拦截
if (!DokitConstant.H5_JS_INJECT && !DokitConstant.H5_VCONSOLE_INJECT) {
return super.shouldInterceptRequest(view, request)
}
request?.let { webRequest ->
//加载页面资源
if (webRequest.isForMainFrame) {
LogHelper.i(
TAG,
"url===>${webRequest.url?.toString()} method==>${webRequest.method} thread==>${Thread.currentThread().name}"
)
val httpUrl = HttpUrl.parse(webRequest.url?.toString())
val url = if (httpUrl?.url()?.query.isNullOrBlank()) {
webRequest.url?.toString() + "?dokit_flag=web"
} else {
webRequest.url?.toString() + "&dokit_flag=web"
}
val response = DokitOkGo.get<String>(url).execute()
//注入本地网络拦截js
var newHtml = if (DokitConstant.H5_JS_INJECT) {
injectJsHook(response.body()?.string())
} else {
response.body()?.string()
}
//注入vConsole的代码
if (DokitConstant.H5_VCONSOLE_INJECT) {
newHtml = injectVConsoleHook(newHtml)
}
return WebResourceResponse(
"text/html",
response.header("content-encoding", "utf-8"),
ConvertUtils.string2InputStream(newHtml, "utf-8")
)
} else {
//加载js网络请求
if (webRequest.url.toString().contains("dokit_flag")) {
val jsRequestId = getUrlQuery(webRequest.url.toString(), "dokit_flag")
val jsRequestBean = JsHookDataManager.jsRequestMap[jsRequestId]
LogHelper.i(TAG, jsRequestBean.toString())
jsRequestBean?.let { requestBean ->
val url = HttpUrl.parse(requestBean.url)
val host = url?.host()
//如果是dokit mock host 则不进行拦截
if (host.equals(NetworkManager.MOCK_HOST, true)) {
JsHookDataManager.jsRequestMap.remove(requestBean.requestId)
return null
}
//web 抓包
if (NetworkManager.isActive()) {
try {
//构建okhttp用来抓包
val newRequest: Request =
JsHttpUtil.createOkHttpRequest(requestBean)
if (!JsHttpUtil.matchWhiteHost(newRequest)) {
//发送模拟请求
mOkHttpClient.newCall(newRequest).execute()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
// web 数据mock
return dealMock(requestBean, url)
}
} else {
return super.shouldInterceptRequest(view, request)
}
}
}
return super.shouldInterceptRequest(view, request)
}
/**
* 处理数据mock的相关逻辑
*/
private fun dealMock(
requestBean: JsRequestBean,
url: HttpUrl?
): WebResourceResponse? {
url?.let { httpUrl ->
try {
val path = URLDecoder.decode(httpUrl.encodedPath(), "utf-8")
val queries = httpUrl.query()
val jsonQuery = JsHttpUtil.transformQuery(queries)
val jsonRequestBody = JsHttpUtil.transformRequestBody(
requestBean.method,
requestBean.body,
requestBean.headers
)
val interceptMatchedId =
DokitDbManager.getInstance().isMockMatched(
path,
jsonQuery,
jsonRequestBody,
DokitDbManager.MOCK_API_INTERCEPT,
DokitDbManager.FROM_SDK_OTHER
)
val templateMatchedId =
DokitDbManager.getInstance().isMockMatched(
path,
jsonQuery,
jsonRequestBody,
DokitDbManager.MOCK_API_TEMPLATE,
DokitDbManager.FROM_SDK_OTHER
)
val newRequest: Request =
JsHttpUtil.createOkHttpRequest(requestBean)
//发送模拟请求
val newResponse =
mOkHttpClient.newCall(newRequest).execute()
//是否命中拦截规则
if (!interceptMatchedId.isNullOrBlank()) {
JsHookDataManager.jsRequestMap.remove(requestBean.requestId)
return JsHttpUtil.matchedX5InterceptRule(
httpUrl,
path,
interceptMatchedId,
templateMatchedId,
newRequest,
newResponse,
mOkHttpClient
)
}
//是否命中模板规则
if (!templateMatchedId.isNullOrBlank()) {
JsHttpUtil.matchedTemplateRule(
newResponse,
path,
templateMatchedId
)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
JsHookDataManager.jsRequestMap.remove(requestBean.requestId)
return null
}
private fun getUrlQuery(url: String, key: String): String? {
val httpUrl = HttpUrl.parse(url)
val queries = httpUrl?.url()?.query?.split("&")
val queryMap = mutableMapOf<String, String>()
queries?.forEach {
val keyAndValue = it.split("=")
queryMap[keyAndValue[0]] = keyAndValue[1]
}
return queryMap[key]
}
/**
* 注入hook js 哇共诺请求的代码
*/
private fun injectJsHook(html: String?): String {
//读取本地js hook 代码
val jsHook = ResourceUtils.readAssets2String("dokit_js_hook.html")
val doc = Jsoup.parse(html)
doc.outputSettings().prettyPrint(true)
val elements = doc.getElementsByTag("head")
if (elements.size > 0) {
elements[0].prepend(jsHook)
}
return doc.toString()
}
/**
* 注入 vConsole的代码
*/
private fun injectVConsoleHook(html: String?): String {
//读取本地js hook 代码
val vconsoleHook = ResourceUtils.readAssets2String("dokit_js_vconsole_hook.html")
val doc = Jsoup.parse(html)
doc.outputSettings().prettyPrint(true)
val elements = doc.getElementsByTag("head")
if (elements.size > 0) {
elements[elements.size - 1].append(vconsoleHook)
}
return doc.toString()
}
//get mime type by url
private fun getMimeType(url: String): String? {
var type: String? = null
val extension = MimeTypeMap.getFileExtensionFromUrl(url)
if (extension != null) {
when (extension) {
"js" -> type = "text/javascript"
"woff" -> type = "application/font-woff"
"woff2" -> type = "application/font-woff2"
"ttf" -> type = "application/x-font-ttf"
"eot" -> type = "application/vnd.ms-fontobject"
"svg" -> type = "text/javascript"
else -> type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
}
}
return type
}
@RequiresApi(Build.VERSION_CODES.N)
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
updateH5DokitUrl(view, request?.url?.path)
if (mWebViewClient != null) {
return mWebViewClient.shouldOverrideUrlLoading(view, request)
}
return super.shouldOverrideUrlLoading(view, request)
}
override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {
if (mWebViewClient != null) {
return mWebViewClient.shouldInterceptRequest(view, url)
}
return super.shouldInterceptRequest(view, url)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
if (mWebViewClient != null) {
return mWebViewClient.onPageStarted(view, url, favicon)
}
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
if (mWebViewClient != null) {
return mWebViewClient.onPageFinished(view, url)
}
super.onPageFinished(view, url)
}
override fun onLoadResource(view: WebView?, url: String?) {
if (mWebViewClient != null) {
return mWebViewClient.onLoadResource(view, url)
}
super.onLoadResource(view, url)
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onPageCommitVisible(view: WebView?, url: String?) {
if (mWebViewClient != null) {
return mWebViewClient.onPageCommitVisible(view, url)
}
super.onPageCommitVisible(view, url)
}
override fun onTooManyRedirects(view: WebView?, cancelMsg: Message?, continueMsg: Message?) {
if (mWebViewClient != null) {
return mWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg)
}
super.onTooManyRedirects(view, cancelMsg, continueMsg)
}
override fun onReceivedError(
view: WebView?,
errorCode: Int,
description: String?,
failingUrl: String?
) {
if (mWebViewClient != null) {
return mWebViewClient.onReceivedError(view, errorCode, description, failingUrl)
}
super.onReceivedError(view, errorCode, description, failingUrl)
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
if (mWebViewClient != null) {
return mWebViewClient.onReceivedError(view, request, error)
}
super.onReceivedError(view, request, error)
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onReceivedHttpError(
view: WebView?,
request: WebResourceRequest?,
errorResponse: WebResourceResponse?
) {
if (mWebViewClient != null) {
return mWebViewClient.onReceivedHttpError(view, request, errorResponse)
}
super.onReceivedHttpError(view, request, errorResponse)
}
override fun onFormResubmission(view: WebView?, dontResend: Message?, resend: Message?) {
if (mWebViewClient != null) {
return mWebViewClient.onFormResubmission(view, dontResend, resend)
}
super.onFormResubmission(view, dontResend, resend)
}
override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
updateH5DokitUrl(view, url)
if (mWebViewClient != null) {
return mWebViewClient.doUpdateVisitedHistory(view, url, isReload)
}
super.doUpdateVisitedHistory(view, url, isReload)
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onReceivedClientCertRequest(view: WebView?, request: ClientCertRequest?) {
if (mWebViewClient != null) {
return mWebViewClient.onReceivedClientCertRequest(view, request)
}
super.onReceivedClientCertRequest(view, request)
}
override fun onReceivedHttpAuthRequest(
view: WebView?,
handler: HttpAuthHandler?,
host: String?,
realm: String?
) {
if (mWebViewClient != null) {
return mWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm)
}
super.onReceivedHttpAuthRequest(view, handler, host, realm)
}
override fun shouldOverrideKeyEvent(view: WebView?, event: KeyEvent?): Boolean {
if (mWebViewClient != null) {
return mWebViewClient.shouldOverrideKeyEvent(view, event)
}
return super.shouldOverrideKeyEvent(view, event)
}
override fun onUnhandledKeyEvent(view: WebView?, event: KeyEvent?) {
if (mWebViewClient != null) {
return mWebViewClient.onUnhandledKeyEvent(view, event)
}
super.onUnhandledKeyEvent(view, event)
}
override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) {
if (mWebViewClient != null) {
return mWebViewClient.onScaleChanged(view, oldScale, newScale)
}
super.onScaleChanged(view, oldScale, newScale)
}
override fun onReceivedLoginRequest(
view: WebView?,
realm: String?,
account: String?,
args: String?
) {
if (mWebViewClient != null) {
return mWebViewClient.onReceivedLoginRequest(view, realm, account, args)
}
super.onReceivedLoginRequest(view, realm, account, args)
}
override fun onReceivedSslError(
p0: WebView?,
p1: SslErrorHandler?,
p2: com.tencent.smtt.export.external.interfaces.SslError?
) {
if (mWebViewClient != null) {
return mWebViewClient.onReceivedSslError(p0, p1, p2)
}
super.onReceivedSslError(p0, p1, p2)
}
override fun onDetectedBlankScreen(p0: String?, p1: Int) {
if (mWebViewClient != null) {
return mWebViewClient.onDetectedBlankScreen(p0, p1)
}
super.onDetectedBlankScreen(p0, p1)
}
}
\ No newline at end of file
......@@ -47,6 +47,7 @@ class H5DokitView : AbsDokitView() {
private lateinit var mRvLocal: RecyclerView
private lateinit var mRvSession: RecyclerView
private lateinit var mRvWrap: LinearLayout
private lateinit var mMoreWrap: RelativeLayout
private lateinit var mHolder: TextView
private lateinit var mLocalAdapter: LocalStorageAdapter
private lateinit var mSessionAdapter: LocalStorageAdapter
......@@ -103,6 +104,7 @@ class H5DokitView : AbsDokitView() {
}
mRvWrap = it.findViewById(R.id.ll_rv_wrap)
mMoreWrap = it.findViewById(R.id.rl_more_wrap)
mHolder = it.findViewById(R.id.tv_holder)
mHolder.text = "更多"
mHolder.setOnClickListener {
......@@ -156,8 +158,10 @@ class H5DokitView : AbsDokitView() {
val webView = performTraverseView()
if (webView == null) {
mTvLink.text = "当前页面不存在WebView"
mMoreWrap.visibility = View.GONE
} else {
mTvLink.text = webView.url
mMoreWrap.visibility = View.VISIBLE
}
mJsCheckBox.isChecked = DokitConstant.H5_JS_INJECT
invalidate()
......@@ -215,7 +219,7 @@ class H5DokitView : AbsDokitView() {
normalLayoutParams?.let {
it.width = ConvertUtils.dp2px(300.0f)
if (isOpen) {
it.height = ConvertUtils.dp2px(600.0f)
it.height = ConvertUtils.dp2px(650.0f)
} else {
it.height = DokitViewLayoutParams.WRAP_CONTENT
}
......
......@@ -104,7 +104,7 @@ internal object JsHttpUtil {
/**
* 返回null 即代表返回原来的数据
*/
fun matchedInterceptRule(
fun matchedNormalInterceptRule(
url: HttpUrl,
path: String,
interceptMatchedId: String,
......@@ -168,7 +168,7 @@ internal object JsHttpUtil {
//判断新的response是否有数据
return if (newResponse.body() != null) {
matchedTemplateRule(newResponse, path, templateMatchedId)
createWebResourceResponse(newResponse)
createNormalWebResourceResponse(newResponse)
} else {
matchedTemplateRule(oldResponse, path, templateMatchedId)
null
......@@ -178,7 +178,7 @@ internal object JsHttpUtil {
return null
}
private fun createWebResourceResponse(response: Response): WebResourceResponse {
private fun createNormalWebResourceResponse(response: Response): WebResourceResponse {
return WebResourceResponse(
response.body()?.contentType().toString(),
"UTF-8",
......@@ -186,6 +186,91 @@ internal object JsHttpUtil {
)
}
/**
* 返回null 即代表返回原来的数据
*/
fun matchedX5InterceptRule(
url: HttpUrl,
path: String,
interceptMatchedId: String,
templateMatchedId: String,
oldRequest: Request,
oldResponse: Response,
okHttpClient: OkHttpClient
): com.tencent.smtt.export.external.interfaces.WebResourceResponse? {
//判断是否需要重定向数据接口
//http https
//判断是否需要重定向数据接口
//http https
val scheme = url.scheme()
val interceptApiBean = DokitDbManager.getInstance().getInterceptApiByIdInMap(
path,
interceptMatchedId,
DokitDbManager.FROM_SDK_OTHER
)
if (interceptApiBean == null) {
matchedTemplateRule(oldResponse, path, templateMatchedId)
return null
}
interceptApiBean as MockInterceptApiBean
val selectedSceneId = interceptApiBean.selectedSceneId
//开关是否被打开
//开关是否被打开
if (!interceptApiBean.isOpen) {
matchedTemplateRule(oldResponse, path, templateMatchedId)
return null
}
//判断是否有选中的场景
//判断是否有选中的场景
if (TextUtils.isEmpty(selectedSceneId)) {
matchedTemplateRule(oldResponse, path, templateMatchedId)
return null
}
val sb = StringBuilder()
val newUrl: String
newUrl = if (NetworkManager.MOCK_SCHEME_HTTP.contains(scheme.toLowerCase())) {
sb.append(NetworkManager.MOCK_SCHEME_HTTP).append(NetworkManager.MOCK_HOST)
.append("/api/app/scene/").append(selectedSceneId).toString()
} else {
sb.append(NetworkManager.MOCK_SCHEME_HTTPS).append(NetworkManager.MOCK_HOST)
.append("/api/app/scene/").append(selectedSceneId).toString()
}
val newRequest = Request.Builder()
.method("GET", null)
.url(newUrl).build()
//需要提前关闭数据流 不然在某些场景下会报错
oldResponse.close()
val newResponse: Response = okHttpClient.newCall(newRequest).execute()
if (newResponse.code() == 200) {
//拦截命中提示
ToastUtils.showShort("接口别名:==" + interceptApiBean.mockApiName + "==已被拦截")
//判断新的response是否有数据
return if (newResponse.body() != null) {
matchedTemplateRule(newResponse, path, templateMatchedId)
createX5WebResourceResponse(newResponse)
} else {
matchedTemplateRule(oldResponse, path, templateMatchedId)
null
}
}
matchedTemplateRule(oldResponse, path, templateMatchedId)
return null
}
private fun createX5WebResourceResponse(response: Response): com.tencent.smtt.export.external.interfaces.WebResourceResponse {
return com.tencent.smtt.export.external.interfaces.WebResourceResponse(
response.body()?.contentType().toString(),
"UTF-8",
ConvertUtils.string2InputStream(response.bodyContent(), "UTF-8")
)
}
/**
*是否命中模板功能
......
......@@ -56,6 +56,14 @@
</LinearLayout>
<View style="@style/DK.Divider" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="tips:js hook和vConsole的代码均在页面加载时注入,状态改变时需要重新加载页面才能生效"
android:textColor="@color/dk_color_FF0006"
android:textSize="10sp"
/>
<!-- js hook switch-->
<RelativeLayout
android:id="@+id/rl_js_switch"
......@@ -109,6 +117,7 @@
<View style="@style/DK.Divider" />
<RelativeLayout
android:id="@+id/rl_more_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
......
......@@ -16,14 +16,14 @@ android.debug.obsoleteApi=true
android.useAndroidX=true
android.enableJetifier=true
android.injected.testOnly=false
kotlin.code.style=official
#dokit全局配置
# 插件开关
DOKIT_PLUGIN_SWITCH=true
# 插件日志
DOKIT_LOG_SWITCH=true
# webview 的全限定名
DOKIT_WEBVIEW_CLASS_NAME=com/didichuxing/doraemonkit/widget/webview/MyWebView
#dokit 慢函数开关
DOKIT_METHOD_SWITCH=true
#dokit 函数调用栈层级
......@@ -31,3 +31,4 @@ DOKIT_METHOD_STACK_LEVEL=4
#0:默认模式 打印函数调用栈 需添加指定入口 默认为application onCreate 和attachBaseContext
#1:普通模式 运行时打印某个函数的耗时 全局业务代码函数插入
DOKIT_METHOD_STRATEGY=0
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册