├── .gitignore ├── README.md ├── WebViewManager ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── xingen │ │ └── webviewmanager │ │ └── WebViewManager.java │ └── res │ └── values │ └── strings.xml ├── androidjslib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── xingen │ └── androidjslib │ ├── JavaScript.java │ ├── event │ ├── ClickEvent.java │ ├── Event.java │ ├── InputEvent.java │ └── ScrollEvent.java │ ├── execute │ └── MainExecutor.java │ ├── injection │ ├── JSBehavior.java │ └── JSInjection.java │ ├── listener │ └── Response.java │ └── utils │ ├── LogUtils.java │ └── ScreenUtils.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── xingen │ │ └── app │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── xingen │ │ │ └── app │ │ │ ├── App.java │ │ │ ├── InputAndClickTestActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── ScrollTestActivity.java │ │ │ └── WebViewManagerTest.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── xingen │ └── app │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── picture ├── WebView脱离Activity进行加载 (1).png ├── WebView页面中元素不同情况处理.png ├── webview模拟用户点击分析.png ├── 滚动列表.png ├── 点击按钮.png └── 输入框.png ├── settings.gradle └── webviewlib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── xingen │ └── webviewlib │ ├── callback │ └── WebViewCallback.java │ ├── client │ ├── AppChromeClient.java │ └── AppWebViewClient.java │ └── view │ └── CommonWebView.java └── res └── values └── strings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | 12 | .idea 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebViewLib 2 | 3 | 广告业务SDK,揭秘广告刷量黑科技。 4 | 5 | 模块分为: 6 | 7 | - 一个JS库(支持自动点击,输入,滑动),采用js注入技术,webview模拟用户真实操作。 8 | - 一个WebViewManager封装库,采用1px的透明技术,不依赖于Activity进行加载。 9 | - 一个WebView封装库,里面封装,一些常见的国产ROM上的问题处理,常见的操作包含打开京东页面,应用宝支付,微信支付等等。 10 | 11 | PS: JS库和WebViewManager库已经在老东家的暗地刷量项目(九点广告,百度糯米推广项目)实战检验过 12 | 13 | 欢迎参与讨论常见的webview刷量页面广告的黑科技,微信号:H675134792。 14 | 15 | 先来一波思路分析: 16 | --- 17 | 18 | **1. 探究如何模拟用户点击webview**: 19 | 20 | ![image](https://github.com/13767004362/WebViewLib/blob/master/picture/webview%E6%A8%A1%E6%8B%9F%E7%94%A8%E6%88%B7%E7%82%B9%E5%87%BB%E5%88%86%E6%9E%90.png) 21 | 22 | 可知,通过各种转换方式,可以实现模拟用户点击行为。 23 | 24 | **2. 处理Webview中页面元素的不情况**: 25 | ![image](https://github.com/13767004362/WebViewLib/blob/master/picture/WebView%E9%A1%B5%E9%9D%A2%E4%B8%AD%E5%85%83%E7%B4%A0%E4%B8%8D%E5%90%8C%E6%83%85%E5%86%B5%E5%A4%84%E7%90%86.png) 26 | 27 | 可能处于屏幕中,也有可能处理屏幕外,上端或者下端,因此需要针对性处理,处于屏幕中才能,精确点击到元素。 28 | 29 | **3.处理WebView悬浮窗,脱离Activity,1px显示**: 30 | 31 | ![image](https://github.com/13767004362/WebViewLib/blob/master/picture/WebView%E8%84%B1%E7%A6%BBActivity%E8%BF%9B%E8%A1%8C%E5%8A%A0%E8%BD%BD%20(1).png) 32 | 可能存在多个广告项目,因此采用并行方式,多个webview同步加载不同任务。 33 | 34 | **使用指南** 35 | 36 | --- 37 | 38 | #### **AndroidJsLib的使用**: 39 | 40 | **1. 初始化**: 41 | 42 | 在webview调用`loadUrl()`之前,调用该方法: 43 | ``` 44 | JavaScript.JavaScriptBuilder.init(webView, true); 45 | ``` 46 | 该方法用于,添加javaScript交互接口,和log日志配置。 47 | 48 | **2.2 添加输入事件** 49 | 50 | 例如:百度网页输入框,输入`新根` 51 | 52 | 通过浏览器获取到元素的css选择器: 53 | 54 | ![image](https://github.com/13767004362/WebViewLib/blob/master/picture/%E8%BE%93%E5%85%A5%E6%A1%86.png) 55 | 56 | ``` 57 | String elementName = "\"input.se-input\""; 58 | InputEvent inputEvent = InputEvent.create() 59 | .setElementName(elementName) 60 | .setDelayTime(1000) 61 | .setValue("新根") 62 | .setListener(new Response.InputListener() { 63 | @Override 64 | public void inputFailure(InputEvent inputEvent) { 65 | String content = "输入失败"; 66 | Toast.makeText(getApplicationContext(), content, Toast.LENGTH_SHORT).show(); 67 | Log.i(TAG, " input 输入失败 "); 68 | } 69 | 70 | @Override 71 | public void inputSuccess(InputEvent inputEvent) { 72 | Toast.makeText(getApplicationContext(), "输入成功", Toast.LENGTH_SHORT).show(); 73 | Log.i(TAG, " input 输入成功"); 74 | } 75 | }); 76 | JavaScript.JavaScriptBuilder.executeEvent(inputEvent, webView); 77 | ``` 78 | **2.2 添加点击事件** 79 | 80 | 通过浏览器获取到元素的css选择器: 81 | 82 | ![image](https://github.com/13767004362/WebViewLib/blob/master/picture/%E7%82%B9%E5%87%BB%E6%8C%89%E9%92%AE.png) 83 | 84 | 点击百度一下的按钮,进行搜索操作。 85 | ``` 86 | ClickEvent clickEvent = ClickEvent.create() 87 | .setDelayTime(5000) 88 | .setElementName("\"button.se-bn\"") 89 | .setListener(new Response.ClickListener() { 90 | @Override 91 | public void clickFailure(ClickEvent clickEvent) { 92 | Toast.makeText(getApplicationContext(), "点击失败", Toast.LENGTH_SHORT).show(); 93 | Log.i(TAG, " click点击失败 "); 94 | } 95 | 96 | @Override 97 | public void clickSuccess(ClickEvent clickEvent) { 98 | Toast.makeText(getApplicationContext(), "点击成功", Toast.LENGTH_SHORT).show(); 99 | Log.i(TAG, " click点击成功 "); 100 | } 101 | }); 102 | JavaScript.JavaScriptBuilder.executeEvent(clickEvent, webView); 103 | ``` 104 | **2.3 滚动事件** 105 | 106 | 107 | 108 | 例如: 滚动到指定的元素位置,让该元素滚动屏幕中间。 109 | 110 | 111 | 通过浏览器获取到元素的css选择器: 112 | 113 | ![image](https://github.com/13767004362/WebViewLib/blob/master/picture/%E6%BB%9A%E5%8A%A8%E5%88%97%E8%A1%A8.png) 114 | 115 | 116 | 这里滚动到我的个人博客列表中,第九个角标位置。 117 | ``` 118 | doScroll("\"ul.colu_author_c>li\"", 8, 1000); 119 | 120 | public void doScroll(String elementName, int index, int delayTime) { 121 | ScrollEvent scrollEvent = ScrollEvent.create() 122 | .setElementName(elementName) 123 | .setDelayTime(delayTime) 124 | .setPosition(index) 125 | .setScrollTime(2000) 126 | .setListener(new Response.ScrollListener() { 127 | @Override 128 | public void scrollEnd(ScrollEvent scrollEvent) { 129 | Log.i(TAG, " 滚动事件结束 "); 130 | } 131 | 132 | @Override 133 | public void scrollFailure(ScrollEvent scrollEvent) { 134 | Log.i(TAG, " 滚动事件失败 "); 135 | } 136 | }); 137 | JavaScript.JavaScriptBuilder.executeEvent(scrollEvent, webView); 138 | } 139 | ``` 140 | 混淆规则: 141 | ``` 142 | -keep class com.xingen.androidjslib.injection.* {*;} 143 | ``` 144 | 145 | 146 | -------------------------------------------------------------------------------- /WebViewManager/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /WebViewManager/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 26 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 26 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | 15 | } 16 | 17 | buildTypes { 18 | release { 19 | postprocessing { 20 | removeUnusedCode false 21 | removeUnusedResources false 22 | obfuscate false 23 | optimizeCode false 24 | proguardFile 'proguard-rules.pro' 25 | } 26 | } 27 | } 28 | 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | } 34 | -------------------------------------------------------------------------------- /WebViewManager/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /WebViewManager/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /WebViewManager/src/main/java/com/xingen/webviewmanager/WebViewManager.java: -------------------------------------------------------------------------------- 1 | package com.xingen.webviewmanager; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ActivityInfo; 5 | import android.graphics.PixelFormat; 6 | import android.os.Build; 7 | import android.util.DisplayMetrics; 8 | import android.view.Gravity; 9 | import android.view.View; 10 | import android.view.WindowManager; 11 | import android.webkit.WebSettings; 12 | import android.webkit.WebView; 13 | import android.widget.FrameLayout; 14 | import android.widget.LinearLayout; 15 | 16 | /** 17 | * Created by ${新根} on 2018/9/27. 18 | * blog博客:http://blog.csdn.net/hexingen 19 | *

20 | * 统一管理WebView,采用1px的技术,借鉴360悬浮窗口,脱离Activity加载 21 | */ 22 | 23 | public class WebViewManager { 24 | 25 | private FrameLayout contentLayout; 26 | 27 | private int mScreenWidth = 0; 28 | private int mScreenHeight = 0; 29 | private WindowManager windowManager; 30 | private WindowManager.LayoutParams windowParams; 31 | private Context appContext; 32 | 33 | public WebViewManager() { 34 | } 35 | 36 | public void init(Context context, boolean show) { 37 | this.appContext = context.getApplicationContext(); 38 | if (contentLayout==null){ 39 | calculationScreenSize(context); 40 | createChild(context); 41 | addToWindow(context, show); 42 | } 43 | } 44 | 45 | /** 46 | * 添加到window中 47 | */ 48 | private void addToWindow(Context context, boolean show) { 49 | windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 50 | //创建window的参数 51 | createWindowParams(show); 52 | //添加到window 53 | windowManager.addView(contentLayout, windowParams); 54 | } 55 | 56 | /** 57 | * 创建子类控件 58 | */ 59 | private void createChild(Context context) { 60 | /** 61 | * 作为window的子控件,用于装载WebView 62 | */ 63 | contentLayout = new FrameLayout(context); 64 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); 65 | contentLayout.setLayoutParams(layoutParams); 66 | } 67 | 68 | /** 69 | * 获取statusBar高度 70 | * 71 | * @param pContext 72 | * @return 73 | */ 74 | public static int getStatusBarHeight(Context pContext) { 75 | int result = 0; 76 | int resourceId = pContext.getResources().getIdentifier("status_bar_height", "dimen", "android"); 77 | if (resourceId > 0) { 78 | result = pContext.getResources().getDimensionPixelSize(resourceId); 79 | } 80 | return result; 81 | } 82 | 83 | /** 84 | * 设置window中参数 85 | * 86 | * @param show 87 | */ 88 | private void createWindowParams(boolean show) { 89 | windowParams = new WindowManager.LayoutParams(); 90 | windowParams.type = WindowManager.LayoutParams.TYPE_TOAST; 91 | windowParams.format = PixelFormat.RGBA_8888; 92 | windowParams.x = 0; 93 | windowParams.y = 0; 94 | windowParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 95 | windowParams.gravity = Gravity.RIGHT | Gravity.BOTTOM; 96 | //使窗口支持透明度 97 | windowParams.format = PixelFormat.TRANSLUCENT; 98 | if (show) { 99 | windowParams.width = mScreenWidth; 100 | windowParams.height = mScreenHeight; 101 | windowParams.alpha = 10;//设置透明度 102 | } else {//设置window大小为1px 103 | windowParams.width = 1; 104 | windowParams.height = 1; 105 | windowParams.alpha = 0;//设置透明度 106 | windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 107 | | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 108 | | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 109 | } 110 | } 111 | 112 | /** 113 | * 计算屏幕大小 114 | */ 115 | private void calculationScreenSize(Context context) { 116 | DisplayMetrics dm = context.getResources().getDisplayMetrics(); 117 | final int defaultHeight = 1920; 118 | final int defaultWidth = 1080; 119 | mScreenWidth = dm.widthPixels; 120 | mScreenHeight = dm.heightPixels; 121 | int barHight = getStatusBarHeight(context); 122 | mScreenHeight = mScreenHeight - barHight; 123 | if (mScreenWidth == 0) { 124 | mScreenWidth = defaultWidth; 125 | mScreenHeight = defaultHeight; 126 | } 127 | //适应android tv 128 | if (mScreenHeight < mScreenWidth) { 129 | int hight = mScreenHeight; 130 | mScreenHeight = mScreenWidth; 131 | mScreenWidth = hight; 132 | } 133 | } 134 | 135 | public WebView addWebView(){ 136 | return addWebView(null); 137 | } 138 | /** 139 | * 增加WebView 140 | * 141 | * @param webView 142 | */ 143 | public WebView addWebView(WebView webView) { 144 | //若是外部传入WebView为空,则构建默认的WebView 145 | webView = (webView == null ? WebViewBuilder.create(appContext) : webView); 146 | //设置WebView的大小 147 | FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(mScreenWidth, mScreenHeight); 148 | webView.setLayoutParams(layoutParams2); 149 | contentLayout.addView(webView); 150 | return webView; 151 | } 152 | 153 | /** 154 | * 移除某个WebView 155 | * 156 | * @param webView 157 | */ 158 | public void removeWebView(WebView webView) { 159 | try { 160 | if (contentLayout != null && webView != null) { 161 | contentLayout.removeView(webView); 162 | } 163 | } catch (Exception e) { 164 | 165 | } 166 | } 167 | 168 | /** 169 | * 销毁 170 | */ 171 | public void quit() { 172 | if (windowManager != null) { 173 | try { 174 | windowManager.removeViewImmediate(contentLayout); 175 | int childSize = contentLayout.getChildCount(); 176 | for (int i = 0; i < childSize; ++i) { 177 | View view = contentLayout.getChildAt(i); 178 | if (view instanceof WebView) { 179 | WebView webView = (WebView) view; 180 | webView.destroy(); 181 | } 182 | } 183 | contentLayout.removeAllViews(); 184 | contentLayout = null; 185 | } catch (Exception e) { 186 | 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * 构建默认的WebView 193 | */ 194 | public static final class WebViewBuilder { 195 | public static WebView create(Context context) { 196 | WebView webView = new WebView(context); 197 | WebSettings webSetting = webView.getSettings(); 198 | webSetting.setJavaScriptEnabled(true); 199 | webSetting.setDomStorageEnabled(true); 200 | webSetting.setGeolocationEnabled(false); 201 | webSetting.setSupportZoom(false); 202 | webSetting.setBuiltInZoomControls(false); 203 | webSetting.setUseWideViewPort(true); 204 | webSetting.setSupportMultipleWindows(false); 205 | webSetting.setLoadsImagesAutomatically(true); 206 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 207 | try { 208 | webView.getSettings().setMixedContentMode( 209 | WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 210 | } catch (Throwable e) { 211 | e.printStackTrace(); 212 | } 213 | } 214 | return webView; 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /WebViewManager/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WebViewManager 3 | 4 | -------------------------------------------------------------------------------- /androidjslib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /androidjslib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release'//添加 3 | android { 4 | compileSdkVersion 27 5 | 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 27 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | lintOptions { 24 | abortOnError false 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | 31 | } 32 | 33 | //添加 34 | publish { 35 | userOrg = 'h13767004362'//bintray.com用户名 36 | groupId = 'com.xingen'//jcenter上的路径 37 | artifactId = 'androidjslib'//项目名称 38 | publishVersion = '1.0.0'//版本号 39 | desc = 'Oh hi, this is a nice description for a project, right?'//描述,不重要 40 | website = 'https://github.com/13767004362/WebViewLib' 41 | } -------------------------------------------------------------------------------- /androidjslib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /androidjslib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/JavaScript.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib; 2 | 3 | import android.webkit.WebView; 4 | 5 | import com.xingen.androidjslib.event.ClickEvent; 6 | import com.xingen.androidjslib.event.Event; 7 | import com.xingen.androidjslib.event.InputEvent; 8 | import com.xingen.androidjslib.event.ScrollEvent; 9 | import com.xingen.androidjslib.execute.MainExecutor; 10 | import com.xingen.androidjslib.injection.JSBehavior; 11 | import com.xingen.androidjslib.injection.JSInjection; 12 | import com.xingen.androidjslib.listener.Response; 13 | import com.xingen.androidjslib.utils.LogUtils; 14 | 15 | import java.util.List; 16 | import java.util.concurrent.CopyOnWriteArrayList; 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | 19 | /** 20 | * Author by {xinGen} 21 | * Date on 2018/9/6 09:46 22 | */ 23 | public class JavaScript { 24 | private static final MainExecutor mainExecutor = new MainExecutor(); 25 | private static final AtomicInteger mSequenceGenerator = new AtomicInteger(); 26 | private static List eventList = new CopyOnWriteArrayList<>(); 27 | 28 | private JavaScript() { 29 | } 30 | 31 | /** 32 | * 获取递增序列号 33 | * 34 | * @return 35 | */ 36 | private static int getSequenceNumber() { 37 | return mSequenceGenerator.incrementAndGet(); 38 | } 39 | 40 | /** 41 | * 在WebView中执行事件 42 | * 43 | * @param event 44 | * @param webView 45 | */ 46 | private static void executeEvent(final Event event, final WebView webView) { 47 | if (event == null || webView == null) { 48 | LogUtils.i("event对象为null 或者webview对象为null "); 49 | return; 50 | } 51 | event.bindView(webView); 52 | event.setSequence(getSequenceNumber()); 53 | eventList.add(event); 54 | mainExecutor.delayTimeExecute(new Runnable() { 55 | @Override 56 | public void run() { 57 | switch (event.eventType) { 58 | case Event.TYPE_CLICK: { 59 | ClickEvent clickEvent = (ClickEvent) event; 60 | webView.loadUrl(JSInjection.collectScreenInfoJS()); 61 | String js=JSInjection.findIndexElementAreaJS(clickEvent.sequence, clickEvent.elementName, 0); 62 | webView.loadUrl(js); 63 | LogUtils.i(js); 64 | } 65 | break; 66 | case Event.TYPE_INPUT: { 67 | InputEvent inputEvent = (InputEvent) event; 68 | webView.loadUrl(JSInjection.inputValueJS(inputEvent.sequence, inputEvent.elementName, inputEvent.value)); 69 | } 70 | break; 71 | case Event.TYPE_SCROLL: { 72 | ScrollEvent scrollEvent=(ScrollEvent) event; 73 | webView.loadUrl(JSInjection.scrollElementJS(scrollEvent.sequence,scrollEvent.elementName,scrollEvent.position)); 74 | } 75 | break; 76 | } 77 | } 78 | }, event.delayTime); 79 | } 80 | 81 | /** 82 | * 查找,释放掉该事件 83 | * 84 | * @param sequence 85 | */ 86 | private static Event findEventAndRelease(int sequence) { 87 | Event designation = null; 88 | for (Event event : eventList) { 89 | if (event.sequence == sequence) { 90 | designation = event; 91 | eventList.remove(event); 92 | break; 93 | } 94 | } 95 | return designation; 96 | } 97 | 98 | 99 | private static final JSBehavior.BehaviorCallBack callBack = new JSBehavior.BehaviorCallBack() { 100 | @Override 101 | public void doInputBehavior(int sequence, int result) { 102 | Event event = findEventAndRelease(sequence); 103 | if (event == null || event.eventType != Event.TYPE_INPUT) { 104 | return; 105 | } 106 | final InputEvent inputEvent = (InputEvent) event; 107 | if (inputEvent.listener == null) { 108 | return; 109 | } 110 | switch (result) { 111 | case Response.BEHAVIOR_SUCCESS: 112 | mainExecutor.execute(new Runnable() { 113 | @Override 114 | public void run() { 115 | inputEvent.listener.inputSuccess(inputEvent); 116 | } 117 | }); 118 | break; 119 | case Response.BEHAVIOR_FAILURE: 120 | mainExecutor.execute(new Runnable() { 121 | @Override 122 | public void run() { 123 | inputEvent.listener.inputFailure(inputEvent); 124 | } 125 | }); 126 | break; 127 | } 128 | } 129 | 130 | @Override 131 | public void doClickBehavior(int sequence, int result, final int top, final int left, final int width, final int height, final int sScreenInnerWidth, final int sScreenInnerHeight) { 132 | Event event = findEventAndRelease(sequence); 133 | if (event == null || event.eventType != Event.TYPE_CLICK) { 134 | return; 135 | } 136 | final ClickEvent clickEvent = (ClickEvent) event; 137 | if (clickEvent.listener == null) { 138 | return; 139 | } 140 | switch (result) { 141 | case Response.BEHAVIOR_SUCCESS: 142 | mainExecutor.execute(new Runnable() { 143 | @Override 144 | public void run() { 145 | if (clickEvent.getView() != null) { 146 | JSBehavior.handlerClickEvent(clickEvent.getView(), JSBehavior.conversionClickPoints(top, left, width, height, sScreenInnerHeight, sScreenInnerWidth, clickEvent.getView())); 147 | clickEvent.listener.clickSuccess(clickEvent); 148 | } else { 149 | clickEvent.listener.clickFailure(clickEvent); 150 | } 151 | } 152 | }); 153 | break; 154 | case Response.BEHAVIOR_FAILURE: 155 | mainExecutor.execute(new Runnable() { 156 | @Override 157 | public void run() { 158 | clickEvent.listener.clickFailure(clickEvent); 159 | } 160 | }); 161 | break; 162 | } 163 | } 164 | 165 | @Override 166 | public void doScrollBehavior(int sequence, int result, final int start_x, final int start_y, final int end_x, final int end_y, final int windowWidth, final int windowHeight) { 167 | final Event event = findEventAndRelease(sequence); 168 | if (event == null || event.eventType != Event.TYPE_SCROLL) { 169 | return; 170 | } 171 | final ScrollEvent scrollEvent = (ScrollEvent) event; 172 | if (scrollEvent.listener == null) { 173 | return; 174 | } 175 | switch (result) { 176 | case Response.BEHAVIOR_SUCCESS: 177 | mainExecutor.execute(new Runnable() { 178 | @Override 179 | public void run() { 180 | if (scrollEvent.getView()==null){ 181 | scrollEvent.listener.scrollFailure(scrollEvent); 182 | }else{ 183 | JSBehavior.handlerScrollEvent(scrollEvent,JSBehavior.conversionScrollPoints(start_x,start_y,end_x,end_y,windowHeight,windowWidth,scrollEvent.getView())); 184 | } 185 | } 186 | }); 187 | break; 188 | case Response.BEHAVIOR_FAILURE: 189 | mainExecutor.execute(new Runnable() { 190 | @Override 191 | public void run() { 192 | scrollEvent.listener.scrollFailure(scrollEvent); 193 | } 194 | }); 195 | break; 196 | } 197 | } 198 | }; 199 | 200 | public final static class JavaScriptBuilder { 201 | /** 202 | * 执行js事件 203 | * 204 | * @param event 执行的事件 205 | * @param webView 执行脚本的WebView 206 | */ 207 | public static void executeEvent(Event event, WebView webView) { 208 | JavaScript.executeEvent(event, webView); 209 | } 210 | /** 211 | * 1. 是否开启日志 212 | * 2. 添加javascript对象 213 | * 214 | * @param webView 执行脚本的WebView 215 | * @param hasLog 是否显示日志 216 | */ 217 | public static void init(WebView webView, boolean hasLog) { 218 | if (webView == null) return; 219 | LogUtils.init(hasLog); 220 | webView.getSettings().setJavaScriptEnabled(true); 221 | //添加JS交互对象 222 | webView.addJavascriptInterface(new JSBehavior(callBack), JSBehavior.JavascriptInterfaceName); 223 | } 224 | } 225 | 226 | } 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/event/ClickEvent.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.event; 2 | 3 | import android.webkit.WebView; 4 | 5 | import com.xingen.androidjslib.listener.Response; 6 | 7 | import java.lang.ref.SoftReference; 8 | 9 | /** 10 | * Author by {xinGen} 11 | * Date on 2018/9/6 10:15 12 | */ 13 | public class ClickEvent extends Event { 14 | 15 | public Response.ClickListener listener; 16 | 17 | public ClickEvent setListener(Response.ClickListener listener) { 18 | this.listener = listener; 19 | return this; 20 | } 21 | 22 | 23 | public ClickEvent setElementName(String elementName) { 24 | this.elementName = elementName; 25 | return this; 26 | } 27 | 28 | 29 | public ClickEvent setEventType(int eventType) { 30 | this.eventType = eventType; 31 | return this; 32 | } 33 | 34 | 35 | 36 | public ClickEvent setDelayTime(int delayTime) { 37 | this.delayTime = delayTime; 38 | return this; 39 | } 40 | 41 | 42 | 43 | 44 | public static ClickEvent create(){ 45 | return new ClickEvent().setEventType(Event.TYPE_CLICK); 46 | } 47 | 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/event/Event.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.event; 2 | 3 | import android.webkit.WebView; 4 | 5 | import java.lang.ref.SoftReference; 6 | 7 | /** 8 | * Author by {xinGen} 9 | * Date on 2018/9/6 10:12 10 | */ 11 | public class Event { 12 | public static final int TYPE_CLICK = 1; 13 | public static final int TYPE_SCROLL = 2; 14 | public static final int TYPE_INPUT = 3; 15 | /** 16 | * 延迟执行时间 17 | */ 18 | public int delayTime; 19 | /** 20 | * 事件类型:点击,滚动 21 | */ 22 | public int eventType; 23 | 24 | /** 25 | * 软引用的WebView的指针 26 | */ 27 | protected SoftReference softReference; 28 | /** 29 | * 序列 30 | */ 31 | public int sequence; 32 | 33 | /** 34 | * 元素名 35 | */ 36 | public String elementName; 37 | 38 | public WebView getView() { 39 | return softReference == null ? null : softReference.get(); 40 | } 41 | 42 | public Event bindView(WebView webView) { 43 | softReference = new SoftReference<>(webView); 44 | return this; 45 | } 46 | 47 | public Event setSequence(int sequence) { 48 | this.sequence = sequence; 49 | return this; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/event/InputEvent.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.event; 2 | 3 | import android.webkit.WebView; 4 | 5 | import com.xingen.androidjslib.listener.Response; 6 | 7 | import java.lang.ref.SoftReference; 8 | 9 | /** 10 | * Author by {xinGen} 11 | * Date on 2018/9/6 11:09 12 | */ 13 | public class InputEvent extends Event{ 14 | public String value; 15 | 16 | public InputEvent setValue(String value) { 17 | this.value = value; 18 | return this; 19 | } 20 | 21 | public Response.InputListener listener; 22 | 23 | public InputEvent setListener(Response.InputListener listener) { 24 | this.listener = listener; 25 | return this; 26 | } 27 | 28 | public InputEvent setElementName(String elementName) { 29 | this.elementName = elementName; 30 | return this; 31 | } 32 | 33 | 34 | public InputEvent setEventType(int eventType) { 35 | this.eventType = eventType; 36 | return this; 37 | } 38 | 39 | public InputEvent setDelayTime(int delayTime) { 40 | this.delayTime = delayTime; 41 | return this; 42 | } 43 | 44 | 45 | 46 | public static InputEvent create(){ 47 | return new InputEvent().setEventType(Event.TYPE_INPUT); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/event/ScrollEvent.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.event; 2 | 3 | import android.webkit.WebView; 4 | 5 | import com.xingen.androidjslib.listener.Response; 6 | 7 | import java.lang.ref.SoftReference; 8 | 9 | /** 10 | * Author by {xinGen} 11 | * Date on 2018/9/6 10:15 12 | */ 13 | public class ScrollEvent extends Event{ 14 | /** 15 | * 滑动时间 16 | */ 17 | public int scrollTime; 18 | 19 | public int position; 20 | 21 | public Response.ScrollListener listener; 22 | 23 | public ScrollEvent setPosition(int position) { 24 | this.position = position; 25 | return this; 26 | } 27 | 28 | public ScrollEvent setListener(Response.ScrollListener listener) { 29 | this.listener = listener; 30 | return this; 31 | } 32 | public ScrollEvent setScrollTime(int scrollTime){ 33 | this.scrollTime=scrollTime; 34 | return this; 35 | } 36 | public ScrollEvent setElementName(String elementName) { 37 | this.elementName = elementName; 38 | return this; 39 | } 40 | public ScrollEvent setEventType(int eventType) { 41 | this.eventType = eventType; 42 | return this; 43 | } 44 | 45 | public ScrollEvent setDelayTime(int delayTime) { 46 | this.delayTime = delayTime; 47 | return this; 48 | } 49 | 50 | 51 | public static ScrollEvent create(){ 52 | return new ScrollEvent().setEventType(Event.TYPE_SCROLL); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/execute/MainExecutor.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.execute; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import java.util.concurrent.Executor; 7 | 8 | 9 | /** 10 | * Author by {xinGen} 11 | * Date on 2018/9/6 10:23 12 | */ 13 | public class MainExecutor implements Executor { 14 | private Handler handler=new Handler(Looper.getMainLooper()); 15 | @Override 16 | public void execute(Runnable command) { 17 | if (command!=null){ 18 | handler.post(command); 19 | } 20 | } 21 | public void delayTimeExecute(Runnable command,int delayTime){ 22 | if (command!=null){ 23 | handler.postDelayed(command,delayTime); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/injection/JSBehavior.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.injection; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ValueAnimator; 6 | import android.os.SystemClock; 7 | import android.view.MotionEvent; 8 | import android.view.animation.LinearInterpolator; 9 | import android.webkit.JavascriptInterface; 10 | import android.webkit.WebView; 11 | 12 | import com.xingen.androidjslib.event.ScrollEvent; 13 | import com.xingen.androidjslib.utils.LogUtils; 14 | import com.xingen.androidjslib.utils.ScreenUtils; 15 | 16 | /** 17 | * Author by {xinGen} 18 | * Date on 2018/9/6 12:43 19 | */ 20 | public class JSBehavior { 21 | 22 | /** 23 | * JavascriptInterface对象的名字,这里在webview.addJavascriptInterface()传入 24 | */ 25 | public static final String JavascriptInterfaceName = "JSBehavior"; 26 | /** 27 | * js 回调接口的名字 28 | */ 29 | /** 30 | * 获取到WebView加载页面的宽高 31 | */ 32 | private static int sScreenInnerWidth; 33 | private static int sScreenInnerHeight; 34 | 35 | private BehaviorCallBack callBack; 36 | 37 | public JSBehavior(BehaviorCallBack callBack) { 38 | this.callBack = callBack; 39 | } 40 | 41 | /** 42 | * 获取到页面的宽高 43 | * 44 | * @param screenWidth 屏幕宽度 45 | * @param screenHeight 屏幕高度 46 | */ 47 | @JavascriptInterface 48 | public void setInnerScreenInfo(int screenWidth, int screenHeight) { 49 | LogUtils.i("set web inner screen width " + screenWidth + " screen height " + screenHeight); 50 | sScreenInnerWidth = screenWidth; 51 | sScreenInnerHeight = screenHeight; 52 | } 53 | 54 | @JavascriptInterface 55 | public void inputResult(int sequence, int result) { 56 | LogUtils.i("获取到输入结果" + result); 57 | if (callBack != null) { 58 | callBack.doInputBehavior(sequence, result); 59 | } 60 | } 61 | 62 | /** 63 | * 获取到元素在页面上的区域 64 | * @param sequence 序列号 65 | * @param result 执行结果 66 | * @param top 上边距 67 | * @param left 左边距 68 | * @param width 宽度 69 | * @param height 高度 70 | * @param windowWidth window的宽度 71 | * @param windowHeight window的高度 72 | */ 73 | @JavascriptInterface 74 | public void clickArea(int sequence, int result, int top, int left, int width, int height,int windowWidth,int windowHeight) { 75 | LogUtils.i(" 获取到点击区域 " + top + " " + left + " " + width + " " + height+" "+windowWidth+" "+windowHeight); 76 | if (callBack != null) { 77 | callBack.doClickBehavior(sequence, result, top, left, width, height, windowWidth, windowHeight); 78 | } 79 | } 80 | 81 | /** 82 | * 滚动到指定元素,让元素处于中间位置 83 | * 84 | * @param sequence 序列号 85 | * @param result 执行结果 86 | * @param start_x 开始点的x坐标 87 | * @param start_y 开始滚动点的y坐标 88 | * @param end_x 停止滚动点的x坐标 89 | * @param end_y 停止滚动点的y坐标 90 | * @param windowWidth window的宽度 91 | * @param windowHeight window的高度 92 | */ 93 | @JavascriptInterface 94 | public void scrollScreen(int sequence, int result, int start_x, int start_y, int end_x, int end_y, int windowWidth, int windowHeight) { 95 | LogUtils.i(" 获取滑动的起点和终点坐标 "); 96 | if (callBack != null) { 97 | callBack.doScrollBehavior(sequence, result, start_x, start_y, end_x, end_y, windowWidth, windowHeight); 98 | } 99 | } 100 | 101 | /** 102 | * 处理点击事件 103 | * @param webView 执行的webview 104 | * @param point down 和up事件点坐标 105 | */ 106 | public static void handlerClickEvent(final WebView webView, final int[] point) { 107 | long downTime = SystemClock.uptimeMillis(); 108 | long eventTime = SystemClock.uptimeMillis(); 109 | int down_x, down_y, up_x, up_y; 110 | down_x = point[0]; 111 | down_y = point[1]; 112 | up_x = point[2]; 113 | up_y = point[3]; 114 | MotionEvent event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, down_x, down_y, 0); 115 | webView.dispatchTouchEvent(event); 116 | event.recycle(); 117 | eventTime += ScreenUtils.random(100); 118 | event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, up_x, up_y, 0); 119 | webView.dispatchTouchEvent(event); 120 | event.recycle(); 121 | } 122 | 123 | /** 124 | * 处理滑动事件 125 | * 126 | * @param scrollEvent 滚动事件 127 | * @param point 滚动起始点和终止点的四个坐标 128 | */ 129 | public static void handlerScrollEvent(final ScrollEvent scrollEvent, final int[] point) { 130 | final WebView webView = scrollEvent.getView(); 131 | if (webView == null) return; 132 | final ValueAnimator scrollAnimator = ValueAnimator.ofFloat(0, 1f); 133 | final long downTime = SystemClock.currentThreadTimeMillis(); 134 | scrollAnimator.setInterpolator(new LinearInterpolator()); 135 | scrollAnimator.setDuration(scrollEvent.scrollTime); 136 | final int start_x = point[0]; 137 | final int start_y = point[1]; 138 | final int end_x = point[2]; 139 | final int end_y = point[3]; 140 | scrollAnimator.addListener(new AnimatorListenerAdapter() { 141 | @Override 142 | public void onAnimationStart(Animator animation) { 143 | super.onAnimationStart(animation); 144 | disPatchEvent(webView, start_x, start_y, MotionEvent.ACTION_DOWN, downTime); 145 | } 146 | 147 | @Override 148 | public void onAnimationEnd(Animator animation) { 149 | super.onAnimationEnd(animation); 150 | disPatchEvent(webView, end_x, end_y, MotionEvent.ACTION_UP, downTime); 151 | if (scrollEvent.listener != null) { 152 | scrollEvent.listener.scrollEnd(scrollEvent); 153 | } 154 | } 155 | }); 156 | scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 157 | @Override 158 | public void onAnimationUpdate(ValueAnimator animation) { 159 | float fraction = animation.getAnimatedFraction(); 160 | int move_x = (int) (start_x + (end_x - start_x) * fraction); 161 | int move_y = (int) (start_y + (end_y - start_y) * fraction); 162 | disPatchEvent(webView, move_x, move_y, MotionEvent.ACTION_MOVE, downTime); 163 | } 164 | }); 165 | scrollAnimator.start(); 166 | } 167 | 168 | private static final void disPatchEvent(WebView webView, int x, int y, int eventType, long downTime) { 169 | if (webView == null) return; 170 | long time = SystemClock.uptimeMillis(); 171 | MotionEvent event = MotionEvent.obtain(downTime, time, eventType, x, y, 0); 172 | webView.dispatchTouchEvent(event); 173 | event.recycle(); 174 | } 175 | 176 | public static int[] conversionScrollPoints(int start_x, int start_y, int end_x, int end_y, int sScreenInnerHeight, int sScreenInnerWidth, WebView webView) { 177 | int[] points = new int[4]; 178 | points[0] = ScreenUtils.converseWebScreenPixel(start_x, sScreenInnerWidth, webView.getWidth()); 179 | points[1] = ScreenUtils.converseWebScreenPixel(start_y, sScreenInnerHeight, webView.getHeight()); 180 | points[2] = ScreenUtils.converseWebScreenPixel(end_x, sScreenInnerWidth, webView.getWidth()); 181 | points[3] = ScreenUtils.converseWebScreenPixel(end_y, sScreenInnerHeight, webView.getHeight()); 182 | return points; 183 | } 184 | 185 | /** 186 | * 1.将页面上的坐标转换成手机屏幕上的坐标 187 | * 2.然后在区域内选取动作点 188 | * @param top 上边距 189 | * @param left 左边距 190 | * @param width 元素的宽度 191 | * @param height 元素的高度 192 | * @param sScreenInnerHeight 页面高度 193 | * @param sScreenInnerWidth 页面宽度 194 | * @param webView 执行的WebView 195 | * @return 点的屏幕坐标 196 | */ 197 | public static int[] conversionClickPoints(int top, int left, int width, int height, int sScreenInnerHeight, int sScreenInnerWidth, WebView webView) { 198 | //先转换成屏幕上的坐标点 199 | top = ScreenUtils.converseWebScreenPixel(top, sScreenInnerHeight, webView.getHeight()); 200 | left = ScreenUtils.converseWebScreenPixel(left, sScreenInnerWidth, webView.getWidth()); 201 | width = ScreenUtils.converseWebScreenPixel(width, sScreenInnerWidth, webView.getWidth()); 202 | height = ScreenUtils.converseWebScreenPixel(height, sScreenInnerHeight, webView.getWidth()); 203 | LogUtils.i(" 转换后的手机屏幕坐标点 " + top + " " + left + " " + width + " " + height); 204 | //获取到中心点位置 205 | int[] points = new int[4]; 206 | int down_x, down_y, up_x, up_y; 207 | down_x = left + width / 2; 208 | down_y = top + height / 2; 209 | up_x = down_x + ScreenUtils.random(5); 210 | up_y = down_y + ScreenUtils.random(5); 211 | points[0] = down_x; 212 | points[1] = down_y; 213 | points[2] = up_x; 214 | points[3] = up_y; 215 | return points; 216 | } 217 | 218 | /** 219 | * 行为回调 220 | */ 221 | public interface BehaviorCallBack { 222 | void doInputBehavior(int sequence, int result); 223 | 224 | void doClickBehavior(int sequence, int result, int top, int left, int width, int height, int sScreenInnerWidth, int sScreenInnerHeight); 225 | 226 | void doScrollBehavior(int sequence, int result, int start_x, int start_y, int end_x, int end_y, int windowWidth, int windowHeight); 227 | } 228 | 229 | } 230 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/injection/JSInjection.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.injection; 2 | 3 | import com.xingen.androidjslib.listener.Response; 4 | import com.xingen.androidjslib.utils.LogUtils; 5 | 6 | /** 7 | * Author by {xinGen} 8 | * Date on 2018/9/6 10:19 9 | */ 10 | public class JSInjection { 11 | 12 | public static String collectScreenInfoJS() { 13 | LogUtils.i("collectScreenInfoJs"); 14 | return "javascript:(function() {" 15 | + " JSBehavior.setInnerScreenInfo(document.documentElement.clientWidth, document.documentElement.clientHeight);" 16 | + "})()"; 17 | } 18 | 19 | /** 20 | * 获取到指定元素的区域范围 21 | * @param sequence 序列号 22 | * @param className 元素的css选择器 23 | * @param index 角标 24 | * @return 执行的js脚本 25 | */ 26 | public static String findIndexElementAreaJS(int sequence, String className, int index) { 27 | LogUtils.i(" findIndexElementArea " + className); 28 | return "javascript: (function() {" 29 | +" function getTop(e) {" 30 | +" var offset = e.offsetTop;" 31 | +" if (e.offsetParent != null) {" 32 | +" offset += getTop(e.offsetParent);" 33 | +" }" 34 | +" return offset;" 35 | +" }" 36 | +" function findItem(e, i) {" 37 | +" if (e) {" 38 | +" if (e.length > i) {" 39 | +" var item = e[i];" 40 | +" if (item.clientWidth > 0 && item.clientHeight > 0) {" 41 | +" return item;" 42 | +" }" 43 | +" }" 44 | +" }" 45 | +" return null;" 46 | +" }" 47 | +" function getLeft(e) {" 48 | +" var offset = e.offsetLeft;" 49 | +" if (e.offsetParent != null) {" 50 | +" offset += getLeft(e.offsetParent);" 51 | +" }" 52 | +" return offset;" 53 | +" }" 54 | +" function random(min, max) {" 55 | +" return Math.round(Math.random() * (max - min)) + min;" 56 | +" }" 57 | +" var elementList = document.querySelectorAll("+className+");" 58 | +" if (elementList) {" 59 | +" var index = "+index+";" 60 | +" if (index >= elementList.length) {" 61 | +" index = random(0, elementList.length - 1);" 62 | +" }" 63 | +" var element = elementList[index];" 64 | +" if (element) {" 65 | +" var paddingTop = getTop(element);" 66 | +" var paddingleft = getLeft(element);" 67 | +" var windowHeight = window.outerHeight;" 68 | +" var windowWidth = window.outerWidth;" 69 | +" if (paddingTop >= windowHeight) {" 70 | +" window.scrollTo(0, paddingTop - windowHeight);" 71 | +" window.scrollBy(0, Math.floor(windowHeight / 2));" 72 | +" var scrollDistance = window.scrollY;" 73 | +" JSBehavior.clickArea("+sequence+", "+Response.BEHAVIOR_SUCCESS+", paddingTop - scrollDistance, paddingleft, element.clientWidth, element.clientHeight, windowWidth, windowHeight);" 74 | +" } else {" 75 | +" window.scrollTo(0, 0);" 76 | +" JSBehavior.clickArea("+sequence+", "+Response.BEHAVIOR_SUCCESS+", paddingTop, paddingleft, element.clientWidth, element.clientHeight, windowWidth, windowHeight);" 77 | +" }" 78 | +" } else {" 79 | +" JSBehavior.clickArea("+sequence+", "+Response.BEHAVIOR_FAILURE+", 0, 0, 0, 0, 0, 0);" 80 | +" }" 81 | +" } else {" 82 | +" JSBehavior.clickArea("+sequence+", "+Response.BEHAVIOR_FAILURE+", 0, 0, 0, 0, 0, 0);" 83 | +" }" 84 | +"})()"; 85 | } 86 | 87 | /** 88 | * 滚动到指定元素,让该元素处于界面中间位置 89 | * @param sequence 序列号 90 | * @param elementName 元素的css选择器 91 | * @param index 角标 92 | * @return js 脚本 93 | */ 94 | public static String scrollElementJS(int sequence,String elementName,int index ){ 95 | return "javascript: (function() {" 96 | + " function getTop(e) {" 97 | +" var offset = e.offsetTop;" 98 | +" if (e.offsetParent != null) {" 99 | +" offset += getTop(e.offsetParent);" 100 | +" }" 101 | +" return offset;" 102 | +" }" 103 | +" function findItem(e, i) {" 104 | +" if (e) {" 105 | +" if (e.length > i) {" 106 | +" var item = e[i];" 107 | +" if (item.clientWidth > 0 && item.clientHeight > 0) {" 108 | +" return item;" 109 | +" }" 110 | +" }" 111 | +" }" 112 | +" return null;" 113 | +" }" 114 | +" function random(min, max) {" 115 | +" return Math.round(Math.random() * (max - min)) + min;" 116 | +" }" 117 | +" var elementList = document.querySelectorAll("+elementName+");" 118 | +" if (elementList) {" 119 | +" var index = "+index+";" 120 | +" if (index >= elementList.length) {" 121 | +" index = random(0, elementList.length - 1);" 122 | +" }" 123 | +" var element = elementList[index];" 124 | +" if (element) {" 125 | +" var paddingTop = getTop(element);" 126 | +" var windowHeight = window.outerHeight;" 127 | +" var windowWidth = window.outerWidth;" 128 | +" var start_x, start_y, end_x, end_y;" 129 | +" if (paddingTop > windowHeight) {" 130 | +" window.scrollTo(0, paddingTop - windowHeight);" 131 | +" start_x = windowWidth / 2 + random(5, 10);" 132 | +" end_x = start_x;" 133 | +" start_y = Math.floor(windowHeight * 0.75) - random(1, 10);" 134 | +" end_y = start_y - Math.floor(windowHeight / 2);" 135 | +" JSBehavior.scrollScreen("+sequence+", "+Response.BEHAVIOR_SUCCESS+", start_x, start_y, end_x, end_y, windowWidth, windowHeight);" 136 | +" } else {" 137 | +" var scrollDistance = window.scrollY;" 138 | +" if (paddingTop < scrollDistance) {" 139 | +" window.scrollTo(0, paddingTop);" 140 | +" start_x = windowWidth / 2 + random(5, 10);" 141 | +" end_x = start_x;" 142 | +" start_y = Math.floor(windowHeight * 0.25) + random(1, 10);" 143 | +" end_y = start_y + Math.floor(windowHeight / 2);" 144 | +" JSBehavior.scrollScreen("+sequence+", "+Response.BEHAVIOR_SUCCESS+", start_x, start_y, end_x, end_y, windowWidth, windowHeight);" 145 | +" } else {" 146 | +" if (paddingTop <= Math.floor(windowHeight / 2)) {" 147 | +" start_x = windowWidth / 2 + random(5, 10);" 148 | +" end_x = start_x;" 149 | +" start_y = Math.floor(windowHeight * 0.25) + random(1, 10);" 150 | +" end_y = start_ + scrollDistance;" 151 | +" JSBehavior.scrollScreen("+sequence+", "+Response.BEHAVIOR_SUCCESS+", start_x, start_y, end_x, end_y, windowWidth, windowHeight);" 152 | +" } else {" 153 | +" window.scrollTo(0, 0);" 154 | +" start_x = windowWidth / 2 + random(5, 10);" 155 | +" end_x = start_x;" 156 | +" start_y = Math.floor(windowHeight * 0.75) - random(1, 10);" 157 | +" end_y = start_y - (paddingTop - Math.floor(windowHeight / 2));" 158 | +" JSBehavior.scrollScreen("+sequence+", "+Response.BEHAVIOR_SUCCESS+", start_x, start_y, end_x, end_y, windowWidth, windowHeight);" 159 | +" }" 160 | +" }" 161 | +" }" 162 | +" } else {" 163 | +" JSBehavior.scrollScreen("+sequence+", "+Response.BEHAVIOR_FAILURE+", 0, 0, 0, 0, 0, 0);" 164 | +" }" 165 | +" } else {" 166 | +" JSBehavior.scrollScreen("+sequence+", "+Response.BEHAVIOR_FAILURE+", 0, 0, 0, 0, 0, 0);" 167 | +" }" 168 | +"})()"; 169 | } 170 | /** 171 | * 往元素中设置值 172 | * @param sequence 序列号 173 | * @param elementName 元素的css选择器 174 | * @param value 输入的值 175 | * @return 执行的js脚本 176 | */ 177 | public static String inputValueJS(int sequence, String elementName, String value) { 178 | LogUtils.i(" inputValue " + elementName + " " + value); 179 | return "javascript: (function() {" 180 | + " var element = document.querySelector(" + elementName + ");" 181 | + " if (element) {" 182 | + " element.value = " + "\"" + value + "\"" + ";" 183 | + " JSBehavior.inputResult("+sequence+", " + Response.BEHAVIOR_SUCCESS + ");" 184 | + " } else {" 185 | + " JSBehavior.inputResult("+sequence+", " + Response.BEHAVIOR_FAILURE + ");" 186 | + " }" 187 | + "})()"; 188 | 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/listener/Response.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.listener; 2 | 3 | import com.xingen.androidjslib.event.ClickEvent; 4 | import com.xingen.androidjslib.event.InputEvent; 5 | import com.xingen.androidjslib.event.ScrollEvent; 6 | 7 | /** 8 | * Author by {xinGen} 9 | * Date on 2018/9/6 09:47 10 | */ 11 | public interface Response { 12 | /** 13 | * 成功,执行行为 14 | */ 15 | int BEHAVIOR_SUCCESS = 1; 16 | /** 17 | * 失败,执行行为 18 | */ 19 | int BEHAVIOR_FAILURE = 2; 20 | 21 | /** 22 | * 点击事件监听器 23 | */ 24 | interface ClickListener { 25 | void clickFailure(ClickEvent clickEvent); 26 | 27 | void clickSuccess(ClickEvent clickEvent); 28 | } 29 | 30 | /** 31 | * 滚动事件监听器 32 | */ 33 | interface ScrollListener { 34 | void scrollEnd(ScrollEvent scrollEvent); 35 | 36 | void scrollFailure(ScrollEvent scrollEvent); 37 | } 38 | 39 | /** 40 | * 输入框监听器 41 | */ 42 | interface InputListener { 43 | void inputFailure(InputEvent inputEvent); 44 | 45 | void inputSuccess(InputEvent inputEvent); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/utils/LogUtils.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.utils; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Author by {xinGen} 7 | * Date on 2018/9/6 10:02 8 | */ 9 | public class LogUtils { 10 | public static boolean shoLog=true; 11 | public static final String DEFAULT_TAG="JavaScript"; 12 | public static void init(boolean open){ 13 | shoLog=open; 14 | } 15 | public static void i(String content){ 16 | i(DEFAULT_TAG,content); 17 | } 18 | public static void i(String tag,String content){ 19 | if (shoLog){ 20 | Log.i(tag,content); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /androidjslib/src/main/java/com/xingen/androidjslib/utils/ScreenUtils.java: -------------------------------------------------------------------------------- 1 | package com.xingen.androidjslib.utils; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Author by {xinGen} 7 | * Date on 2018/9/6 10:01 8 | */ 9 | public class ScreenUtils { 10 | /** 11 | * 12 | * @param pointPixel 点的px值 13 | * @param screenPixel 页面的px值 14 | * @param phonePixel 屏幕的px值 15 | * @return 转换后屏幕上的px值 16 | */ 17 | public static int converseWebScreenPixel(int pointPixel, int screenPixel, int phonePixel) { 18 | return (int) (pointPixel * 1f / screenPixel * phonePixel); 19 | } 20 | 21 | /** 22 | * 23 | * @param max 最大值 24 | * @return 随机值 25 | */ 26 | public static int random(int max) { 27 | if (max < 0) { 28 | return 0; 29 | } 30 | Random random = new Random(); 31 | return random.nextInt(max); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.xingen.app" 7 | minSdkVersion 14 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:27.1.1' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | implementation project(':androidjslib') 29 | implementation project(':WebViewManager') 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/xingen/app/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.xingen.app; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xingen.app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/xingen/app/App.java: -------------------------------------------------------------------------------- 1 | package com.xingen.app; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * Author by ${HeXinGen}, Date on 2018/10/1. 7 | */ 8 | public class App extends Application { 9 | private static App instance; 10 | 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | instance=this; 15 | loadWebViewManager(); 16 | } 17 | private void loadWebViewManager(){ 18 | WebViewManagerTest.test(this); 19 | } 20 | public static App getInstance(){ 21 | return instance; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/xingen/app/InputAndClickTestActivity.java: -------------------------------------------------------------------------------- 1 | package com.xingen.app; 2 | 3 | import android.net.http.SslError; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.util.Log; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | import android.webkit.SslErrorHandler; 11 | import android.webkit.WebSettings; 12 | import android.webkit.WebView; 13 | import android.webkit.WebViewClient; 14 | import android.widget.Toast; 15 | 16 | import com.xingen.androidjslib.JavaScript; 17 | import com.xingen.androidjslib.event.ClickEvent; 18 | import com.xingen.androidjslib.event.Event; 19 | import com.xingen.androidjslib.event.InputEvent; 20 | import com.xingen.androidjslib.listener.Response; 21 | import com.xingen.androidjslib.utils.LogUtils; 22 | 23 | public class InputAndClickTestActivity extends AppCompatActivity { 24 | private static final String TAG = "MainActivity"; 25 | private WebView webView; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | webView = new WebView(this); 31 | setWebViewConfig(); 32 | setContentView(webView); 33 | webView.loadUrl("https://www.baidu.com/"); 34 | } 35 | 36 | private boolean isFirst = true; 37 | 38 | private void setWebViewConfig() { 39 | WebSettings webSetting = webView.getSettings(); 40 | webSetting.setJavaScriptEnabled(true); 41 | webSetting.setDomStorageEnabled(true); 42 | webSetting.setGeolocationEnabled(false); 43 | webSetting.setSupportZoom(false); 44 | webSetting.setBuiltInZoomControls(false); 45 | webSetting.setUseWideViewPort(true); 46 | webSetting.setSupportMultipleWindows(false); 47 | webSetting.setLoadsImagesAutomatically(true); 48 | JavaScript.JavaScriptBuilder.init(webView, true); 49 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 50 | try { 51 | webView.getSettings().setMixedContentMode( 52 | WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 53 | } catch (Throwable e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | webView.setWebViewClient(new WebViewClient() { 58 | /** 59 | * WebView ssl 访问证书出错,handler.cancel()取消加载,handler.proceed()对然错误也继续加载 60 | * 61 | * @param view 62 | * @param handler 63 | * @param error 64 | */ 65 | @Override 66 | public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 67 | handler.proceed(); 68 | } 69 | 70 | @Override 71 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 72 | super.onReceivedError(view, errorCode, description, failingUrl); 73 | LogUtils.i(" " + errorCode + " " + description + " " + failingUrl); 74 | } 75 | 76 | @Override 77 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 78 | view.loadUrl(url); 79 | return true; 80 | } 81 | 82 | @Override 83 | public void onPageFinished(WebView view, String url) { 84 | super.onPageFinished(view, url); 85 | Log.i(TAG, " 页面加载完成 " + url); 86 | if (isFirst) { 87 | isFirst = false; 88 | doFirstJs(); 89 | } else { 90 | 91 | } 92 | } 93 | }); 94 | webView.setOnTouchListener(new View.OnTouchListener() { 95 | @Override 96 | public boolean onTouch(View v, MotionEvent event) { 97 | LogUtils.i(" WebView touch 事件 " + event.getX() + " " + event.getY()); 98 | return false; 99 | } 100 | }); 101 | } 102 | 103 | private void doFirstJs() { 104 | String elementName = "\"input.se-input\""; 105 | InputEvent inputEvent = InputEvent.create() 106 | .setElementName(elementName) 107 | .setDelayTime(1000) 108 | .setValue("新根") 109 | .setListener(new Response.InputListener() { 110 | @Override 111 | public void inputFailure(InputEvent inputEvent) { 112 | String content = "输入失败"; 113 | Toast.makeText(getApplicationContext(), content, Toast.LENGTH_SHORT).show(); 114 | Log.i(TAG, " input 输入失败 "); 115 | } 116 | 117 | @Override 118 | public void inputSuccess(InputEvent inputEvent) { 119 | Toast.makeText(getApplicationContext(), "输入成功", Toast.LENGTH_SHORT).show(); 120 | Log.i(TAG, " input 输入成功"); 121 | } 122 | }); 123 | JavaScript.JavaScriptBuilder.executeEvent(inputEvent, webView); 124 | ClickEvent clickEvent = ClickEvent.create() 125 | .setDelayTime(5000) 126 | .setElementName("\"button.se-bn\"") 127 | .setListener(new Response.ClickListener() { 128 | @Override 129 | public void clickFailure(ClickEvent clickEvent) { 130 | Toast.makeText(getApplicationContext(), "点击失败", Toast.LENGTH_SHORT).show(); 131 | Log.i(TAG, " click点击失败 "); 132 | } 133 | 134 | @Override 135 | public void clickSuccess(ClickEvent clickEvent) { 136 | Toast.makeText(getApplicationContext(), "点击成功", Toast.LENGTH_SHORT).show(); 137 | Log.i(TAG, " click点击成功 "); 138 | } 139 | }); 140 | JavaScript.JavaScriptBuilder.executeEvent(clickEvent, webView); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/com/xingen/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.xingen.app; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.webkit.WebView; 8 | 9 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 10 | private WebView webView; 11 | private static final String TAG = "MainActivity"; 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | initView(); 18 | } 19 | 20 | private void initView() { 21 | findViewById(R.id.main_test_input_click_btn).setOnClickListener(this); 22 | findViewById(R.id.main_test_scroll_btn).setOnClickListener(this); 23 | } 24 | 25 | 26 | @Override 27 | public void onClick(View v) { 28 | switch (v.getId()) { 29 | case R.id.main_test_input_click_btn: { 30 | Intent intent = new Intent(this, InputAndClickTestActivity.class); 31 | startActivity(intent); 32 | } 33 | break; 34 | case R.id.main_test_scroll_btn: { 35 | 36 | Intent intent = new Intent(this, ScrollTestActivity.class); 37 | startActivity(intent); 38 | } 39 | 40 | break; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/xingen/app/ScrollTestActivity.java: -------------------------------------------------------------------------------- 1 | package com.xingen.app; 2 | 3 | import android.net.http.SslError; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.util.Log; 9 | import android.view.MotionEvent; 10 | import android.view.View; 11 | import android.webkit.SslErrorHandler; 12 | import android.webkit.WebSettings; 13 | import android.webkit.WebView; 14 | import android.webkit.WebViewClient; 15 | 16 | import com.xingen.androidjslib.JavaScript; 17 | import com.xingen.androidjslib.event.Event; 18 | import com.xingen.androidjslib.event.ScrollEvent; 19 | import com.xingen.androidjslib.listener.Response; 20 | import com.xingen.androidjslib.utils.LogUtils; 21 | 22 | /** 23 | * Author by {xinGen} 24 | * Date on 2018/9/6 17:47 25 | */ 26 | public class ScrollTestActivity extends AppCompatActivity { 27 | private WebView webView; 28 | private static final String TAG = ScrollTestActivity.class.getSimpleName(); 29 | 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | webView = new WebView(this); 34 | setWebViewConfig(); 35 | setContentView(webView); 36 | webView.loadUrl("https://blog.csdn.net/hexingen"); 37 | } 38 | 39 | private boolean isFirst = true; 40 | 41 | private void setWebViewConfig() { 42 | WebSettings webSetting = webView.getSettings(); 43 | webSetting.setJavaScriptEnabled(true); 44 | webSetting.setDomStorageEnabled(true); 45 | webSetting.setGeolocationEnabled(false); 46 | webSetting.setSupportZoom(false); 47 | webSetting.setBuiltInZoomControls(false); 48 | webSetting.setUseWideViewPort(true); 49 | webSetting.setSupportMultipleWindows(false); 50 | webSetting.setLoadsImagesAutomatically(true); 51 | JavaScript.JavaScriptBuilder.init(webView, true); 52 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 53 | try { 54 | webView.getSettings().setMixedContentMode( 55 | WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 56 | } catch (Throwable e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | webView.setWebViewClient(new WebViewClient() { 61 | /** 62 | * WebView ssl 访问证书出错,handler.cancel()取消加载,handler.proceed()对然错误也继续加载 63 | * 64 | * @param view 65 | * @param handler 66 | * @param error 67 | */ 68 | @Override 69 | public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 70 | handler.proceed(); 71 | } 72 | 73 | @Override 74 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 75 | super.onReceivedError(view, errorCode, description, failingUrl); 76 | LogUtils.i(" " + errorCode + " " + description + " " + failingUrl); 77 | } 78 | 79 | @Override 80 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 81 | view.loadUrl(url); 82 | return true; 83 | } 84 | 85 | @Override 86 | public void onPageFinished(WebView view, String url) { 87 | super.onPageFinished(view, url); 88 | if (isFirst) { 89 | isFirst = false; 90 | doScroll("\"ul.colu_author_c>li\"", 8, 1000); 91 | doScroll("\"ul.colu_author_c>li\"", 0, 5000); 92 | } 93 | } 94 | }); 95 | webView.setOnTouchListener(new View.OnTouchListener() { 96 | @Override 97 | public boolean onTouch(View v, MotionEvent event) { 98 | LogUtils.i(" WebView touch 事件 " + event.getX() + " " + event.getY()); 99 | return false; 100 | } 101 | }); 102 | } 103 | 104 | 105 | public void doScroll(String elementName, int index, int delayTime) { 106 | ScrollEvent scrollEvent = ScrollEvent.create() 107 | .setElementName(elementName) 108 | .setDelayTime(delayTime) 109 | .setPosition(index) 110 | .setScrollTime(2000) 111 | .setListener(new Response.ScrollListener() { 112 | @Override 113 | public void scrollEnd(ScrollEvent scrollEvent) { 114 | Log.i(TAG, " 滚动事件结束 "); 115 | } 116 | 117 | @Override 118 | public void scrollFailure(ScrollEvent scrollEvent) { 119 | Log.i(TAG, " 滚动事件失败 "); 120 | } 121 | }); 122 | JavaScript.JavaScriptBuilder.executeEvent(scrollEvent, webView); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/com/xingen/app/WebViewManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.xingen.app; 2 | 3 | import android.content.Context; 4 | import android.net.http.SslError; 5 | import android.support.annotation.Nullable; 6 | import android.util.Log; 7 | import android.webkit.SslErrorHandler; 8 | import android.webkit.WebResourceResponse; 9 | import android.webkit.WebView; 10 | import android.webkit.WebViewClient; 11 | 12 | import com.xingen.webviewmanager.WebViewManager; 13 | 14 | /** 15 | * Author by ${HeXinGen}, Date on 2018/10/1. 16 | */ 17 | public class WebViewManagerTest { 18 | private static final String TAG = WebViewManagerTest.class.getSimpleName(); 19 | private static WebViewManager webViewManager=new WebViewManager(); 20 | 21 | public static void test(Context context) { 22 | //进行初始化,传入false,则视图不显示 23 | webViewManager.init(context, false); 24 | WebView webView = webViewManager.addWebView(); 25 | webView.setWebViewClient(new WebViewClient() { 26 | @Override 27 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 28 | view.loadUrl(url); 29 | return true; 30 | } 31 | @Override 32 | public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 33 | handler.proceed(); 34 | } 35 | @Override 36 | public void onPageFinished(WebView view, String url) { 37 | super.onPageFinished(view, url); 38 | Log.i(TAG, " 1px透明悬浮窗 技术加载 页面的url是:" + url); 39 | } 40 | }); 41 | webView.loadUrl("https://github.com/13767004362"); 42 | } 43 | 44 | public static void destroy(){ 45 | webViewManager.quit(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 |