├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── 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 │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── fragment_test.xml │ │ │ │ ├── activity_weber.xml │ │ │ │ ├── activity_x5_web.xml │ │ │ │ ├── activity_web.xml │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── assets │ │ │ └── webpage │ │ │ │ ├── hitTestResult.html │ │ │ │ ├── websocket.html │ │ │ │ ├── fileChooser.html │ │ │ │ ├── fullscreenVideo.html │ │ │ │ └── jsbridge.html │ │ ├── java │ │ │ └── vip │ │ │ │ └── ruoyun │ │ │ │ └── webviewhelper │ │ │ │ ├── weber │ │ │ │ ├── TestFragment.java │ │ │ │ ├── X5CanDo.java │ │ │ │ ├── Test.java │ │ │ │ └── WeBer.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── App.java │ │ │ │ ├── helper │ │ │ │ ├── BaseWebViewClient.java │ │ │ │ ├── BaseJavascriptInterface.java │ │ │ │ ├── WebViewOpenImageCore.java │ │ │ │ └── WebViewHelper.java │ │ │ │ ├── PlayVideoFunc.java │ │ │ │ ├── WebActivity.java │ │ │ │ └── WeberActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── vip │ │ │ └── ruoyun │ │ │ └── webviewhelper │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── vip │ │ └── ruoyun │ │ └── webviewhelper │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro ├── build.gradle └── proguard-rules-x5-web.pro ├── weber-test ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── java │ │ │ └── vip │ │ │ │ └── ruoyun │ │ │ │ └── weber │ │ │ │ └── test │ │ │ │ ├── WeBerFileProvider.java │ │ │ │ ├── WeBerBridge.java │ │ │ │ ├── Test.java │ │ │ │ ├── WeBerWebChormeClient.java │ │ │ │ ├── WeBerWebClient.java │ │ │ │ ├── WeBerView.java │ │ │ │ ├── generate │ │ │ │ └── WeBerBridgeImp.java │ │ │ │ ├── WeBerChromeClient.java │ │ │ │ ├── WeBerDemo.java │ │ │ │ └── WeBer.java │ │ ├── AndroidManifest.xml │ │ └── assets │ │ │ ├── javascript.html │ │ │ └── 02.html │ ├── test │ │ └── java │ │ │ └── vip │ │ │ └── ruoyun │ │ │ └── weber │ │ │ └── test │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── vip │ │ └── ruoyun │ │ └── weber │ │ └── test │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── weber-core ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── xml │ │ │ │ └── weber_file_paths.xml │ │ ├── java │ │ │ └── vip │ │ │ │ └── ruoyun │ │ │ │ └── webkit │ │ │ │ ├── WeBerFileProvider.java │ │ │ │ ├── WeBer.java │ │ │ │ └── WeBerChromeClient.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── vip │ │ │ └── ruoyun │ │ │ └── webkit │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── vip │ │ └── ruoyun │ │ └── webkit │ │ └── ExampleInstrumentedTest.java ├── consumer-rules.pro ├── jcenter.gradle ├── proguard-rules.pro ├── build.gradle └── README.md ├── weber-x5-core ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ └── xml │ │ │ │ └── weber_file_paths.xml │ │ ├── java │ │ │ └── vip │ │ │ │ └── ruoyun │ │ │ │ └── webkit │ │ │ │ └── x5 │ │ │ │ ├── WeBerFileProvider.java │ │ │ │ ├── WeBerViewClient.java │ │ │ │ ├── WeBerView.java │ │ │ │ ├── WeBer.java │ │ │ │ └── WeBerChromeClient.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── vip │ │ │ └── ruoyun │ │ │ └── webkit │ │ │ └── x5 │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── vip │ │ └── ruoyun │ │ └── webkit │ │ └── x5 │ │ └── ExampleInstrumentedTest.java ├── jcenter.gradle ├── proguard-rules.pro ├── build.gradle └── consumer-rules.pro ├── weber-x5-jsbridge ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── vip │ │ │ │ └── ruoyun │ │ │ │ └── webkit │ │ │ │ └── x5 │ │ │ │ └── jsbridge │ │ │ │ ├── WebViewJavascriptBridge.java │ │ │ │ ├── BridgeHandler.java │ │ │ │ ├── DefaultHandler.java │ │ │ │ ├── Message.java │ │ │ │ └── BridgeUtil.java │ │ └── assets │ │ │ └── WeBerViewJsBridge.js │ ├── test │ │ └── java │ │ │ └── vip │ │ │ └── ruoyun │ │ │ └── webkit │ │ │ └── x5 │ │ │ └── jsbridge │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── vip │ │ └── ruoyun │ │ └── webkit │ │ └── x5 │ │ └── jsbridge │ │ └── ExampleInstrumentedTest.java ├── README.md ├── jcenter.gradle ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /weber-test/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /weber-core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /weber-x5-core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /weber-x5-jsbridge/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':weber-test' 2 | include ':app', ':weber-core', ':weber-x5-core', ':weber-x5-jsbridge' 3 | rootProject.name='WebViewHelper' 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugyun/WeBer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /weber-x5-jsbridge/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /weber-test/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | #重点坑:针对Android4.4,系统把openFileChooser方法去掉了,不混淆openFileChooser() 2 | -keepclassmembers class * extends android.webkit.WebChromeClient{ 3 | public void openFileChooser(...); 4 | } 5 | 6 | -------------------------------------------------------------------------------- /weber-core/src/main/res/xml/weber_file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /weber-x5-core/src/main/res/xml/weber_file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | WebViewHelper 3 | 4 | 5 | Hello blank fragment 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | .idea -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 25 17:30:47 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /weber-core/src/main/java/vip/ruoyun/webkit/WeBerFileProvider.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit; 2 | 3 | import android.support.v4.content.FileProvider; 4 | 5 | /** 6 | * Created by ruoyun on 2019-09-19. 7 | * Author:若云 8 | * Mail:zyhdvlp@gmail.com 9 | * Depiction: 10 | */ 11 | public class WeBerFileProvider extends FileProvider { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /weber-test/src/main/java/vip/ruoyun/weber/test/WeBerFileProvider.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import android.support.v4.content.FileProvider; 4 | 5 | /** 6 | * Created by ruoyun on 2019-09-19. 7 | * Author:若云 8 | * Mail:zyhdvlp@gmail.com 9 | * Depiction: 10 | */ 11 | public class WeBerFileProvider extends FileProvider { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /weber-x5-core/src/main/java/vip/ruoyun/webkit/x5/WeBerFileProvider.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5; 2 | 3 | import android.support.v4.content.FileProvider; 4 | 5 | /** 6 | * Created by ruoyun on 2019-09-19. 7 | * Author:若云 8 | * Mail:zyhdvlp@gmail.com 9 | * Depiction: 10 | */ 11 | public class WeBerFileProvider extends FileProvider { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /weber-x5-jsbridge/README.md: -------------------------------------------------------------------------------- 1 | 2 | # WeBer jsBridge 3 | 4 | ## H5开发体验 5 | 6 | h5 的使用方法和 ios 的 [WebViewJavascriptBridge](https://github.com/marcuswestin/WebViewJavascriptBridge) 库的一致,h5 在开发中只要实现一套代码就可以和 ios android 进行通信。 7 | 8 | ## 使用 9 | 10 | ```groovy 11 | implementation 'vip.ruoyun.webkit:weber-x5-jsbridge:1.0.2' 12 | ``` 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /weber-x5-jsbridge/src/main/java/vip/ruoyun/webkit/x5/jsbridge/WebViewJavascriptBridge.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5.jsbridge; 2 | 3 | 4 | import com.tencent.smtt.sdk.ValueCallback; 5 | 6 | 7 | public interface WebViewJavascriptBridge { 8 | void send(String data); 9 | 10 | void send(String data, ValueCallback responseCallback); 11 | } 12 | -------------------------------------------------------------------------------- /weber-x5-jsbridge/src/main/java/vip/ruoyun/webkit/x5/jsbridge/BridgeHandler.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5.jsbridge; 2 | 3 | import com.tencent.smtt.sdk.ValueCallback; 4 | 5 | /** 6 | * Created by ruoyun on 2019-07-08. 7 | * Author:若云 8 | * Mail:zyhdvlp@gmail.com 9 | * Depiction: 10 | */ 11 | public interface BridgeHandler { 12 | void handler(String data, ValueCallback valueCallback); 13 | } 14 | -------------------------------------------------------------------------------- /weber-test/src/main/java/vip/ruoyun/weber/test/WeBerBridge.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.SOURCE) 9 | @Target(ElementType.TYPE) 10 | public @interface WeBerBridge { 11 | String value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /weber-test/src/main/java/vip/ruoyun/weber/test/Test.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import android.content.Context; 4 | import android.webkit.WebView; 5 | 6 | /** 7 | * Created by ruoyun on 2019-05-29. 8 | * Author:若云 9 | * Mail:zyhdvlp@gmail.com 10 | * Depiction: 11 | */ 12 | public class Test { 13 | 14 | 15 | public void test(Context context) { 16 | WebView webView = new WebView(context); 17 | WeBer.init().build(webView); 18 | 19 | 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /weber-core/src/test/java/vip/ruoyun/webkit/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/test/java/vip/ruoyun/webviewhelper/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /weber-x5-core/src/test/java/vip/ruoyun/webkit/x5/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /weber-core/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | #重点坑:针对Android4.4,系统把openFileChooser方法去掉了,不混淆openFileChooser() 2 | -keepclassmembers class * extends android.webkit.WeBerChromeClient{ 3 | public void openFileChooser(...); 4 | } 5 | 6 | ## js 调用方法 7 | #保留annotation, 例如 @JavascriptInterface 等 annotation 8 | -keepattributes *Annotation* 9 | 10 | #保留跟 javascript相关的属性 11 | -keepattributes JavascriptInterface 12 | 13 | #保留JavascriptInterface中的方法 14 | -keepclassmembers class * { 15 | @android.webkit.JavascriptInterface ; 16 | } -------------------------------------------------------------------------------- /weber-test/src/test/java/vip/ruoyun/weber/test/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } -------------------------------------------------------------------------------- /weber-x5-jsbridge/src/main/java/vip/ruoyun/webkit/x5/jsbridge/DefaultHandler.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5.jsbridge; 2 | 3 | 4 | import com.tencent.smtt.sdk.ValueCallback; 5 | 6 | public class DefaultHandler implements BridgeHandler { 7 | 8 | String TAG = "DefaultHandler"; 9 | 10 | @Override 11 | public void handler(String data, ValueCallback function) { 12 | if (function != null) { 13 | function.onReceiveValue("DefaultHandler response data"); 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /weber-x5-jsbridge/src/test/java/vip/ruoyun/webkit/x5/jsbridge/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5.jsbridge; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /weber-test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_weber.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/assets/webpage/hitTestResult.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <h2>请长按一下html元素</h2> 7 | 12 | 13 | 14 |

请长按一下html元素


15 |

hello world!


16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /weber-core/jcenter.gradle: -------------------------------------------------------------------------------- 1 | //上传 jcenter 的插件 2 | apply plugin: 'com.novoda.bintray-release' // 新增 3 | 4 | /** 5 | * 参考 6 | * 'com.android.support.test.espresso:espresso-core:3.0.2' 7 | * group: 'com.android.support.test.espresso', name: 'espresso-core', version: '3.0.2' 8 | */ 9 | 10 | // 新增 11 | publish { 12 | userOrg = 'bugyun' //bintray.com用户名 13 | groupId = 'vip.ruoyun.webkit' //jcenter group 14 | artifactId = 'weber-core' //项目名称 name 15 | publishVersion = '1.0.0'//版本号 version 16 | desc = 'Android 内核 WebView 的 Helper'//描述 17 | website = 'https://github.com/bugyun/WeBer' // github 网址 18 | } 19 | 20 | // ./gradlew clean build bintrayUpload -PbintrayUser=bintray.user -PbintrayKey=bintray.apikey -PdryRun=false -------------------------------------------------------------------------------- /weber-x5-core/jcenter.gradle: -------------------------------------------------------------------------------- 1 | //上传 jcenter 的插件 2 | apply plugin: 'com.novoda.bintray-release' // 新增 3 | 4 | /** 5 | * 参考 6 | * 'com.android.support.test.espresso:espresso-core:3.0.2' 7 | * group: 'com.android.support.test.espresso', name: 'espresso-core', version: '3.0.2' 8 | */ 9 | 10 | // 新增 11 | publish { 12 | userOrg = 'bugyun' //bintray.com用户名 13 | groupId = 'vip.ruoyun.webkit' //jcenter group 14 | artifactId = 'weber-x5-core' //项目名称 name 15 | publishVersion = '1.0.9'//版本号 version 16 | desc = 'Android x5 内核 WebView 的 Helper'//描述 17 | website = 'https://github.com/bugyun/WeBer' // github 网址 18 | } 19 | 20 | // ./gradlew clean build bintrayUpload -PbintrayUser=bintray.user -PbintrayKey=bintray.apikey -PdryRun=false -------------------------------------------------------------------------------- /weber-x5-jsbridge/jcenter.gradle: -------------------------------------------------------------------------------- 1 | //上传 jcenter 的插件 2 | apply plugin: 'com.novoda.bintray-release' // 新增 3 | 4 | /** 5 | * 参考 6 | * 'com.android.support.test.espresso:espresso-core:3.0.2' 7 | * group: 'com.android.support.test.espresso', name: 'espresso-core', version: '3.0.2' 8 | */ 9 | 10 | // 新增 11 | publish { 12 | userOrg = 'bugyun' //bintray.com用户名 13 | groupId = 'vip.ruoyun.webkit' //jcenter group 14 | artifactId = 'weber-x5-jsbridge' //项目名称 name 15 | publishVersion = '1.0.1'//版本号 version 16 | desc = 'Android x5 内核 WebView 的 jsbridge'//描述 17 | website = 'https://github.com/bugyun/WeBer' // github 网址 18 | } 19 | 20 | // ./gradlew clean build bintrayUpload -PbintrayUser=bintray.user -PbintrayKey=bintray.apikey -PdryRun=false -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_x5_web.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /weber-test/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | 8 | minSdkVersion 15 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | 29 | implementation fileTree(dir: "libs", include: ["*.jar"]) 30 | implementation 'com.android.support:appcompat-v7:28.0.0' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 33 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 34 | 35 | } -------------------------------------------------------------------------------- /weber-x5-jsbridge/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //apply from: 'jcenter.gradle'//上传 jcenter 的插件 3 | 4 | android { 5 | compileSdkVersion 28 6 | buildToolsVersion "28.0.3" 7 | 8 | 9 | defaultConfig { 10 | minSdkVersion 15 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | // release { 21 | // minifyEnabled false 22 | // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | // } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 33 | // implementation 'vip.ruoyun.webkit:weber-x5-core:1.0.7' 34 | implementation project(path: ':weber-x5-core') 35 | 36 | } 37 | -------------------------------------------------------------------------------- /weber-core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /weber-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //apply from: 'jcenter.gradle'//上传 jcenter 的插件 3 | 4 | android { 5 | compileSdkVersion 28 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 15 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles 'consumer-rules.pro' 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 31 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 32 | 33 | implementation 'com.android.support:support-fragment:28.0.0' 34 | api 'vip.ruoyun.helper:avoid-onresult-helper:1.0.3' 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/weber/TestFragment.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper.weber; 2 | 3 | 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import vip.ruoyun.webviewhelper.R; 13 | 14 | 15 | /** 16 | * A simple {@link Fragment} subclass. 17 | */ 18 | public class TestFragment extends Fragment { 19 | 20 | 21 | public TestFragment() { 22 | // Required empty public constructor 23 | } 24 | 25 | 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 28 | Bundle savedInstanceState) { 29 | // Inflate the layout for this fragment 30 | return inflater.inflate(R.layout.fragment_test, container, false); 31 | } 32 | 33 | 34 | @Override 35 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 36 | super.onViewCreated(view, savedInstanceState); 37 | 38 | 39 | 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /weber-x5-core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //apply from: 'jcenter.gradle'//上传 jcenter 的插件 3 | 4 | android { 5 | compileSdkVersion 28 6 | 7 | 8 | defaultConfig { 9 | minSdkVersion 15 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles 'consumer-rules.pro'//app混淆时的处理 16 | } 17 | 18 | buildTypes { 19 | // release { 20 | // minifyEnabled true 21 | // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | // } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 33 | 34 | implementation 'com.android.support:support-fragment:28.0.0' 35 | api 'vip.ruoyun.helper:avoid-onresult-helper:1.0.4' 36 | api 'com.tencent.tbs.tbssdk:sdk:43903' 37 | } 38 | -------------------------------------------------------------------------------- /weber-core/src/main/java/vip/ruoyun/webkit/WeBer.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | 6 | public class WeBer { 7 | 8 | static String authority = "provider"; 9 | 10 | public static Builder with() { 11 | return new Builder(); 12 | } 13 | 14 | public interface Interceptor { 15 | 16 | void beforeInit(Context context); 17 | } 18 | 19 | public static class Builder { 20 | 21 | private boolean isMultiProcessOptimize = false; 22 | 23 | private Interceptor interceptor; 24 | 25 | private Builder() { 26 | } 27 | 28 | /** 29 | * 在初始化之前做一些配置 30 | */ 31 | public Builder interceptor(Interceptor interceptor) { 32 | this.interceptor = interceptor; 33 | return this; 34 | } 35 | 36 | public Builder authority(@NonNull String authority) { 37 | WeBer.authority = authority; 38 | return this; 39 | } 40 | 41 | public void build(Context context) { 42 | if (interceptor != null) { 43 | interceptor.beforeInit(context); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/weber/X5CanDo.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper.weber; 2 | 3 | import android.util.Log; 4 | 5 | import com.tencent.smtt.sdk.QbSdk; 6 | import com.tencent.smtt.sdk.TbsListener; 7 | 8 | public class X5CanDo { 9 | 10 | 11 | public void test() { 12 | // QbSdk.setDownloadWithoutWifi(true); 13 | QbSdk.PreInitCallback preInitCallback = new QbSdk.PreInitCallback() { 14 | @Override 15 | public void onViewInitFinished(boolean isSuccess) {//x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。 16 | Log.d("app", " onViewInitFinished is " + isSuccess); 17 | } 18 | 19 | @Override 20 | public void onCoreInitFinished() { 21 | 22 | } 23 | }; 24 | QbSdk.setTbsListener(new TbsListener() { 25 | @Override 26 | public void onDownloadFinish(int i) { 27 | //tbs内核下载完成回调 28 | } 29 | 30 | @Override 31 | public void onInstallFinish(int i) { 32 | //内核安装完成回调, 33 | } 34 | 35 | @Override 36 | public void onDownloadProgress(int i) { 37 | //下载进度监听 38 | } 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/MainActivity.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper; 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.widget.Button; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_main); 15 | Button mButton = findViewById(R.id.mButton); 16 | Button mX5Button = findViewById(R.id.mX5Button); 17 | 18 | mButton.setOnClickListener(new View.OnClickListener() { 19 | @Override 20 | public void onClick(View v) { 21 | Intent intent = new Intent(MainActivity.this, WebActivity.class); 22 | startActivity(intent); 23 | } 24 | }); 25 | 26 | mX5Button.setOnClickListener(new View.OnClickListener() { 27 | @Override 28 | public void onClick(View v) { 29 | Intent intent = new Intent(MainActivity.this, WeberActivity.class); 30 | startActivity(intent); 31 | } 32 | }); 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /weber-test/src/main/java/vip/ruoyun/weber/test/WeBerWebChormeClient.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import android.net.Uri; 4 | import android.webkit.JsPromptResult; 5 | import android.webkit.WebChromeClient; 6 | import android.webkit.WebView; 7 | 8 | import vip.ruoyun.weber.test.generate.WeBerBridgeImp; 9 | 10 | public class WeBerWebChormeClient extends WebChromeClient { 11 | 12 | public WeBerWebChormeClient(WeBerView weBerView) { 13 | this.weBerView = weBerView; 14 | } 15 | 16 | //保存反射的对象 17 | private final WeBerView weBerView; 18 | 19 | @Override 20 | public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { 21 | if (url.startsWith("js://webview")) { // 如果是返回数据 22 | //解析 url 23 | Uri parse = Uri.parse(url); 24 | String method = parse.getQueryParameter("method"); 25 | String callback = parse.getQueryParameter("callback"); 26 | String value = parse.getQueryParameter("value"); 27 | //下面代码自动生成 28 | Object object = weBerView.objectMap.get(method); 29 | if (null != object) { 30 | WeBerBridgeImp.eve(object, method, callback, value, weBerView); 31 | } 32 | return true; 33 | } 34 | 35 | return super.onJsPrompt(view, url, message, defaultValue, result); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /weber-test/src/main/java/vip/ruoyun/weber/test/WeBerWebClient.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import android.net.Uri; 4 | import android.webkit.WebView; 5 | import android.webkit.WebViewClient; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.HashMap; 9 | 10 | import vip.ruoyun.weber.test.generate.WeBerBridgeImp; 11 | 12 | public class WeBerWebClient extends WebViewClient { 13 | 14 | 15 | public WeBerWebClient(WeBerView weBerView) { 16 | this.weBerView = weBerView; 17 | } 18 | 19 | //保存反射的对象 20 | private HashMap methodMap = new HashMap<>(); 21 | private final WeBerView weBerView; 22 | 23 | @Override 24 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 25 | if (url.startsWith("js://webview")) { // 如果是返回数据 26 | //解析 url 27 | Uri parse = Uri.parse(url); 28 | String method = parse.getQueryParameter("method"); 29 | String callback = parse.getQueryParameter("callback"); 30 | String value = parse.getQueryParameter("value"); 31 | //下面代码自动生成 32 | Object object = weBerView.objectMap.get(method); 33 | if (null != object) { 34 | WeBerBridgeImp.eve(object, method, callback, value, weBerView); 35 | } 36 | return true; 37 | } 38 | return super.shouldOverrideUrlLoading(view, url); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/helper/BaseWebViewClient.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper.helper; 2 | 3 | import android.graphics.Bitmap; 4 | import android.net.http.SslError; 5 | import android.webkit.SslErrorHandler; 6 | import android.webkit.WebView; 7 | import android.webkit.WebViewClient; 8 | 9 | 10 | /** 11 | * Created by ruoyun on 2018/7/4. 12 | * Author:若云 13 | * Mail:zyhdvlp@gmail.com 14 | * Depiction: 15 | */ 16 | //Web视图 17 | public class BaseWebViewClient extends WebViewClient { 18 | 19 | private static final String TAG = "WebViewHelper"; 20 | 21 | private boolean isReceivedError = false; 22 | 23 | private OnLoadWebViewListener onLoadWebViewListener; 24 | 25 | public interface OnLoadWebViewListener { 26 | void onPageFinished(boolean isSuccess); 27 | } 28 | 29 | @Override 30 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 31 | super.onPageStarted(view, url, favicon); 32 | isReceivedError = false; 33 | } 34 | 35 | @Override 36 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 37 | // if (url.startsWith("tel:")) { 38 | // Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 39 | // view.getContext().startActivity(intent); 40 | // return true; 41 | // } 42 | // if (url.startsWith("http://callback/h5")) { 43 | // MessageHelper.sendMessage(new H5PhoneAuthMessage()); 44 | // activity.finish(); 45 | // return true; 46 | // } 47 | view.loadUrl(url); 48 | return true; 49 | } 50 | 51 | @Override 52 | public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { 53 | handler.proceed();// 接受所有网站的证书 54 | // handler.cancel(); 55 | } 56 | 57 | @Override 58 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 59 | super.onReceivedError(view, errorCode, description, failingUrl); 60 | isReceivedError = true; 61 | } 62 | 63 | @Override 64 | public void onPageFinished(WebView view, String url) { 65 | super.onPageFinished(view, url); 66 | if (!view.getSettings().getLoadsImagesAutomatically()) { 67 | view.getSettings().setLoadsImagesAutomatically(true); 68 | } 69 | if (onLoadWebViewListener != null) { 70 | onLoadWebViewListener.onPageFinished(!isReceivedError); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/assets/webpage/fullscreenVideo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | 18 | 21 |
22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 52 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/helper/BaseJavascriptInterface.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper.helper; 2 | 3 | import android.app.Activity; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.text.TextUtils; 7 | import android.webkit.JavascriptInterface; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | 12 | /** 13 | * Created by ruoyun on 2018/7/4. 14 | * Author:若云 15 | * Mail:zyhdvlp@gmail.com 16 | * Depiction: 17 | */ 18 | public class BaseJavascriptInterface { 19 | private Activity context; 20 | private long timeMillis; 21 | private Handler handler = new Handler(Looper.myLooper()); 22 | 23 | public BaseJavascriptInterface(Activity context) { 24 | this.context = context; 25 | timeMillis = System.currentTimeMillis(); 26 | } 27 | 28 | @JavascriptInterface 29 | public void closeWebview() { 30 | handler.post(new Runnable() { 31 | @Override 32 | public void run() { 33 | context.finish(); 34 | } 35 | }); 36 | } 37 | 38 | @JavascriptInterface 39 | public String getDeviceInfo() { 40 | JSONObject jsonObject = new JSONObject(); 41 | try { 42 | // jsonObject.put("pid", UserSPUtils.getDeviceMD5()); 43 | // jsonObject.put("version", SystemUtils.getVersionName(context)); 44 | // jsonObject.put("token", UserSPUtils.getUserToken()); 45 | jsonObject.put("type", "android " + android.os.Build.VERSION.RELEASE); 46 | } catch (JSONException e) { 47 | e.printStackTrace(); 48 | return ""; 49 | } 50 | String deviceInfo = jsonObject.toString(); 51 | // LogUtils.i(deviceInfo); 52 | return deviceInfo; 53 | } 54 | 55 | @JavascriptInterface 56 | public void open(String name) { 57 | // test 试试我们贷多少,apply 立即申请 login 58 | if (System.currentTimeMillis() - timeMillis > 1000) { 59 | timeMillis = System.currentTimeMillis(); 60 | // LogUtils.i("callOnJsToUser执行我了。。。。"); 61 | // UserInfoActivity.actionStart(context, userId); 62 | } 63 | // LogUtils.d("callOnJsToUser 被调用"); 64 | if (TextUtils.isEmpty(name)) { 65 | return; 66 | } 67 | switch (name) { 68 | case "login": 69 | handler.post(new Runnable() { 70 | @Override 71 | public void run() { 72 | // LoginNewActivity.actionStart(context); 73 | } 74 | }); 75 | break; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /weber-core/README.md: -------------------------------------------------------------------------------- 1 | # WeBer 2 | Android 内核 WebView 的 Helper 3 | 完美兼容 AndroidX 和 android 库,欢迎使用~~~ 4 | 5 | ## 特有功能 6 | 支持 h5 中 input 标签,标准 Android WebView 不支持此标签并且只支持单文件操作。 7 | - 打开所有文件 `` 8 | - 打开照相机 `` 优先级大于 accept 9 | - 打开摄像机 `` 10 | - 打开图片 `` 11 | - 等等相关文件操作 12 | 13 | # 使用方法 14 | 15 | ## 添加依赖 16 | 17 | jcenter()仓库,在子项目中的 build.gradle 文件中添加 18 | 19 | ```java 20 | dependencies { 21 | implementation 'vip.ruoyun.webkit:weber-core:1.0.0' 22 | } 23 | ``` 24 | 25 | ## 使用 26 | 27 | ### Application中进行初始化 28 | ```java 29 | WeBer.with() 30 | .interceptor(new WeBer.Interceptor() { //在初始化之前做一些配置 31 | @Override 32 | public void beforeInit(final Context context) { 33 | 34 | } 35 | }) 36 | //配置 android:authorities => "${applicationId}."+"${authority}" 37 | //下面会生成 => android:authorities="${applicationId}.provider" 38 | .authority("provider") 39 | .build(this); 40 | ``` 41 | 42 | ## WeBerChromeClient 支持 input 标签 43 | 44 | 需要继承 WeBerChromeClient,可以添加文件的监听器。 45 | 46 | 当 h5 有input 标签的时候,响应事件。可以不设置。 47 | ```html 48 | 49 | ``` 50 | ### input 标签相关配置 51 | 52 | - ```multiple="multiple" : 只能支持单文件,所以设置multiple无效``` 53 | - ```accept="video/*" : 打开摄像机功能``` 54 | - ```capture="camera" : 如果有值的话,就会调用照相机功能,优先级大于 accept``` 55 | - ```accept="image/*" : 选择文件,根据设置 image/*图片, */* 所有文件``` 56 | 57 | ### onActivityResult 58 | 不需要在 onActivityResult 事件中添加回调,使用 https://github.com/bugyun/AvoidOnResultHelper 优化回调. 59 | 60 | 61 | ### WeBerChromeClient.setFileChooserIntercept() 拦截器 62 | 63 | 如果直接使用 input 标签来打开具体对应的功能,是需要 Android 手机对应的权限的.可以通过下面的方法添加拦截器,true 表示拦截此次打开(相机/文件/摄像机)的请求来判断是否炫需要权限。 64 | 65 | 权限库推荐使用 https://github.com/bugyun/MissPermission 来进行权限的检查和请求。 66 | 67 | 可以通过判断 intent 的 getAction() 来进行具体权限的检查,然后请求对应的权限。代码如下 68 | 69 | 70 | ```java 71 | class TestWeBerChromeClient extends WeBerChromeClient { 72 | ... 73 | } 74 | 75 | TestWeBerChromeClient chromeClient = new TestWeBerChromeClient(this); 76 | 77 | //可选操作 78 | chromeClient.setFileChooserIntercept(new WeBerChromeClient.FileChooserIntercept() { 79 | /** 80 | * @param isCapture 是否是照相功能 81 | * @param acceptType input标签 acceptType的属性 82 | * @param intent 意图 83 | * @return 是否要拦截, 可根据 intent 来判断是否要进行照相机权限检查,代码如下 84 | */ 85 | @Override 86 | public boolean onFileChooserIntercept(boolean isCapture,String[] acceptType, Intent intent) { 87 | if (MediaStore.ACTION_VIDEO_CAPTURE.equals(intent.getAction())) {//要使用摄像机 88 | //要使用摄像机,判断权限 android.permission.CAMERA 89 | //可以使用 https://github.com/bugyun/MissPermission ,来进行权限的请求. 90 | return true;//拦截 91 | } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intent.getAction())) {//要使用照相机 92 | //要使用照相机,判断权限 android.permission.CAMERA 93 | //可以使用 https://github.com/bugyun/MissPermission ,来进行权限的请求. 94 | return true;//拦截 95 | } 96 | return false;//不拦截 97 | } 98 | }); 99 | ``` 100 | 101 | ## 混淆 102 | 103 | 内部已经内置了混淆,所以不需要添加任何混淆 104 | 105 | 106 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/PlayVideoFunc.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper; 2 | 3 | import android.os.Bundle; 4 | import android.widget.Toast; 5 | 6 | import com.tencent.smtt.sdk.WebView; 7 | 8 | /** 9 | * Created by ruoyun on 2019-07-03. 10 | * Author:若云 11 | * Mail:zyhdvlp@gmail.com 12 | * Depiction: 13 | */ 14 | public class PlayVideoFunc { 15 | 16 | // ///////////////////////////////////////// 17 | // 向webview发出信息 18 | public static void enableX5FullscreenFunc(WebView webView) { 19 | 20 | if (webView.getX5WebViewExtension() != null) { 21 | Toast.makeText(webView.getContext(), "开启X5全屏播放模式", Toast.LENGTH_LONG).show(); 22 | Bundle data = new Bundle(); 23 | 24 | data.putBoolean("standardFullScreen", false);// true表示标准全屏,false表示X5全屏;不设置默认false, 25 | 26 | data.putBoolean("supportLiteWnd", false);// false:关闭小窗;true:开启小窗;不设置默认true, 27 | 28 | data.putInt("DefaultVideoScreen", 2);// 1:以页面内开始播放,2:以全屏开始播放;不设置默认:1 29 | 30 | webView.getX5WebViewExtension().invokeMiscMethod("setVideoParams", 31 | data); 32 | } 33 | } 34 | 35 | public static void disableX5FullscreenFunc(WebView webView) { 36 | if (webView.getX5WebViewExtension() != null) { 37 | Toast.makeText(webView.getContext(), "恢复webkit初始状态", Toast.LENGTH_LONG).show(); 38 | Bundle data = new Bundle(); 39 | 40 | data.putBoolean("standardFullScreen", true);// true表示标准全屏,会调起onShowCustomView(),false表示X5全屏;不设置默认false, 41 | 42 | data.putBoolean("supportLiteWnd", false);// false:关闭小窗;true:开启小窗;不设置默认true, 43 | 44 | data.putInt("DefaultVideoScreen", 2);// 1:以页面内开始播放,2:以全屏开始播放;不设置默认:1 45 | 46 | webView.getX5WebViewExtension().invokeMiscMethod("setVideoParams", 47 | data); 48 | } 49 | } 50 | 51 | public static void enableLiteWndFunc(WebView webView) { 52 | if (webView.getX5WebViewExtension() != null) { 53 | Toast.makeText(webView.getContext(), "开启小窗模式", Toast.LENGTH_LONG).show(); 54 | Bundle data = new Bundle(); 55 | 56 | data.putBoolean("standardFullScreen", false);// true表示标准全屏,会调起onShowCustomView(),false表示X5全屏;不设置默认false, 57 | 58 | data.putBoolean("supportLiteWnd", true);// false:关闭小窗;true:开启小窗;不设置默认true, 59 | 60 | data.putInt("DefaultVideoScreen", 2);// 1:以页面内开始播放,2:以全屏开始播放;不设置默认:1 61 | 62 | webView.getX5WebViewExtension().invokeMiscMethod("setVideoParams", 63 | data); 64 | } 65 | } 66 | 67 | public static void enablePageVideoFunc(WebView webView) { 68 | if (webView.getX5WebViewExtension() != null) { 69 | Toast.makeText(webView.getContext(), "页面内全屏播放模式", Toast.LENGTH_LONG).show(); 70 | Bundle data = new Bundle(); 71 | 72 | data.putBoolean("standardFullScreen", false);// true表示标准全屏,会调起onShowCustomView(),false表示X5全屏;不设置默认false, 73 | 74 | data.putBoolean("supportLiteWnd", false);// false:关闭小窗;true:开启小窗;不设置默认true, 75 | 76 | data.putInt("DefaultVideoScreen", 1);// 1:以页面内开始播放,2:以全屏开始播放;不设置默认:1 77 | 78 | webView.getX5WebViewExtension().invokeMiscMethod("setVideoParams", 79 | data); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /weber-x5-core/src/main/java/vip/ruoyun/webkit/x5/WeBerView.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.util.AttributeSet; 6 | 7 | import com.tencent.smtt.sdk.WebSettings; 8 | import com.tencent.smtt.sdk.WebView; 9 | 10 | import java.util.Map; 11 | 12 | public class WeBerView extends WebView { 13 | 14 | public WeBerView(Context context) { 15 | super(context); 16 | init(); 17 | } 18 | 19 | public WeBerView(Context context, AttributeSet attributeSet) { 20 | super(context, attributeSet); 21 | init(); 22 | } 23 | 24 | public WeBerView(Context context, AttributeSet attributeSet, int i) { 25 | super(context, attributeSet, i); 26 | init(); 27 | } 28 | 29 | public WeBerView(Context context, AttributeSet attributeSet, int i, boolean b) { 30 | super(context, attributeSet, i, b); 31 | init(); 32 | } 33 | 34 | public WeBerView(Context context, AttributeSet attributeSet, int i, Map map, boolean b) { 35 | super(context, attributeSet, i, map, b); 36 | init(); 37 | } 38 | 39 | private void init() { 40 | WeBerViewClient client = new WeBerViewClient(); 41 | setWebViewClient(client); 42 | initWebViewSettings(); 43 | this.getView().setClickable(true); 44 | } 45 | 46 | private void initWebViewSettings() { 47 | WebSettings webSetting = this.getSettings(); 48 | webSetting.setJavaScriptEnabled(true); //支持js 49 | 50 | webSetting.setJavaScriptCanOpenWindowsAutomatically(true);//支持通过JS打开新窗口 51 | webSetting.setAllowFileAccess(true);////设置可以访问文件 52 | webSetting.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);//可能的话,使所有列的宽度不超过屏幕宽度。默认值 53 | 54 | webSetting.setSupportZoom(true); //支持缩放,默认为true。是下面那个的前提。 55 | webSetting.setBuiltInZoomControls(true);//设置内置的缩放控件 56 | webSetting.setSupportMultipleWindows(false);//是否支持多窗口 57 | 58 | webSetting.setUseWideViewPort(true);//将图片调整到适合webview的大小 59 | webSetting.setLoadWithOverviewMode(true);// 缩放至屏幕的大小 60 | 61 | webSetting.setAppCacheEnabled(true); 62 | webSetting.setDatabaseEnabled(true); 63 | webSetting.setDomStorageEnabled(true); 64 | webSetting.setGeolocationEnabled(true); 65 | webSetting.setAppCacheMaxSize(Long.MAX_VALUE); 66 | webSetting.setTextZoom(100);//设置文本的缩放倍数,默认为 100 67 | 68 | webSetting.setPluginsEnabled(true);//支持插件 69 | 70 | webSetting.setAppCachePath(getContext().getDir("appcache", Context.MODE_PRIVATE).getPath()); 71 | webSetting.setDatabasePath(getContext().getDir("databases", Context.MODE_PRIVATE).getPath()); 72 | webSetting.setGeolocationDatabasePath(getContext().getDir("geolocation", Context.MODE_PRIVATE).getPath()); 73 | // webSetting.setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY); 74 | webSetting.setPluginState(WebSettings.PluginState.ON_DEMAND); 75 | webSetting.setRenderPriority(WebSettings.RenderPriority.HIGH);//提高渲染的优先级 76 | // webSetting.setPreFectch(true); 77 | 78 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 79 | webSetting.setAllowUniversalAccessFromFileURLs(true); 80 | } 81 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//在Android 5.0之后,WebView默认不允许Https + Http的混合使用方式 82 | webSetting.setMixedContentMode(android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 83 | } 84 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 85 | this.removeJavascriptInterface("searchBoxJavaBridge_"); 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /weber-test/src/main/java/vip/ruoyun/weber/test/WeBerChromeClient.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.ClipData; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.os.Build; 9 | import android.webkit.ValueCallback; 10 | import android.webkit.WebChromeClient; 11 | import android.webkit.WebView; 12 | 13 | public class WeBerChromeClient extends WebChromeClient { 14 | 15 | private ValueCallback uploadMessage; 16 | private ValueCallback uploadMessageAboveL; 17 | private final static int FILE_CHOOSER_RESULT_CODE = 10000; 18 | private Activity activity; 19 | 20 | 21 | // For Android < 3.0 22 | public void openFileChooser(ValueCallback valueCallback) { 23 | uploadMessage = valueCallback; 24 | openImageChooserActivity(); 25 | } 26 | 27 | // For Android >= 3.0 28 | public void openFileChooser(ValueCallback valueCallback, String acceptType) { 29 | uploadMessage = valueCallback; 30 | openImageChooserActivity(); 31 | } 32 | 33 | //For Android >= 4.1 34 | public void openFileChooser(ValueCallback valueCallback, String acceptType, String capture) { 35 | uploadMessage = valueCallback; 36 | openImageChooserActivity(); 37 | } 38 | 39 | // For Android >= 5.0 40 | @Override 41 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { 42 | uploadMessageAboveL = filePathCallback; 43 | openImageChooserActivity(); 44 | return true; 45 | } 46 | 47 | 48 | private void openImageChooserActivity() { 49 | Intent i = new Intent(Intent.ACTION_GET_CONTENT); 50 | i.addCategory(Intent.CATEGORY_OPENABLE); 51 | i.setType("image/*"); 52 | activity.startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE); 53 | } 54 | 55 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 56 | // super.onActivityResult(requestCode, resultCode, data); 57 | if (requestCode == FILE_CHOOSER_RESULT_CODE) { 58 | if (null == uploadMessage && null == uploadMessageAboveL) 59 | return; 60 | Uri result = data == null || resultCode != Activity.RESULT_OK ? null : data.getData(); 61 | if (uploadMessageAboveL != null) { 62 | onActivityResultAboveL(requestCode, resultCode, data); 63 | } else if (uploadMessage != null) { 64 | uploadMessage.onReceiveValue(result); 65 | uploadMessage = null; 66 | } 67 | } 68 | } 69 | 70 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 71 | private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) { 72 | if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null) 73 | return; 74 | Uri[] results = null; 75 | if (resultCode == Activity.RESULT_OK) { 76 | if (intent != null) { 77 | String dataString = intent.getDataString(); 78 | ClipData clipData = intent.getClipData(); 79 | if (clipData != null) { 80 | results = new Uri[clipData.getItemCount()]; 81 | for (int i = 0; i < clipData.getItemCount(); i++) { 82 | ClipData.Item item = clipData.getItemAt(i); 83 | results[i] = item.getUri(); 84 | } 85 | } 86 | if (dataString != null) 87 | results = new Uri[]{Uri.parse(dataString)}; 88 | } 89 | } 90 | uploadMessageAboveL.onReceiveValue(results); 91 | uploadMessageAboveL = null; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/assets/webpage/jsbridge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | js调用java 6 | 7 | 8 | 9 | 10 |

11 |

12 | 13 |

14 |

15 |

16 | 17 |

18 |

19 | 20 |

21 |

22 | 23 |

24 |

25 | 27 |

28 |

29 | 31 |

32 |

33 | 34 |

35 |

36 | 37 |

38 | 39 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/helper/WebViewOpenImageCore.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper.helper; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.ClipData; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.os.Build; 9 | import android.webkit.ValueCallback; 10 | import android.webkit.WebChromeClient; 11 | import android.webkit.WebView; 12 | 13 | /** 14 | * Created by ruoyun on 2018/7/4. 15 | * Author:若云 16 | * Mail:zyhdvlp@gmail.com 17 | * Depiction:webview打开原生图片库 18 | */ 19 | public class WebViewOpenImageCore { 20 | private Activity activity; 21 | private ValueCallback uploadMessage; 22 | private ValueCallback uploadMessageAboveL; 23 | private final static int FILE_CHOOSER_RESULT_CODE = 10000; 24 | 25 | public WebViewOpenImageCore(Activity activity) { 26 | this.activity = activity; 27 | } 28 | 29 | public void setImageCore(WebView webView) { 30 | webView.setWebChromeClient(new WebChromeClient() { 31 | // For Android < 3.0 32 | public void openFileChooser(ValueCallback valueCallback) { 33 | uploadMessage = valueCallback; 34 | openImageChooserActivity(); 35 | } 36 | 37 | // For Android >= 3.0 38 | public void openFileChooser(ValueCallback valueCallback, String acceptType) { 39 | uploadMessage = valueCallback; 40 | openImageChooserActivity(); 41 | } 42 | 43 | //For Android >= 4.1 44 | public void openFileChooser(ValueCallback valueCallback, String acceptType, String capture) { 45 | uploadMessage = valueCallback; 46 | openImageChooserActivity(); 47 | } 48 | 49 | // For Android >= 5.0 50 | @Override 51 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { 52 | uploadMessageAboveL = filePathCallback; 53 | openImageChooserActivity(); 54 | return true; 55 | } 56 | }); 57 | } 58 | 59 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 60 | if (requestCode == FILE_CHOOSER_RESULT_CODE) { 61 | if (null == uploadMessage && null == uploadMessageAboveL) 62 | return; 63 | Uri result = data == null || resultCode != Activity.RESULT_OK ? null : data.getData(); 64 | if (uploadMessageAboveL != null) { 65 | onActivityResultAboveL(requestCode, resultCode, data); 66 | } else if (uploadMessage != null) { 67 | uploadMessage.onReceiveValue(result); 68 | uploadMessage = null; 69 | } 70 | } 71 | } 72 | 73 | private void openImageChooserActivity() { 74 | Intent i = new Intent(Intent.ACTION_GET_CONTENT); 75 | i.addCategory(Intent.CATEGORY_OPENABLE); 76 | i.setType("image/*"); 77 | activity.startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE); 78 | } 79 | 80 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 81 | private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) { 82 | if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null) 83 | return; 84 | Uri[] results = null; 85 | if (resultCode == Activity.RESULT_OK) { 86 | if (intent != null) { 87 | String dataString = intent.getDataString(); 88 | ClipData clipData = intent.getClipData(); 89 | if (clipData != null) { 90 | results = new Uri[clipData.getItemCount()]; 91 | for (int i = 0; i < clipData.getItemCount(); i++) { 92 | ClipData.Item item = clipData.getItemAt(i); 93 | results[i] = item.getUri(); 94 | } 95 | } 96 | if (dataString != null) 97 | results = new Uri[]{Uri.parse(dataString)}; 98 | } 99 | } 100 | uploadMessageAboveL.onReceiveValue(results); 101 | uploadMessageAboveL = null; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /weber-x5-jsbridge/src/main/java/vip/ruoyun/webkit/x5/jsbridge/Message.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5.jsbridge; 2 | 3 | import android.text.TextUtils; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | import org.json.JSONTokener; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * data of bridge 15 | * 16 | * @author haoqing 17 | */ 18 | public class Message { 19 | 20 | private String callbackId; //callbackId 21 | private String responseId; //responseId 22 | private String responseData; //responseData 23 | private String data; //data of message 24 | private String handlerName; //name of handler 25 | 26 | private final static String CALLBACK_ID_STR = "callbackId"; 27 | private final static String RESPONSE_ID_STR = "responseId"; 28 | private final static String RESPONSE_DATA_STR = "responseData"; 29 | private final static String DATA_STR = "data"; 30 | private final static String HANDLER_NAME_STR = "handlerName"; 31 | 32 | String getResponseId() { 33 | return responseId; 34 | } 35 | 36 | void setResponseId(String responseId) { 37 | this.responseId = responseId; 38 | } 39 | 40 | String getResponseData() { 41 | return responseData; 42 | } 43 | 44 | void setResponseData(String responseData) { 45 | this.responseData = responseData; 46 | } 47 | 48 | String getCallbackId() { 49 | return callbackId; 50 | } 51 | 52 | void setCallbackId(String callbackId) { 53 | this.callbackId = callbackId; 54 | } 55 | 56 | String getData() { 57 | return data; 58 | } 59 | 60 | void setData(String data) { 61 | this.data = data; 62 | } 63 | 64 | String getHandlerName() { 65 | return handlerName; 66 | } 67 | 68 | void setHandlerName(String handlerName) { 69 | this.handlerName = handlerName; 70 | } 71 | 72 | String toJson() { 73 | JSONObject jsonObject = new JSONObject(); 74 | try { 75 | jsonObject.put(CALLBACK_ID_STR, getCallbackId()); 76 | jsonObject.put(DATA_STR, getData()); 77 | jsonObject.put(HANDLER_NAME_STR, getHandlerName()); 78 | String data = getResponseData(); 79 | if (TextUtils.isEmpty(data)) { 80 | jsonObject.put(RESPONSE_DATA_STR, data); 81 | } else { 82 | jsonObject.put(RESPONSE_DATA_STR, new JSONTokener(data).nextValue()); 83 | } 84 | jsonObject.put(RESPONSE_DATA_STR, getResponseData()); 85 | jsonObject.put(RESPONSE_ID_STR, getResponseId()); 86 | return jsonObject.toString(); 87 | } catch (JSONException e) { 88 | e.printStackTrace(); 89 | } 90 | return null; 91 | } 92 | 93 | public static Message toObject(String jsonStr) { 94 | Message m = new Message(); 95 | try { 96 | JSONObject jsonObject = new JSONObject(jsonStr); 97 | m.setHandlerName(jsonObject.has(HANDLER_NAME_STR) ? jsonObject.getString(HANDLER_NAME_STR) : null); 98 | m.setCallbackId(jsonObject.has(CALLBACK_ID_STR) ? jsonObject.getString(CALLBACK_ID_STR) : null); 99 | m.setResponseData(jsonObject.has(RESPONSE_DATA_STR) ? jsonObject.getString(RESPONSE_DATA_STR) : null); 100 | m.setResponseId(jsonObject.has(RESPONSE_ID_STR) ? jsonObject.getString(RESPONSE_ID_STR) : null); 101 | m.setData(jsonObject.has(DATA_STR) ? jsonObject.getString(DATA_STR) : null); 102 | return m; 103 | } catch (JSONException e) { 104 | e.printStackTrace(); 105 | } 106 | return m; 107 | } 108 | 109 | static List toArrayList(String jsonStr) { 110 | List list = new ArrayList<>(); 111 | try { 112 | JSONArray jsonArray = new JSONArray(jsonStr); 113 | for (int i = 0; i < jsonArray.length(); i++) { 114 | Message m = new Message(); 115 | JSONObject jsonObject = jsonArray.getJSONObject(i); 116 | m.setHandlerName(jsonObject.has(HANDLER_NAME_STR) ? jsonObject.getString(HANDLER_NAME_STR) : null); 117 | m.setCallbackId(jsonObject.has(CALLBACK_ID_STR) ? jsonObject.getString(CALLBACK_ID_STR) : null); 118 | m.setResponseData(jsonObject.has(RESPONSE_DATA_STR) ? jsonObject.getString(RESPONSE_DATA_STR) : null); 119 | m.setResponseId(jsonObject.has(RESPONSE_ID_STR) ? jsonObject.getString(RESPONSE_ID_STR) : null); 120 | m.setData(jsonObject.has(DATA_STR) ? jsonObject.getString(DATA_STR) : null); 121 | list.add(m); 122 | } 123 | } catch (JSONException e) { 124 | e.printStackTrace(); 125 | } 126 | return list; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/weber/Test.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper.weber; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.os.Environment; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | import android.view.View; 9 | 10 | import com.tencent.smtt.sdk.TbsReaderView; 11 | import com.tencent.smtt.sdk.WebView; 12 | 13 | import java.io.File; 14 | import vip.ruoyun.webkit.x5.WeBer; 15 | 16 | /** 17 | * Created by ruoyun on 2019-07-02. 18 | * Author:若云 19 | * Mail:zyhdvlp@gmail.com 20 | * Depiction: 21 | */ 22 | public class Test { 23 | 24 | 25 | public void test(Context context) { 26 | WeBer.with().build(context); 27 | } 28 | 29 | 30 | //判断是否加载了 x5 内核 31 | public static void test(WebView webView) { 32 | webView.getX5WebViewExtension(); 33 | //在没有⾃定义UA的情况下,使⽤您的app打开⽹⻚页,显示000000表示加载的是系统内核,显示⼤于零的数字表示加载了x5内核(该数字 是x5内核版本号) 34 | //http://soft.imtt.qq.com/browser/tes/feedback.html 35 | } 36 | 37 | 38 | public static void longclick(WebView webView) { 39 | //4、操作图片(完整代码) 40 | webView.setOnLongClickListener(new View.OnLongClickListener() { 41 | @Override 42 | public boolean onLongClick(View v) { 43 | WebView.HitTestResult result = ((WebView) v).getHitTestResult(); 44 | if (null == result) { 45 | return false; 46 | } 47 | int type = result.getType(); 48 | if (type == WebView.HitTestResult.UNKNOWN_TYPE) { 49 | return false; 50 | } 51 | // 这里可以拦截很多类型,我们只处理图片类型就可以了 52 | switch (type) { 53 | case WebView.HitTestResult.PHONE_TYPE: // 处理拨号 54 | break; 55 | case WebView.HitTestResult.EMAIL_TYPE: // 处理Email 56 | break; 57 | case WebView.HitTestResult.GEO_TYPE: // 地图类型 58 | break; 59 | case WebView.HitTestResult.SRC_ANCHOR_TYPE: // 超链接 60 | break; 61 | case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: 62 | break; 63 | case WebView.HitTestResult.IMAGE_TYPE: // 处理长按图片的菜单项 64 | // 获取图片的路径 65 | String saveImgUrl = result.getExtra(); 66 | // 跳转到图片详情页,显示图片 67 | // Intent i = new Intent(MainActivity.this, ImageActivity.class); 68 | // i.putExtra("imgUrl", saveImgUrl); 69 | // startActivity(i); 70 | break; 71 | default: 72 | break; 73 | } 74 | return true; 75 | } 76 | }); 77 | } 78 | 79 | /** 80 | * X5内核的一大特色就是可以在手机不安装office的情况下,可以只需下载插件即可浏览office文档 81 | * 这个类 感觉不太建议使用,应该使用下面的 QbSdk.canOpenFile 方法 82 | */ 83 | private void displayFile(Context context, File mFile) { 84 | if (mFile != null && !TextUtils.isEmpty(mFile.toString())) { 85 | //增加下面一句解决没有TbsReaderTemp文件夹存在导致加载文件失败 86 | String bsReaderTemp = Environment.getExternalStorageDirectory() + "/" + "TbsReaderTemp"; 87 | File bsReaderTempFile = new File(bsReaderTemp); 88 | 89 | if (!bsReaderTempFile.exists()) { 90 | Log.d("", "准备创建/storage/emulated/0/TbsReaderTemp!!"); 91 | boolean mkdir = bsReaderTempFile.mkdir(); 92 | if (!mkdir) { 93 | Log.e("", "创建/storage/emulated/0/TbsReaderTemp失败!!!!!"); 94 | } 95 | } 96 | TbsReaderView tbsReaderView = new TbsReaderView(context, new TbsReaderView.ReaderCallback() { 97 | @Override 98 | public void onCallBackAction(Integer integer, Object o, Object o1) { 99 | 100 | } 101 | }); 102 | Bundle bundle = new Bundle(); 103 | String filePath = ""; 104 | bundle.putString(TbsReaderView.KEY_FILE_PATH, filePath);//传递文件路径 105 | bundle.putString(TbsReaderView.KEY_TEMP_PATH, bsReaderTemp);//加载插件保存的路径 106 | boolean result = tbsReaderView.preOpen(getFileType(filePath), false);//判断是否支持打开此类型的文件 107 | if (result) { 108 | tbsReaderView.openFile(bundle); 109 | } 110 | } 111 | } 112 | 113 | private void colse(TbsReaderView tbsReaderView) { 114 | tbsReaderView.onStop(); 115 | } 116 | 117 | /*** 118 | * 获取文件类型 119 | * 120 | * @param paramString 121 | * @return 122 | */ 123 | private String getFileType(String paramString) { 124 | String str = ""; 125 | 126 | if (TextUtils.isEmpty(paramString)) { 127 | Log.d("", "paramString---->null"); 128 | return str; 129 | } 130 | Log.d("", "paramString:" + paramString); 131 | int i = paramString.lastIndexOf('.'); 132 | if (i <= -1) { 133 | Log.d("", "i <= -1"); 134 | return str; 135 | } 136 | str = paramString.substring(i + 1); 137 | Log.d("", "paramString.substring(i + 1)------>" + str); 138 | return str; 139 | } 140 | } 141 | 142 | 143 | -------------------------------------------------------------------------------- /weber-x5-jsbridge/src/main/java/vip/ruoyun/webkit/x5/jsbridge/BridgeUtil.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5.jsbridge; 2 | 3 | import android.content.Context; 4 | 5 | import com.tencent.smtt.sdk.WebView; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.InputStreamReader; 11 | 12 | public class BridgeUtil { 13 | final static String YY_OVERRIDE_SCHEMA = "yy://"; 14 | final static String YY_RETURN_DATA = YY_OVERRIDE_SCHEMA + "return/";//格式为 yy://return/{function}/returncontent 15 | final static String YY_FETCH_QUEUE = YY_RETURN_DATA + "_fetchQueue/"; 16 | final static String EMPTY_STR = ""; 17 | final static String UNDERLINE_STR = "_"; 18 | final static String SPLIT_MARK = "/"; 19 | 20 | final static String CALLBACK_ID_FORMAT = "JAVA_CB_%s"; 21 | final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');"; 22 | final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();"; 23 | public final static String JAVASCRIPT_STR = "javascript:"; 24 | 25 | // 例子 javascript:WebViewJavascriptBridge._fetchQueue(); --> _fetchQueue 26 | public static String parseFunctionName(String jsUrl) { 27 | return jsUrl.replace("javascript:WebViewJavascriptBridge.", "").replaceAll("\\(.*\\);", ""); 28 | } 29 | 30 | // 获取到传递信息的body值 31 | // url = yy://return/_fetchQueue/[{"responseId":"JAVA_CB_2_3957","responseData":"Javascript Says Right back aka!"}] 32 | public static String getDataFromReturnUrl(String url) { 33 | if (url.startsWith(YY_FETCH_QUEUE)) { 34 | // return = [{"responseId":"JAVA_CB_2_3957","responseData":"Javascript Says Right back aka!"}] 35 | return url.replace(YY_FETCH_QUEUE, EMPTY_STR); 36 | } 37 | 38 | // temp = _fetchQueue/[{"responseId":"JAVA_CB_2_3957","responseData":"Javascript Says Right back aka!"}] 39 | String temp = url.replace(YY_RETURN_DATA, EMPTY_STR); 40 | String[] functionAndData = temp.split(SPLIT_MARK); 41 | 42 | if (functionAndData.length >= 2) { 43 | StringBuilder sb = new StringBuilder(); 44 | for (int i = 1; i < functionAndData.length; i++) { 45 | sb.append(functionAndData[i]); 46 | } 47 | // return = [{"responseId":"JAVA_CB_2_3957","responseData":"Javascript Says Right back aka!"}] 48 | return sb.toString(); 49 | } 50 | return null; 51 | } 52 | 53 | // 获取到传递信息的方法 54 | // url = yy://return/_fetchQueue/[{"responseId":"JAVA_CB_1_360","responseData":"Javascript Says Right back aka!"}] 55 | public static String getFunctionFromReturnUrl(String url) { 56 | // temp = _fetchQueue/[{"responseId":"JAVA_CB_1_360","responseData":"Javascript Says Right back aka!"}] 57 | String temp = url.replace(YY_RETURN_DATA, EMPTY_STR); 58 | String[] functionAndData = temp.split(SPLIT_MARK); 59 | if (functionAndData.length >= 1) { 60 | // functionAndData[0] = _fetchQueue 61 | return functionAndData[0]; 62 | } 63 | return null; 64 | } 65 | 66 | 67 | /** 68 | * js 文件将注入为第一个script引用 69 | * 70 | * @param view WebView 71 | * @param url url 72 | */ 73 | public static void webViewLoadJs(WebView view, String url) { 74 | String js = "var newscript = document.createElement(\"script\");"; 75 | js += "newscript.src=\"" + url + "\";"; 76 | js += "document.scripts[0].parentNode.insertBefore(newscript,document.scripts[0]);"; 77 | view.loadUrl("javascript:" + js); 78 | } 79 | 80 | /** 81 | * 这里只是加载lib包中assets中的 WebViewJavascriptBridge.js 82 | * 83 | * @param view webview 84 | * @param path 路径 85 | */ 86 | public static void webViewLoadLocalJs(WebView view, String path) { 87 | String jsContent = assetFile2Str(view.getContext(), path); 88 | view.loadUrl("javascript:" + jsContent); 89 | } 90 | 91 | /** 92 | * 解析assets文件夹里面的代码,去除注释,取可执行的代码 93 | * 94 | * @param c context 95 | * @param urlStr 路径 96 | * @return 可执行代码 97 | */ 98 | public static String assetFile2Str(Context c, String urlStr) { 99 | InputStream in = null; 100 | try { 101 | in = c.getAssets().open(urlStr); 102 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in)); 103 | String line = null; 104 | StringBuilder sb = new StringBuilder(); 105 | do { 106 | line = bufferedReader.readLine(); 107 | if (line != null && !line.matches("^\\s*\\/\\/.*")) { // 去除注释 108 | sb.append(line); 109 | } 110 | } while (line != null); 111 | 112 | bufferedReader.close(); 113 | in.close(); 114 | 115 | return sb.toString(); 116 | } catch (Exception e) { 117 | e.printStackTrace(); 118 | } finally { 119 | if (in != null) { 120 | try { 121 | in.close(); 122 | } catch (IOException e) { 123 | e.printStackTrace(); 124 | } 125 | } 126 | } 127 | return null; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /weber-test/src/main/java/vip/ruoyun/weber/test/WeBerDemo.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.net.Uri; 5 | import android.os.Build; 6 | import android.support.annotation.RequiresApi; 7 | import android.view.ViewGroup; 8 | import android.webkit.*; 9 | 10 | import java.util.ArrayList; 11 | 12 | public class WeBerDemo { 13 | 14 | private ArrayList webChromeClients = new ArrayList<>(); 15 | private ArrayList webViewClients = new ArrayList<>(); 16 | 17 | private WebView webView; 18 | 19 | public static void init() { 20 | 21 | } 22 | 23 | 24 | @SuppressLint("ObsoleteSdkInt") 25 | public void addWebChromeClient(WebChromeClient webChromeClient) { 26 | WebSettings settings = webView.getSettings(); 27 | 28 | settings.setSavePassword(false);//关闭密码保存提醒功能 29 | //通过以下设置,防止越权访问,跨域等安全问题: 30 | settings.setAllowFileAccess(false);//使其不能加载本地的 html 文件,但是 x5 需要打开 31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 32 | settings.setAllowFileAccessFromFileURLs(false); 33 | settings.setAllowUniversalAccessFromFileURLs(false); 34 | } 35 | 36 | 37 | //JSCore 38 | // 39 | 40 | 41 | // 对于Android调用JS代码的方法有2种: 42 | 43 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {//小于 4.3 要删除多余的 js 对象 44 | webView.removeJavascriptInterface("searchBoxJavaBridge_"); 45 | webView.removeJavascriptInterface("accessibility"); 46 | webView.removeJavascriptInterface("accessibilityTraversal"); 47 | } 48 | 49 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 50 | 51 | 52 | String name = "Nihao "; 53 | String method = "javascript:callJS(\"" + name + "\")"; 54 | method = "javascript:callJS(\"你好\")"; 55 | //19 4.4 以上的版本才能运行 占比99.16% https://mta.qq.com/mta/data/device/os 56 | webView.evaluateJavascript(method, new ValueCallback() { 57 | @Override 58 | public void onReceiveValue(String value) { 59 | 60 | } 61 | }); 62 | // loadUrl evaluateJavascript 63 | 64 | webView.evaluateJavascript(method, null); 65 | } else { 66 | //必须要在页面加载完成之后才能调用 67 | webView.loadUrl("javascript:callJS()"); 68 | } 69 | 70 | //往 js 中注入类 .17之前不安全 71 | webView.addJavascriptInterface(new AndroidtoJs(), "test"); 72 | 73 | webView.loadUrl("file:///android_asset/javascript.html"); 74 | } 75 | 76 | 77 | // 继承自Object类 78 | @WeBerBridge("test") 79 | public class AndroidtoJs { 80 | 81 | // 定义JS需要调用的方法 82 | // 被JS调用的方法必须加入@JavascriptInterface注解 83 | @JavascriptInterface 84 | public void hello(String msg) { 85 | System.out.println("JS调用了Android的hello方法"); 86 | } 87 | 88 | @JavascriptInterface 89 | public String test(String msg) { 90 | 91 | return "你好"; 92 | } 93 | } 94 | 95 | @WeBerBridge("test") 96 | public class Android2JS { 97 | public String test(String msg) { 98 | return "你好"; 99 | } 100 | 101 | } 102 | 103 | //https://android.googlesource.com/platform/cts/+/764c7c7fd72240330c15a3bcb1c7bd99cde83a1c/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java 104 | //https://html.spec.whatwg.org/multipage/web-messaging.html#messagechannel 105 | @RequiresApi(api = Build.VERSION_CODES.M) 106 | public void setWebChromeClient() { 107 | //第三种方式 postWebMessage WebMessagePort 108 | WebMessagePort[] webMessageChannel = webView.createWebMessageChannel(); 109 | // webView.postWebMessage(webMessageChannel); 110 | 111 | 112 | final WebMessagePort[] channel = webView.createWebMessageChannel(); 113 | 114 | WebMessagePort port = channel[0]; 115 | port.setWebMessageCallback(new WebMessagePort.WebMessageCallback() { 116 | @Override 117 | public void onMessage(WebMessagePort port, WebMessage message) { 118 | 119 | message.getData(); 120 | 121 | } 122 | }); 123 | 124 | webView.postWebMessage(new WebMessage("", new WebMessagePort[]{channel[1]}), 125 | Uri.parse("")); 126 | 127 | 128 | } 129 | 130 | 131 | public void test() { 132 | //方式1. 加载一个网页: 133 | webView.loadUrl("http://www.google.com/"); 134 | 135 | //方式2:加载apk包中的html页面 136 | webView.loadUrl("file:///android_asset/test.html"); 137 | 138 | //方式3:加载手机本地的html页面 139 | webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html"); 140 | 141 | // 方式4: 加载 HTML 页面的一小段内容 142 | /** 143 | * 参数说明: 144 | * 参数1:需要截取展示的内容 145 | * 内容里不能出现 ’#’, ‘%’, ‘\’ , ‘?’ 这四个字符,若出现了需用 %23, %25, %27, %3f 对应来替代,否则会出现异常 146 | * 参数2:展示内容的类型 147 | * 参数3:字节码 148 | * webView.loadData(String data, String mimeType, String encoding) 149 | */ 150 | //webView.loadData(String data, String mimeType, String encoding) 151 | 152 | } 153 | 154 | public void destory() { 155 | if (webView != null) { 156 | ((ViewGroup) webView.getParent()).removeView(webView);//rootView.removeView(mWebView); 157 | webView.destroy(); 158 | webView = null; 159 | } 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /weber-x5-core/src/main/java/vip/ruoyun/webkit/x5/WeBer.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.util.Log; 7 | import com.tencent.smtt.export.external.TbsCoreSettings; 8 | import com.tencent.smtt.sdk.QbSdk; 9 | import com.tencent.smtt.sdk.TbsVideo; 10 | import com.tencent.smtt.sdk.ValueCallback; 11 | import com.tencent.smtt.sdk.WebView; 12 | import java.util.HashMap; 13 | 14 | public class WeBer { 15 | 16 | public static final String debugTBSUrl = "http://debugtbs.qq.com/"; 17 | 18 | static String authority = "provider"; 19 | 20 | public static Builder with() { 21 | return new Builder(); 22 | } 23 | 24 | public interface Interceptor { 25 | 26 | void beforeInit(Context context); 27 | } 28 | 29 | public static class Builder { 30 | 31 | private boolean isMultiProcessOptimize = false; 32 | 33 | private QbSdk.PreInitCallback preInitCallback; 34 | 35 | private Interceptor interceptor; 36 | 37 | private Builder() { 38 | } 39 | 40 | /** 41 | * 接⼊TBS SDK后,⾸次启动卡顿怎么办? 42 | *

43 | * 由于在Android 5.0 +系统使⽤Art虚拟机在APP⾸次加载Dex时会进⾏Dex2oat,会有⼀定耗时,APP 44 | *

45 | * 接⼊时可以尽早的调⽤QbSdk.initX5Environment⽅法,在异步线程初始化。如果想达到进⼀步的优 化效果,可以使⽤SpeedyClassLoader⽅案进⾏集成 46 | * 多进程方式 47 | * 在调用TBS init 初始化之前、创建WebView之前进行如下配置,以开启优化方案 48 | */ 49 | public Builder multiProcessOptimize(boolean isMultiProcessOptimize) { 50 | this.isMultiProcessOptimize = isMultiProcessOptimize; 51 | return this; 52 | } 53 | 54 | public Builder preInitCallBack(@NonNull QbSdk.PreInitCallback preInitCallback) { 55 | this.preInitCallback = preInitCallback; 56 | return this; 57 | } 58 | 59 | /** 60 | * 在初始化之前做一些配置 61 | */ 62 | public Builder interceptor(Interceptor interceptor) { 63 | this.interceptor = interceptor; 64 | return this; 65 | } 66 | 67 | public Builder authority(@NonNull String authority) { 68 | WeBer.authority = authority; 69 | return this; 70 | } 71 | 72 | public void build(Context context) { 73 | if (isMultiProcessOptimize) { 74 | HashMap map = new HashMap(); 75 | map.put(TbsCoreSettings.TBS_SETTINGS_USE_SPEEDY_CLASSLOADER, true); 76 | map.put(TbsCoreSettings.TBS_SETTINGS_USE_DEXLOADER_SERVICE, true); 77 | QbSdk.initTbsSettings(map); 78 | } 79 | if (interceptor != null) { 80 | interceptor.beforeInit(context); 81 | } 82 | QbSdk.initX5Environment(context.getApplicationContext(), preInitCallback); //x5内核初始化接口 83 | } 84 | } 85 | 86 | //--------------静态方法---------------------------- 87 | 88 | /** 89 | * 得到错误信息,当客户端 crash 的时候 90 | * map.put("x5crashInfo", x5CrashInfo); 91 | */ 92 | public static String getCrashMessage(Context context) { 93 | return WebView.getCrashExtraMessage(context); 94 | } 95 | 96 | 97 | /** 98 | * 播放视频的调用接口 99 | * 通过TbsVideo的静态方法,如下: 100 | * canUseTbsPlayer(context) : 判断当前Tbs播放器是否已经可以使用 101 | * openVideo(context,videoUrl) : 直接调用播放接口,传入视频流的url 102 | * openVideo(context,videoUrl,extraData) : extraData对象是根据定制需要传入约定的信息,没有需要可以传如null 103 | * screenMode 102 来实现默认全屏+控制栏等UI 104 | */ 105 | public static boolean playVideo(Context context, String videoUrl) { 106 | return playVideo(context, videoUrl, null); 107 | } 108 | 109 | public static boolean playVideo(Context context, String videoUrl, Bundle extraData) { 110 | if (TbsVideo.canUseTbsPlayer(context)) {//是否可以播放 111 | TbsVideo.openVideo(context, videoUrl, extraData); 112 | return true; 113 | } 114 | return false; 115 | } 116 | 117 | /** 118 | * 目前TSB还不支持在线预览功能,只支持本地文件打开,打开方法请参考官网文件版本sdk内的使用说明https://x5.tencent.com/tbs/sdk.html 119 | * 使用要求: android.permission.WRITE_EXTERNAL_STORAGE 权限 120 | * 121 | * @param context 调起 miniqb 的 Activity 的 context。此参数只能是 activity 类型的 context,不能设置为 Application 的 context。 122 | * @param filePath 文件路径。格式为 android 本地存储路径格式,例如:/sdcard/Download/xxx.doc. 不支持 file:/// 格式。暂不支持在线文件。 123 | * @param params miniqb 的扩展功能。为非必填项,可传入 null 使用默认设置。 124 | * local: “true”表示是进入文件查看器,如果不设置或设置为“false”,则进入 miniqb 浏览器模式。不是必 须设置项。 125 | * style: “0”表示文件查看器使用默认的 UI 样式。“1”表示文件查看器使用微信的 UI 样式。不设置此 key 或设置错误值,则为默认 UI 样式。 126 | * topBarBgColor: 定制文件查看器的顶部栏背景色。格式为“#xxxxxx”,例“#2CFC47”;不设置此 key 或设置 错误值,则为默认 UI 样式。 127 | * menuData: 该参数用来定制文件右上角弹出菜单,可传入菜单项的 icon 的文本,用户点击菜单项后,sdk 会通过 startActivity+intent 的方式回调。 128 | * @param valueCallback 提供 miniqb 打开/关闭时给调用方回调通知,以便应用层做相应处理。 在单独进程打开文件的场景中,回调参数出现如下字符时,表示可以关闭当前进程,避免内存占用。 129 | * @return 1:用 QQ 浏览器打开 2:用 MiniQB 打开 3:调起阅读器弹框 -1:filePath 为空 打开失败 130 | */ 131 | public static int openFile(Context context, String filePath, HashMap params, 132 | final ValueCallback valueCallback) { 133 | QbSdk.canOpenFile(context, filePath, new ValueCallback() { 134 | @Override 135 | public void onReceiveValue(Boolean isCanOpen) { 136 | valueCallback.onReceiveValue(isCanOpen); 137 | } 138 | }); 139 | return QbSdk.openFileReader(context, filePath, params, new ValueCallback() { 140 | @Override 141 | public void onReceiveValue(String val) { 142 | Log.d("test", "onReceiveValue,val =" + val); 143 | } 144 | }); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/helper/WebViewHelper.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper.helper; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | import android.webkit.CookieManager; 6 | import android.webkit.WebSettings; 7 | import android.webkit.WebView; 8 | import android.webkit.WebViewClient; 9 | 10 | 11 | /** 12 | * Created by ruoyun on 2018/7/4. 13 | * Author:若云 14 | * Mail:zyhdvlp@gmail.com 15 | * Depiction: 16 | */ 17 | public class WebViewHelper { 18 | private WebView mWebView; 19 | 20 | private WebViewHelper(WebView mWebView) { 21 | this.mWebView = mWebView; 22 | } 23 | 24 | public WebView getWebView() { 25 | return mWebView; 26 | } 27 | 28 | public void setWebViewClient(WebViewClient mWebViewClient) { 29 | mWebView.setWebViewClient(mWebViewClient); 30 | } 31 | 32 | public void addJavascriptInterface(BaseJavascriptInterface javascriptInterface, String name) { 33 | mWebView.addJavascriptInterface(javascriptInterface, name); 34 | } 35 | 36 | public void removeJavascriptInterface(String name) { 37 | mWebView.removeJavascriptInterface(name); 38 | } 39 | 40 | public static class Builder { 41 | private boolean acceptCookie = true; 42 | private boolean javaScriptEnabled = true; 43 | private boolean savePassWord = false; 44 | private boolean saveFormData = false; 45 | private boolean domStorageEnabled = true; 46 | private boolean allowFileAccess = true; 47 | private boolean builtInZoomControls = false; 48 | private boolean javaScriptCanOpenWindowsAutomatically = true; 49 | private int cacheMode = WebSettings.LOAD_NO_CACHE; 50 | private boolean blockNetworkImage = false; 51 | private int scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY; 52 | private int backgroundColor = 0; 53 | private int mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW; 54 | 55 | public Builder buildAcceptCookie(boolean acceptCookie) { 56 | this.acceptCookie = acceptCookie; 57 | return this; 58 | } 59 | 60 | public Builder buildJavaScriptEnabled(boolean javaScriptEnabled) { 61 | this.javaScriptEnabled = javaScriptEnabled; 62 | return this; 63 | } 64 | 65 | public Builder buildSavePassWord(boolean savePassWord) { 66 | this.savePassWord = savePassWord; 67 | return this; 68 | } 69 | 70 | public Builder buildSaveFormData(boolean saveFormData) { 71 | this.saveFormData = saveFormData; 72 | return this; 73 | } 74 | 75 | public Builder buildDomStorageEnabled(boolean domStorageEnabled) { 76 | this.domStorageEnabled = domStorageEnabled; 77 | return this; 78 | } 79 | 80 | public Builder buildAllowFileAccess(boolean allowFileAccess) { 81 | this.allowFileAccess = allowFileAccess; 82 | return this; 83 | } 84 | 85 | public Builder buildBuiltInZoomControls(boolean builtInZoomControls) { 86 | this.builtInZoomControls = builtInZoomControls; 87 | return this; 88 | } 89 | 90 | public Builder buildJavaScriptCanOpenWindowsAutomatically(boolean javaScriptCanOpenWindowsAutomatically) { 91 | this.javaScriptCanOpenWindowsAutomatically = javaScriptCanOpenWindowsAutomatically; 92 | return this; 93 | } 94 | 95 | public Builder buildCacheMode(int cacheMode) { 96 | this.cacheMode = cacheMode; 97 | return this; 98 | } 99 | 100 | public Builder buildBlockNetworkImage(boolean blockNetworkImage) { 101 | this.blockNetworkImage = blockNetworkImage; 102 | return this; 103 | } 104 | 105 | public Builder buildsScrollBarStyle(int scrollBarStyle) { 106 | this.scrollBarStyle = scrollBarStyle; 107 | return this; 108 | } 109 | 110 | public Builder buildBackgroundColor(int backgroundColor) { 111 | this.backgroundColor = backgroundColor; 112 | return this; 113 | } 114 | 115 | public Builder buildMixedContentMode(int mixedContentMode) { 116 | this.mixedContentMode = mixedContentMode; 117 | return this; 118 | } 119 | 120 | public WebViewHelper build(WebView webView) { 121 | WebViewHelper webViewHelper = new WebViewHelper(webView); 122 | CookieManager.getInstance().setAcceptCookie(acceptCookie); 123 | webView.getSettings().setJavaScriptEnabled(javaScriptEnabled); 124 | webView.getSettings().setSavePassword(savePassWord); 125 | webView.getSettings().setSaveFormData(saveFormData); 126 | webView.getSettings().setDomStorageEnabled(domStorageEnabled); 127 | webView.getSettings().setAllowFileAccess(allowFileAccess); 128 | webView.getSettings().setBuiltInZoomControls(builtInZoomControls); 129 | webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(javaScriptCanOpenWindowsAutomatically); 130 | webView.getSettings().setCacheMode(cacheMode);//设置 缓存模式 131 | webView.getSettings().setBlockNetworkImage(blockNetworkImage); 132 | webView.setScrollBarStyle(scrollBarStyle); 133 | webView.setBackgroundColor(backgroundColor); 134 | //在5.0以上,不允许一个HTML页面既有http也有https的加载,所以设置了下面的混合模式 135 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 136 | webView.getSettings().setMixedContentMode(mixedContentMode); 137 | } 138 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 139 | // webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 140 | // } 141 | return webViewHelper; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /weber-x5-jsbridge/src/main/assets/WeBerViewJsBridge.js: -------------------------------------------------------------------------------- 1 | //notation: js file can only use this kind of comments 2 | //since comments will cause error when use in webview.loadurl, 3 | //comments will be remove by java use regexp 4 | (function() { 5 | if (window.WebViewJavascriptBridge) { 6 | return; 7 | } 8 | 9 | var messagingIframe; 10 | var bizMessagingIframe; 11 | var sendMessageQueue = []; 12 | var receiveMessageQueue = []; 13 | var messageHandlers = {}; 14 | 15 | var CUSTOM_PROTOCOL_SCHEME = 'yy'; 16 | var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'; 17 | 18 | var responseCallbacks = {}; 19 | var uniqueId = 1; 20 | 21 | // 创建消息index队列iframe 22 | function _createQueueReadyIframe(doc) { 23 | messagingIframe = doc.createElement('iframe'); 24 | messagingIframe.style.display = 'none'; 25 | doc.documentElement.appendChild(messagingIframe); 26 | } 27 | //创建消息体队列iframe 28 | function _createQueueReadyIframe4biz(doc) { 29 | bizMessagingIframe = doc.createElement('iframe'); 30 | bizMessagingIframe.style.display = 'none'; 31 | doc.documentElement.appendChild(bizMessagingIframe); 32 | } 33 | //set default messageHandler 初始化默认的消息线程 34 | function init(messageHandler) { 35 | if (WebViewJavascriptBridge._messageHandler) { 36 | throw new Error('WebViewJavascriptBridge.init called twice'); 37 | } 38 | WebViewJavascriptBridge._messageHandler = messageHandler; 39 | var receivedMessages = receiveMessageQueue; 40 | receiveMessageQueue = null; 41 | for (var i = 0; i < receivedMessages.length; i++) { 42 | _dispatchMessageFromNative(receivedMessages[i]); 43 | } 44 | } 45 | 46 | // 发送 47 | function send(data, responseCallback) { 48 | _doSend({ 49 | data: data 50 | }, responseCallback); 51 | } 52 | 53 | // 注册线程 往数组里面添加值 54 | function registerHandler(handlerName, handler) { 55 | messageHandlers[handlerName] = handler; 56 | } 57 | // 调用线程 58 | function callHandler(handlerName, data, responseCallback) { 59 | _doSend({ 60 | handlerName: handlerName, 61 | data: data 62 | }, responseCallback); 63 | } 64 | 65 | //sendMessage add message, 触发native处理 sendMessage 66 | function _doSend(message, responseCallback) { 67 | if (responseCallback) { 68 | var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); 69 | responseCallbacks[callbackId] = responseCallback; 70 | message.callbackId = callbackId; 71 | } 72 | 73 | sendMessageQueue.push(message); 74 | messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; 75 | } 76 | 77 | // 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容 78 | function _fetchQueue() { 79 | var messageQueueString = JSON.stringify(sendMessageQueue); 80 | sendMessageQueue = []; 81 | //android can't read directly the return data, so we can reload iframe src to communicate with java 82 | if (messageQueueString !== '[]') { 83 | bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString); 84 | } 85 | } 86 | 87 | //提供给native使用, 88 | function _dispatchMessageFromNative(messageJSON) { 89 | setTimeout(function() { 90 | var message = JSON.parse(messageJSON); 91 | var responseCallback; 92 | //java call finished, now need to call js callback function 93 | if (message.responseId) { 94 | responseCallback = responseCallbacks[message.responseId]; 95 | if (!responseCallback) { 96 | return; 97 | } 98 | responseCallback(message.responseData); 99 | delete responseCallbacks[message.responseId]; 100 | } else { 101 | //直接发送 102 | if (message.callbackId) { 103 | var callbackResponseId = message.callbackId; 104 | responseCallback = function(responseData) { 105 | _doSend({ 106 | responseId: callbackResponseId, 107 | responseData: responseData 108 | }); 109 | }; 110 | } 111 | 112 | var handler = WebViewJavascriptBridge._messageHandler; 113 | if (message.handlerName) { 114 | handler = messageHandlers[message.handlerName]; 115 | } 116 | //查找指定handler 117 | try { 118 | handler(message.data, responseCallback); 119 | } catch (exception) { 120 | if (typeof console != 'undefined') { 121 | console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception); 122 | } 123 | } 124 | } 125 | }); 126 | } 127 | 128 | //提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以 129 | function _handleMessageFromNative(messageJSON) { 130 | console.log(messageJSON); 131 | if (receiveMessageQueue) { 132 | receiveMessageQueue.push(messageJSON); 133 | } 134 | _dispatchMessageFromNative(messageJSON); 135 | 136 | } 137 | 138 | var WebViewJavascriptBridge = window.WebViewJavascriptBridge = { 139 | init: init, 140 | send: send, 141 | registerHandler: registerHandler, 142 | callHandler: callHandler, 143 | _fetchQueue: _fetchQueue, 144 | _handleMessageFromNative: _handleMessageFromNative 145 | }; 146 | 147 | var doc = document; 148 | _createQueueReadyIframe(doc); 149 | _createQueueReadyIframe4biz(doc); 150 | var readyEvent = doc.createEvent('Events'); 151 | readyEvent.initEvent('WebViewJavascriptBridgeReady'); 152 | readyEvent.bridge = WebViewJavascriptBridge; 153 | doc.dispatchEvent(readyEvent); 154 | })(); 155 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /weber-test/src/main/java/vip/ruoyun/weber/test/WeBer.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.weber.test; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.view.View; 6 | import android.webkit.WebSettings; 7 | import android.webkit.WebView; 8 | 9 | /** 10 | * Created by ruoyun on 2019-05-29. 11 | * Author:若云 12 | * Mail:zyhdvlp@gmail.com 13 | * Depiction: 14 | */ 15 | public class WeBer { 16 | 17 | private Builder builder; 18 | 19 | private WeBer(Builder builder) { 20 | this.builder = builder; 21 | } 22 | 23 | 24 | public static Builder init() { 25 | return new Builder(); 26 | } 27 | 28 | 29 | public static class Builder { 30 | //https://www.jianshu.com/p/0d7d429bd216 31 | 32 | //WebSettings 33 | private boolean javaScriptEnabled = true;//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript 34 | private boolean pluginsEnabled = true;//支持插件 35 | 36 | //设置自适应屏幕,两者合用(下面这两个方法合用) 37 | private boolean useWideViewPort = true;//将图片调整到适合webview的大小 38 | 39 | /** 40 | * 设置WebView是否使用预览模式加载界面。 41 | * 是否允许WebView度超出以概览的方式载入页面,默认false 42 | * 即缩小内容以适应屏幕宽度。该项设置在内容宽度超出WebView控件的宽度时生效,例如当getUseWideViewPort() 返回true时。 43 | */ 44 | private boolean loadWithOverviewMode = false; 45 | 46 | /** 47 | * 设置WebView是否支持使用屏幕控件或手势进行缩放,默认是true,支持缩放。 48 | */ 49 | private boolean supportZoom = true; // 支持缩放,默认为true。是下面那个的前提 50 | /** 51 | * 设置WebView是否使用其内置的变焦机制,该机制集合屏幕缩放控件使用,默认是false,不使用内置变焦机制。 52 | * 设置内置的缩放控件。若为false,则该WebView不可缩放 53 | */ 54 | private boolean builtInZoomControls = true; 55 | /** 56 | * 设置WebView使用内置缩放机制时,是否展现在屏幕缩放控件上,默认true,展现在控件上。 57 | */ 58 | private boolean displayZoomControls = false; 59 | /** 60 | * 设置WebView是否通过手势触发播放媒体,默认是true,需要手势触发。 61 | */ 62 | private boolean mediaPlaybackRequiresUserGesture = false; 63 | 64 | //其他细节操作 65 | /** 66 | * 缓存模式如下: 67 | * LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据 68 | * LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。 69 | * LOAD_NO_CACHE: 不使用缓存,只从网络获取数据. 70 | * LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。 71 | */ 72 | private int cacheMode = WebSettings.LOAD_DEFAULT; 73 | /** 74 | * 设置脚本是否允许自动打开弹窗,默认false,不允许 75 | */ 76 | private boolean javaScriptCanOpenWindowsAutomatically = true; 77 | /** 78 | * 设置WebView是否加载图片资源,默认true,自动加载图片 79 | */ 80 | private boolean loadsImagesAutomatically = true; //支持自动加载图片 81 | /** 82 | * 是否禁止从网络(通过http和https URI schemes访问的资源)下载图片资源,默认值为false。 83 | * 注意,除非getLoadsImagesAutomatically()返回true,否则该方法无效。还请注意,即使此项设置为false, 84 | * 使用setBlockNetworkLoads(boolean)禁止所有网络加载也会阻止网络图片的加载。 85 | * 当此项设置的值从true变为false,WebView当前显示的内容所引用的网络图片资源会自动获取 86 | */ 87 | private boolean blockNetworkImage = false; 88 | /** 89 | * 设置WebView加载页面文本内容的编码,默认“UTF-8”。 90 | */ 91 | private String defaultTextEncodingName = "utf-8";//设置编码格式 92 | /** 93 | * 设置是否开启DOM存储API权限,默认false,未开启, 94 | */ 95 | private boolean domStorageEnabled = true; 96 | /** 97 | * 设置是否开启数据库存储API权限,默认false,未开启, 98 | */ 99 | private boolean databaseEnabled = false; 100 | /** 101 | * 设置Application缓存API是否开启,默认false,设置有效的缓存路径参考setAppCachePath(String path)方法 102 | */ 103 | private boolean appCacheEnabled = true; 104 | /** 105 | * 设置当前Application缓存文件路径,Application Cache API能够开启需要指定Application具备写入权限的路径 106 | */ 107 | private String appcachepath = ""; 108 | /** 109 | * MIXED_CONTENT_ALWAYS_ALLOW 允许从任何来源加载内容,即使起源是不安全的; 110 | * MIXED_CONTENT_NEVER_ALLOW 不允许Https加载Http的内容,即不允许从安全的起源去加载一个不安全的资源; 111 | * MIXED_CONTENT_COMPLTIBILITY_MODE 当涉及到混合式内容时,WebView会尝试去兼容最新Web浏览器的风格; 112 | */ 113 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 114 | private int mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW;//原因是Android 5.0上Webview默认不允许加载Http与Https混合内容 115 | 116 | /** 117 | * 开启后,在用户输入密码时,会弹出提示框:询问用户是否保存密码; 118 | * 如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险 119 | */ 120 | private boolean savePassWord = false;//保存密码 121 | 122 | 123 | /** 124 | * 设置是否允许 WebView 使用 File 协议,默认设置为true,即允许在 File 域下执行任意 JavaScript 代码 125 | * 如果使用 https://github.com/Tencent/VasSonic 框架的话,需要打开此选项 126 | */ 127 | private boolean allowFileAccess = true; //默认 false 128 | /** 129 | * 是否允许通过 file url 加载的 Js代码读取其他的本地文件,file:///etc/hosts 130 | * 当设置成为 false 时,上述JS的攻击代码执行会导致错误,表示浏览器禁止从 file url 中的 JavaScript 读取其它本地文件。 131 | * 在Android 4.1前默认允许 , 在Android 4.1后默认禁止 132 | */ 133 | private boolean allowFileAccessFromFileURLs = false; // 134 | /** 135 | * 是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源) 136 | * 在Android 4.1前默认允许(setAllowFileAccessFromFileURLs()不起作用),在Android 4.1后默认禁止 137 | */ 138 | private boolean allowUniversalAccessFromFileURLs = false; //设置可以访问文件 139 | 140 | /** 141 | * 是否允许在WebView中访问内容URL(Content Url),默认允许。内容Url访问允许WebView从安装在系统中的内容提供者载入内容。 142 | */ 143 | private boolean allowContentAccess = true; 144 | 145 | 146 | private boolean acceptCookie = true; 147 | 148 | /** 149 | * 设置WebView是否保存表单数据,默认true,保存数据。 150 | * 在Android O中,该平台实现了一个功能齐全的自动填充功能来存储表单数据。因此,禁用了Webview表单数据保存功能。 151 | * 请注意,该功能将继续支持旧版本的Android和以前一样。 152 | */ 153 | private boolean saveFormData = false; 154 | 155 | 156 | /** 157 | * 设置WebView中加载页面字体变焦百分比,默认100,整型数。 158 | */ 159 | private int textZoom = 100; 160 | 161 | /** 162 | * 设置WebView访问第三方Cookies策略, 163 | */ 164 | private boolean AcceptThirdPartyCookies = false; 165 | 166 | /** 167 | * 设置WebView是否支持多屏窗口 168 | */ 169 | private boolean supportMultipleWindows = true; 170 | /** 171 | * 设置是否开启定位功能,默认true,开启定位 172 | */ 173 | private boolean geolocationEnabled = true; 174 | private long appCacheMaxSize = Long.MAX_VALUE; 175 | 176 | /** 177 | * 设置是否支持flash插件 178 | */ 179 | private WebSettings.PluginState setPluginState = WebSettings.PluginState.ON_DEMAND; 180 | 181 | private int scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY; 182 | private int backgroundColor = 0; 183 | 184 | 185 | private WebView webView; 186 | 187 | private Builder() { 188 | 189 | } 190 | 191 | public WeBer build(WebView webView) { 192 | this.webView = webView; 193 | WebSettings settings = webView.getSettings(); 194 | settings.setAppCachePath("");//每个 Application 只调用一次 195 | settings.setAppCacheMaxSize(Long.MAX_VALUE);// 196 | settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS); 197 | // settings.setAppCacheEnabled(Long.MAX_VALUE);// 198 | // settings.setSaveFormData(Long.MAX_VALUE);// 199 | // settings.setSaveFormData(Long.MAX_VALUE);// 200 | // settings.setAllowContentAccess(Long.MAX_VALUE);// 201 | 202 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 203 | settings.setMixedContentMode(mixedContentMode); 204 | } 205 | 206 | 207 | return new WeBer(this); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/weber/WeBer.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper.weber; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.view.View; 7 | import android.webkit.ValueCallback; 8 | import com.tencent.smtt.sdk.WebSettings; 9 | 10 | import vip.ruoyun.webkit.x5.WeBerView; 11 | 12 | public class WeBer { 13 | 14 | private WeBer() { 15 | 16 | } 17 | 18 | public static void init(Context context) { 19 | // weber.init(context); 20 | } 21 | 22 | public static void openFile(Context context, ValueCallback valueCallback) { 23 | // WeBerHelper.openFile(context, valueCallback); 24 | } 25 | 26 | public static Builder init() { 27 | return new Builder(); 28 | } 29 | 30 | public static class Builder { 31 | //https://www.jianshu.com/p/0d7d429bd216 32 | 33 | //WebSettings 34 | private boolean javaScriptEnabled = true;//如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript 35 | private boolean pluginsEnabled = true;//支持插件 36 | 37 | //设置自适应屏幕,两者合用(下面这两个方法合用) 38 | private boolean useWideViewPort = true;//将图片调整到适合webview的大小 39 | 40 | /** 41 | * 设置WebView是否使用预览模式加载界面。 42 | * 是否允许WebView度超出以概览的方式载入页面,默认false 43 | * 即缩小内容以适应屏幕宽度。该项设置在内容宽度超出WebView控件的宽度时生效,例如当getUseWideViewPort() 返回true时。 44 | */ 45 | private boolean loadWithOverviewMode = false; 46 | 47 | /** 48 | * 设置WebView是否支持使用屏幕控件或手势进行缩放,默认是true,支持缩放。 49 | */ 50 | private boolean supportZoom = true; // 支持缩放,默认为true。是下面那个的前提 51 | /** 52 | * 设置WebView是否使用其内置的变焦机制,该机制集合屏幕缩放控件使用,默认是false,不使用内置变焦机制。 53 | * 设置内置的缩放控件。若为false,则该WebView不可缩放 54 | */ 55 | private boolean builtInZoomControls = true; 56 | /** 57 | * 设置WebView使用内置缩放机制时,是否展现在屏幕缩放控件上,默认true,展现在控件上。 58 | */ 59 | private boolean displayZoomControls = false; 60 | /** 61 | * 设置WebView是否通过手势触发播放媒体,默认是true,需要手势触发。 62 | */ 63 | private boolean mediaPlaybackRequiresUserGesture = false; 64 | 65 | //其他细节操作 66 | /** 67 | * 缓存模式如下: 68 | * LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据 69 | * LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。 70 | * LOAD_NO_CACHE: 不使用缓存,只从网络获取数据. 71 | * LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。 72 | */ 73 | private int cacheMode = WebSettings.LOAD_DEFAULT; 74 | /** 75 | * 设置脚本是否允许自动打开弹窗,默认false,不允许 76 | */ 77 | private boolean javaScriptCanOpenWindowsAutomatically = true; 78 | /** 79 | * 设置WebView是否加载图片资源,默认true,自动加载图片 80 | */ 81 | private boolean loadsImagesAutomatically = true; //支持自动加载图片 82 | /** 83 | * 是否禁止从网络(通过http和https URI schemes访问的资源)下载图片资源,默认值为false。 84 | * 注意,除非getLoadsImagesAutomatically()返回true,否则该方法无效。还请注意,即使此项设置为false, 85 | * 使用setBlockNetworkLoads(boolean)禁止所有网络加载也会阻止网络图片的加载。 86 | * 当此项设置的值从true变为false,WebView当前显示的内容所引用的网络图片资源会自动获取 87 | */ 88 | private boolean blockNetworkImage = false; 89 | /** 90 | * 设置WebView加载页面文本内容的编码,默认“UTF-8”。 91 | */ 92 | private String defaultTextEncodingName = "utf-8";//设置编码格式 93 | /** 94 | * 设置是否开启DOM存储API权限,默认false,未开启, 95 | */ 96 | private boolean domStorageEnabled = true; 97 | /** 98 | * 设置是否开启数据库存储API权限,默认false,未开启, 99 | */ 100 | private boolean databaseEnabled = false; 101 | /** 102 | * 设置Application缓存API是否开启,默认false,设置有效的缓存路径参考setAppCachePath(String path)方法 103 | */ 104 | private boolean appCacheEnabled = true; 105 | /** 106 | * 设置当前Application缓存文件路径,Application Cache API能够开启需要指定Application具备写入权限的路径 107 | */ 108 | private String appcachepath = ""; 109 | /** 110 | * MIXED_CONTENT_ALWAYS_ALLOW 允许从任何来源加载内容,即使起源是不安全的; 111 | * MIXED_CONTENT_NEVER_ALLOW 不允许Https加载Http的内容,即不允许从安全的起源去加载一个不安全的资源; 112 | * MIXED_CONTENT_COMPLTIBILITY_MODE 当涉及到混合式内容时,WebView会尝试去兼容最新Web浏览器的风格; 113 | */ 114 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 115 | // private int mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW;//原因是Android 5.0上Webview默认不允许加载Http与Https混合内容 116 | 117 | /** 118 | * 开启后,在用户输入密码时,会弹出提示框:询问用户是否保存密码; 119 | * 如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险 120 | */ 121 | private boolean savePassWord = false;//保存密码 122 | 123 | 124 | /** 125 | * 设置是否允许 WebView 使用 File 协议,默认设置为true,即允许在 File 域下执行任意 JavaScript 代码 126 | * 如果使用 https://github.com/Tencent/VasSonic 框架的话,需要打开此选项 127 | */ 128 | private boolean allowFileAccess = true; //默认 false 129 | /** 130 | * 是否允许通过 file url 加载的 Js代码读取其他的本地文件,file:///etc/hosts 131 | * 当设置成为 false 时,上述JS的攻击代码执行会导致错误,表示浏览器禁止从 file url 中的 JavaScript 读取其它本地文件。 132 | * 在Android 4.1前默认允许 , 在Android 4.1后默认禁止 133 | */ 134 | private boolean allowFileAccessFromFileURLs = false; // 135 | /** 136 | * 是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源) 137 | * 在Android 4.1前默认允许(setAllowFileAccessFromFileURLs()不起作用),在Android 4.1后默认禁止 138 | */ 139 | private boolean allowUniversalAccessFromFileURLs = false; //设置可以访问文件 140 | 141 | /** 142 | * 是否允许在WebView中访问内容URL(Content Url),默认允许。内容Url访问允许WebView从安装在系统中的内容提供者载入内容。 143 | */ 144 | private boolean allowContentAccess = true; 145 | 146 | 147 | private boolean acceptCookie = true; 148 | 149 | /** 150 | * 设置WebView是否保存表单数据,默认true,保存数据。 151 | * 在Android O中,该平台实现了一个功能齐全的自动填充功能来存储表单数据。因此,禁用了Webview表单数据保存功能。 152 | * 请注意,该功能将继续支持旧版本的Android和以前一样。 153 | */ 154 | private boolean saveFormData = false; 155 | 156 | 157 | /** 158 | * 设置WebView中加载页面字体变焦百分比,默认100,整型数。 159 | */ 160 | private int textZoom = 100; 161 | 162 | /** 163 | * 设置WebView访问第三方Cookies策略, 164 | */ 165 | private boolean AcceptThirdPartyCookies = false; 166 | 167 | /** 168 | * 设置WebView是否支持多屏窗口 169 | */ 170 | private boolean supportMultipleWindows = true; 171 | /** 172 | * 设置是否开启定位功能,默认true,开启定位 173 | */ 174 | private boolean geolocationEnabled = true; 175 | private long appCacheMaxSize = Long.MAX_VALUE; 176 | 177 | /** 178 | * 设置是否支持flash插件 179 | */ 180 | private WebSettings.PluginState setPluginState = WebSettings.PluginState.ON_DEMAND; 181 | 182 | private int scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY; 183 | private int backgroundColor = 0; 184 | 185 | 186 | private WeBerView webView; 187 | 188 | private Builder() { 189 | 190 | } 191 | 192 | public void build(WeBerView webView) { 193 | this.webView = webView; 194 | com.tencent.smtt.sdk.WebSettings settings = webView.getSettings(); 195 | settings.setAppCachePath("");//每个 Application 只调用一次 196 | settings.setAppCacheMaxSize(Long.MAX_VALUE);// 197 | settings.setLayoutAlgorithm(com.tencent.smtt.sdk.WebSettings.LayoutAlgorithm.NARROW_COLUMNS); 198 | // settings.setAppCacheEnabled(Long.MAX_VALUE);// 199 | // settings.setSaveFormData(Long.MAX_VALUE);// 200 | // settings.setSaveFormData(Long.MAX_VALUE);// 201 | // settings.setAllowContentAccess(Long.MAX_VALUE);// 202 | 203 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 204 | // settings.setMixedContentMode(mixedContentMode); 205 | } 206 | } 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /app/proguard-rules-x5-web.pro: -------------------------------------------------------------------------------- 1 | #-optimizationpasses 7 2 | #-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 3 | -dontoptimize 4 | -dontusemixedcaseclassnames 5 | -verbose 6 | -dontskipnonpubliclibraryclasses 7 | -dontskipnonpubliclibraryclassmembers 8 | -dontwarn dalvik.** 9 | -dontwarn com.tencent.smtt.** 10 | #-overloadaggressively 11 | 12 | # ------------------ Keep LineNumbers and properties ---------------- # 13 | -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod 14 | # -------------------------------------------------------------------------- 15 | 16 | # Addidional for x5.sdk classes for apps 17 | 18 | -keep class com.tencent.smtt.export.external.**{ 19 | *; 20 | } 21 | 22 | -keep class com.tencent.tbs.video.interfaces.IUserStateChangedListener { 23 | *; 24 | } 25 | 26 | -keep class com.tencent.smtt.sdk.CacheManager { 27 | public *; 28 | } 29 | 30 | -keep class com.tencent.smtt.sdk.CookieManager { 31 | public *; 32 | } 33 | 34 | -keep class com.tencent.smtt.sdk.WebHistoryItem { 35 | public *; 36 | } 37 | 38 | -keep class com.tencent.smtt.sdk.WebViewDatabase { 39 | public *; 40 | } 41 | 42 | -keep class com.tencent.smtt.sdk.WebBackForwardList { 43 | public *; 44 | } 45 | 46 | -keep public class com.tencent.smtt.sdk.WebView { 47 | public ; 48 | public ; 49 | } 50 | 51 | -keep public class com.tencent.smtt.sdk.WebView$HitTestResult { 52 | public static final ; 53 | public java.lang.String getExtra(); 54 | public int getType(); 55 | } 56 | 57 | -keep public class com.tencent.smtt.sdk.WebView$WebViewTransport { 58 | public ; 59 | } 60 | 61 | -keep public class com.tencent.smtt.sdk.WebView$PictureListener { 62 | public ; 63 | public ; 64 | } 65 | 66 | 67 | -keepattributes InnerClasses 68 | 69 | -keep public enum com.tencent.smtt.sdk.WebSettings$** { 70 | *; 71 | } 72 | 73 | -keep public enum com.tencent.smtt.sdk.QbSdk$** { 74 | *; 75 | } 76 | 77 | -keep public class com.tencent.smtt.sdk.WebSettings { 78 | public *; 79 | } 80 | 81 | 82 | -keepattributes Signature 83 | -keep public class com.tencent.smtt.sdk.ValueCallback { 84 | public ; 85 | public ; 86 | } 87 | 88 | -keep public class com.tencent.smtt.sdk.WebViewClient { 89 | public ; 90 | public ; 91 | } 92 | 93 | -keep public class com.tencent.smtt.sdk.DownloadListener { 94 | public ; 95 | public ; 96 | } 97 | 98 | -keep public class com.tencent.smtt.sdk.WebChromeClient { 99 | public ; 100 | public ; 101 | } 102 | 103 | -keep public class com.tencent.smtt.sdk.WebChromeClient$FileChooserParams { 104 | public ; 105 | public ; 106 | } 107 | 108 | -keep class com.tencent.smtt.sdk.SystemWebChromeClient{ 109 | public *; 110 | } 111 | # 1. extension interfaces should be apparent 112 | -keep public class com.tencent.smtt.export.external.extension.interfaces.* { 113 | public protected *; 114 | } 115 | 116 | # 2. interfaces should be apparent 117 | -keep public class com.tencent.smtt.export.external.interfaces.* { 118 | public protected *; 119 | } 120 | 121 | -keep public class com.tencent.smtt.sdk.WebViewCallbackClient { 122 | public protected *; 123 | } 124 | 125 | -keep public class com.tencent.smtt.sdk.WebStorage$QuotaUpdater { 126 | public ; 127 | public ; 128 | } 129 | 130 | -keep public class com.tencent.smtt.sdk.WebIconDatabase { 131 | public ; 132 | public ; 133 | } 134 | 135 | -keep public class com.tencent.smtt.sdk.WebStorage { 136 | public ; 137 | public ; 138 | } 139 | 140 | -keep public class com.tencent.smtt.sdk.DownloadListener { 141 | public ; 142 | public ; 143 | } 144 | 145 | -keep public class com.tencent.smtt.sdk.QbSdk { 146 | public ; 147 | public ; 148 | } 149 | 150 | -keep public class com.tencent.smtt.sdk.QbSdk$PreInitCallback { 151 | public ; 152 | public ; 153 | } 154 | -keep public class com.tencent.smtt.sdk.CookieSyncManager { 155 | public ; 156 | public ; 157 | } 158 | 159 | -keep public class com.tencent.smtt.sdk.Tbs* { 160 | public ; 161 | public ; 162 | } 163 | 164 | -keep public class com.tencent.smtt.utils.LogFileUtils { 165 | public ; 166 | public ; 167 | } 168 | 169 | -keep public class com.tencent.smtt.utils.TbsLog { 170 | public ; 171 | public ; 172 | } 173 | 174 | -keep public class com.tencent.smtt.utils.TbsLogClient { 175 | public ; 176 | public ; 177 | } 178 | 179 | -keep public class com.tencent.smtt.sdk.CookieSyncManager { 180 | public ; 181 | public ; 182 | } 183 | 184 | # Added for game demos 185 | -keep public class com.tencent.smtt.sdk.TBSGamePlayer { 186 | public ; 187 | public ; 188 | } 189 | 190 | -keep public class com.tencent.smtt.sdk.TBSGamePlayerClient* { 191 | public ; 192 | public ; 193 | } 194 | 195 | -keep public class com.tencent.smtt.sdk.TBSGamePlayerClientExtension { 196 | public ; 197 | public ; 198 | } 199 | 200 | -keep public class com.tencent.smtt.sdk.TBSGamePlayerService* { 201 | public ; 202 | public ; 203 | } 204 | 205 | -keep public class com.tencent.smtt.utils.Apn { 206 | public ; 207 | public ; 208 | } 209 | -keep class com.tencent.smtt.** { 210 | *; 211 | } 212 | # end 213 | 214 | 215 | -keep public class com.tencent.smtt.export.external.extension.proxy.ProxyWebViewClientExtension { 216 | public ; 217 | public ; 218 | } 219 | 220 | -keep class MTT.ThirdAppInfoNew { 221 | *; 222 | } 223 | 224 | -keep class com.tencent.mtt.MttTraceEvent { 225 | *; 226 | } 227 | 228 | # Game related 229 | -keep public class com.tencent.smtt.gamesdk.* { 230 | public protected *; 231 | } 232 | 233 | -keep public class com.tencent.smtt.sdk.TBSGameBooter { 234 | public ; 235 | public ; 236 | } 237 | 238 | -keep public class com.tencent.smtt.sdk.TBSGameBaseActivity { 239 | public protected *; 240 | } 241 | 242 | -keep public class com.tencent.smtt.sdk.TBSGameBaseActivityProxy { 243 | public protected *; 244 | } 245 | 246 | -keep public class com.tencent.smtt.gamesdk.internal.TBSGameServiceClient { 247 | public *; 248 | } 249 | #--------------------------------------------------------------------------- 250 | 251 | 252 | #------------------ 下方是android平台自带的排除项,这里不要动 ---------------- 253 | 254 | -keep public class * extends android.app.Activity{ 255 | public ; 256 | public ; 257 | } 258 | -keep public class * extends android.app.Application{ 259 | public ; 260 | public ; 261 | } 262 | -keep public class * extends android.app.Service 263 | -keep public class * extends android.content.BroadcastReceiver 264 | -keep public class * extends android.content.ContentProvider 265 | -keep public class * extends android.app.backup.BackupAgentHelper 266 | -keep public class * extends android.preference.Preference 267 | 268 | -keepclassmembers enum * { 269 | public static **[] values(); 270 | public static ** valueOf(java.lang.String); 271 | } 272 | 273 | -keepclasseswithmembers class * { 274 | public (android.content.Context, android.util.AttributeSet); 275 | } 276 | 277 | -keepclasseswithmembers class * { 278 | public (android.content.Context, android.util.AttributeSet, int); 279 | } 280 | 281 | -keepattributes *Annotation* 282 | 283 | -keepclasseswithmembernames class *{ 284 | native ; 285 | } 286 | 287 | -keep class * implements android.os.Parcelable { 288 | public static final android.os.Parcelable$Creator *; 289 | } 290 | 291 | #------------------ 下方是共性的排除项目 ---------------- 292 | # 方法名中含有“JNI”字符的,认定是Java Native Interface方法,自动排除 293 | # 方法名中含有“JRI”字符的,认定是Java Reflection Interface方法,自动排除 294 | 295 | -keepclasseswithmembers class * { 296 | ... *JNI*(...); 297 | } 298 | 299 | -keepclasseswithmembernames class * { 300 | ... *JRI*(...); 301 | } 302 | 303 | -keep class **JNI* {*;} 304 | 305 | -------------------------------------------------------------------------------- /weber-x5-core/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | #-optimizationpasses 7 2 | #-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 3 | -dontoptimize 4 | -dontusemixedcaseclassnames 5 | -verbose 6 | -dontskipnonpubliclibraryclasses 7 | -dontskipnonpubliclibraryclassmembers 8 | -dontwarn dalvik.** 9 | -dontwarn com.tencent.smtt.** 10 | #-overloadaggressively 11 | 12 | # ------------------ Keep LineNumbers and properties ---------------- # 13 | -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod 14 | # -------------------------------------------------------------------------- 15 | 16 | # Addidional for x5.sdk classes for apps 17 | 18 | -keep class com.tencent.smtt.export.external.**{ 19 | *; 20 | } 21 | 22 | -keep class com.tencent.tbs.video.interfaces.IUserStateChangedListener { 23 | *; 24 | } 25 | 26 | -keep class com.tencent.smtt.sdk.CacheManager { 27 | public *; 28 | } 29 | 30 | -keep class com.tencent.smtt.sdk.CookieManager { 31 | public *; 32 | } 33 | 34 | -keep class com.tencent.smtt.sdk.WebHistoryItem { 35 | public *; 36 | } 37 | 38 | -keep class com.tencent.smtt.sdk.WebViewDatabase { 39 | public *; 40 | } 41 | 42 | -keep class com.tencent.smtt.sdk.WebBackForwardList { 43 | public *; 44 | } 45 | 46 | -keep public class com.tencent.smtt.sdk.WebView { 47 | public ; 48 | public ; 49 | } 50 | 51 | -keep public class com.tencent.smtt.sdk.WebView$HitTestResult { 52 | public static final ; 53 | public java.lang.String getExtra(); 54 | public int getType(); 55 | } 56 | 57 | -keep public class com.tencent.smtt.sdk.WebView$WebViewTransport { 58 | public ; 59 | } 60 | 61 | -keep public class com.tencent.smtt.sdk.WebView$PictureListener { 62 | public ; 63 | public ; 64 | } 65 | 66 | 67 | -keepattributes InnerClasses 68 | 69 | -keep public enum com.tencent.smtt.sdk.WebSettings$** { 70 | *; 71 | } 72 | 73 | -keep public enum com.tencent.smtt.sdk.QbSdk$** { 74 | *; 75 | } 76 | 77 | -keep public class com.tencent.smtt.sdk.WebSettings { 78 | public *; 79 | } 80 | 81 | 82 | -keepattributes Signature 83 | -keep public class com.tencent.smtt.sdk.ValueCallback { 84 | public ; 85 | public ; 86 | } 87 | 88 | -keep public class com.tencent.smtt.sdk.WebViewClient { 89 | public ; 90 | public ; 91 | } 92 | 93 | -keep public class com.tencent.smtt.sdk.DownloadListener { 94 | public ; 95 | public ; 96 | } 97 | 98 | -keep public class com.tencent.smtt.sdk.WebChromeClient { 99 | public ; 100 | public ; 101 | } 102 | 103 | -keep public class com.tencent.smtt.sdk.WebChromeClient$FileChooserParams { 104 | public ; 105 | public ; 106 | } 107 | 108 | -keep class com.tencent.smtt.sdk.SystemWebChromeClient{ 109 | public *; 110 | } 111 | # 1. extension interfaces should be apparent 112 | -keep public class com.tencent.smtt.export.external.extension.interfaces.* { 113 | public protected *; 114 | } 115 | 116 | # 2. interfaces should be apparent 117 | -keep public class com.tencent.smtt.export.external.interfaces.* { 118 | public protected *; 119 | } 120 | 121 | -keep public class com.tencent.smtt.sdk.WebViewCallbackClient { 122 | public protected *; 123 | } 124 | 125 | -keep public class com.tencent.smtt.sdk.WebStorage$QuotaUpdater { 126 | public ; 127 | public ; 128 | } 129 | 130 | -keep public class com.tencent.smtt.sdk.WebIconDatabase { 131 | public ; 132 | public ; 133 | } 134 | 135 | -keep public class com.tencent.smtt.sdk.WebStorage { 136 | public ; 137 | public ; 138 | } 139 | 140 | -keep public class com.tencent.smtt.sdk.DownloadListener { 141 | public ; 142 | public ; 143 | } 144 | 145 | -keep public class com.tencent.smtt.sdk.QbSdk { 146 | public ; 147 | public ; 148 | } 149 | 150 | -keep public class com.tencent.smtt.sdk.QbSdk$PreInitCallback { 151 | public ; 152 | public ; 153 | } 154 | -keep public class com.tencent.smtt.sdk.CookieSyncManager { 155 | public ; 156 | public ; 157 | } 158 | 159 | -keep public class com.tencent.smtt.sdk.Tbs* { 160 | public ; 161 | public ; 162 | } 163 | 164 | -keep public class com.tencent.smtt.utils.LogFileUtils { 165 | public ; 166 | public ; 167 | } 168 | 169 | -keep public class com.tencent.smtt.utils.TbsLog { 170 | public ; 171 | public ; 172 | } 173 | 174 | -keep public class com.tencent.smtt.utils.TbsLogClient { 175 | public ; 176 | public ; 177 | } 178 | 179 | -keep public class com.tencent.smtt.sdk.CookieSyncManager { 180 | public ; 181 | public ; 182 | } 183 | 184 | # Added for game demos 185 | -keep public class com.tencent.smtt.sdk.TBSGamePlayer { 186 | public ; 187 | public ; 188 | } 189 | 190 | -keep public class com.tencent.smtt.sdk.TBSGamePlayerClient* { 191 | public ; 192 | public ; 193 | } 194 | 195 | -keep public class com.tencent.smtt.sdk.TBSGamePlayerClientExtension { 196 | public ; 197 | public ; 198 | } 199 | 200 | -keep public class com.tencent.smtt.sdk.TBSGamePlayerService* { 201 | public ; 202 | public ; 203 | } 204 | 205 | -keep public class com.tencent.smtt.utils.Apn { 206 | public ; 207 | public ; 208 | } 209 | -keep class com.tencent.smtt.** { 210 | *; 211 | } 212 | # end 213 | 214 | 215 | -keep public class com.tencent.smtt.export.external.extension.proxy.ProxyWebViewClientExtension { 216 | public ; 217 | public ; 218 | } 219 | 220 | -keep class MTT.ThirdAppInfoNew { 221 | *; 222 | } 223 | 224 | -keep class com.tencent.mtt.MttTraceEvent { 225 | *; 226 | } 227 | 228 | # Game related 229 | -keep public class com.tencent.smtt.gamesdk.* { 230 | public protected *; 231 | } 232 | 233 | -keep public class com.tencent.smtt.sdk.TBSGameBooter { 234 | public ; 235 | public ; 236 | } 237 | 238 | -keep public class com.tencent.smtt.sdk.TBSGameBaseActivity { 239 | public protected *; 240 | } 241 | 242 | -keep public class com.tencent.smtt.sdk.TBSGameBaseActivityProxy { 243 | public protected *; 244 | } 245 | 246 | -keep public class com.tencent.smtt.gamesdk.internal.TBSGameServiceClient { 247 | public *; 248 | } 249 | #--------------------------------------------------------------------------- 250 | 251 | 252 | #------------------ 下方是android平台自带的排除项,这里不要动 ---------------- 253 | 254 | -keep public class * extends android.app.Activity{ 255 | public ; 256 | public ; 257 | } 258 | -keep public class * extends android.app.Application{ 259 | public ; 260 | public ; 261 | } 262 | -keep public class * extends android.app.Service 263 | -keep public class * extends android.content.BroadcastReceiver 264 | -keep public class * extends android.content.ContentProvider 265 | -keep public class * extends android.app.backup.BackupAgentHelper 266 | -keep public class * extends android.preference.Preference 267 | 268 | -keepclassmembers enum * { 269 | public static **[] values(); 270 | public static ** valueOf(java.lang.String); 271 | } 272 | 273 | -keepclasseswithmembers class * { 274 | public (android.content.Context, android.util.AttributeSet); 275 | } 276 | 277 | -keepclasseswithmembers class * { 278 | public (android.content.Context, android.util.AttributeSet, int); 279 | } 280 | 281 | -keepattributes *Annotation* 282 | 283 | -keepclasseswithmembernames class *{ 284 | native ; 285 | } 286 | 287 | -keep class * implements android.os.Parcelable { 288 | public static final android.os.Parcelable$Creator *; 289 | } 290 | 291 | #------------------ 下方是共性的排除项目 ---------------- 292 | # 方法名中含有“JNI”字符的,认定是Java Native Interface方法,自动排除 293 | # 方法名中含有“JRI”字符的,认定是Java Reflection Interface方法,自动排除 294 | 295 | -keepclasseswithmembers class * { 296 | ... *JNI*(...); 297 | } 298 | 299 | -keepclasseswithmembernames class * { 300 | ... *JRI*(...); 301 | } 302 | 303 | -keep class **JNI* {*;} 304 | 305 | -keepclassmembers class * extends com.tencent.smtt.sdk.WebChromeClient{ 306 | public void openFileChooser(...); 307 | } -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/WebActivity.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.AlertDialog; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.graphics.Bitmap; 8 | import android.net.Uri; 9 | import android.net.http.SslError; 10 | import android.os.Build; 11 | import android.os.Bundle; 12 | import android.os.Handler; 13 | import android.os.Looper; 14 | import android.os.Message; 15 | import android.support.annotation.Nullable; 16 | import android.support.v7.app.AppCompatActivity; 17 | import android.util.Log; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.webkit.ClientCertRequest; 21 | import android.webkit.ConsoleMessage; 22 | import android.webkit.DownloadListener; 23 | import android.webkit.GeolocationPermissions; 24 | import android.webkit.JavascriptInterface; 25 | import android.webkit.JsPromptResult; 26 | import android.webkit.JsResult; 27 | import android.webkit.PermissionRequest; 28 | import android.webkit.SslErrorHandler; 29 | import android.webkit.ValueCallback; 30 | import android.webkit.WebChromeClient; 31 | import android.webkit.WebResourceRequest; 32 | import android.webkit.WebSettings; 33 | import android.webkit.WebStorage; 34 | import android.webkit.WebView; 35 | import android.webkit.WebViewClient; 36 | import android.widget.Button; 37 | import vip.ruoyun.webkit.WeBer; 38 | import vip.ruoyun.webkit.WeBerChromeClient; 39 | import vip.ruoyun.webkit.WeBerChromeClient.FileChooserIntercept; 40 | 41 | public class WebActivity extends AppCompatActivity { 42 | 43 | private WebView mWebView; 44 | 45 | private Button mButton; 46 | 47 | private ViewGroup rootView; 48 | 49 | private boolean isDebug; 50 | 51 | private Handler handler = new Handler(Looper.myLooper()); 52 | 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | setContentView(R.layout.activity_web); 57 | rootView = findViewById(R.id.rootView); 58 | mButton = findViewById(R.id.mButton); 59 | mButton.setOnClickListener(new View.OnClickListener() { 60 | @Override 61 | public void onClick(View v) { 62 | finish(); 63 | } 64 | }); 65 | 66 | mWebView = findViewById(R.id.mWebView); 67 | // mWebView.onResume(); 68 | // mWebView.loadUrl("https://www.baidu.com/"); 69 | //往 js 中注入类 .17之前不安全 70 | mWebView.addJavascriptInterface(new AndroidtoJs(), "test"); 71 | 72 | WebSettings wetSettings = mWebView.getSettings(); 73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//在Android 5.0之后,WebView默认不允许Https + Http的混合使用方式 74 | wetSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); 75 | } 76 | wetSettings.setDomStorageEnabled(true);//DOM storage 是HTML5提供的一种标准接口,主要将键值对存储在本地 77 | wetSettings.setJavaScriptEnabled(true); 78 | 79 | WeBerChromeClient weBerChromeClient = new WeBerChromeClient(this); 80 | weBerChromeClient.setFileChooserIntercept(new FileChooserIntercept() { 81 | @Override 82 | public boolean onFileChooserIntercept(final boolean isCapture, final String[] acceptType, 83 | final Intent intent) { 84 | return false; 85 | } 86 | }); 87 | mWebView.setWebChromeClient(weBerChromeClient); 88 | 89 | mWebView.setDownloadListener(new DownloadListener() { 90 | @Override 91 | public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, 92 | long contentLength) { 93 | 94 | } 95 | }); 96 | mWebView.setWebViewClient(new WebViewClient() { 97 | 98 | @Override 99 | public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { 100 | return super.shouldOverrideUrlLoading(view, request); 101 | } 102 | 103 | @Override 104 | public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { 105 | //客户端证书 106 | super.onReceivedClientCertRequest(view, request); 107 | } 108 | 109 | @Override 110 | public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) { 111 | 112 | //1.可以通过 url 来判断是否要接受证书 113 | if (error.getUrl().contains("http://ruoyun.vip") || error.getUrl().contains("https://ruoyun.vip")) { 114 | handler.proceed(); 115 | } else { 116 | handler.cancel();//super.onReceivedSslError(view, handler, error); 117 | } 118 | 119 | //2.弹框,让用户来决定 120 | //https://codeday.me/bug/20170927/77276.html 121 | final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext()); 122 | builder.setMessage("是否通过 ssl 验证"); 123 | builder.setPositiveButton("continue", new DialogInterface.OnClickListener() { 124 | @Override 125 | public void onClick(DialogInterface dialog, int which) { 126 | handler.proceed(); 127 | } 128 | }); 129 | builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() { 130 | @Override 131 | public void onClick(DialogInterface dialog, int which) { 132 | handler.cancel(); 133 | } 134 | }); 135 | final AlertDialog dialog = builder.create(); 136 | dialog.show(); 137 | 138 | //3.测试的时候通过 139 | // if (isDebug) {//因为使用抓包工具抓取https时,是需要安装证书的,验证自然无法通过。 140 | // handler.proceed();// 接受所有网站的证书 141 | // } else { 142 | // handler.cancel();//super.onReceivedSslError(view, handler, error); 143 | // } 144 | } 145 | }); 146 | //file:///android_asset/webpage/fileChooser.html 147 | mWebView.loadUrl("file:///android_asset/webpage/fileChooser.html"); 148 | 149 | } 150 | 151 | // 继承自Object类 152 | public class AndroidtoJs { 153 | 154 | // 定义JS需要调用的方法 155 | // 被JS调用的方法必须加入@JavascriptInterface注解 156 | @SuppressLint("NewApi") 157 | @JavascriptInterface 158 | public String hello(String msg) { 159 | long newTime = System.currentTimeMillis(); 160 | Log.e("zyh", newTime + ""); 161 | System.out.println("JS调用了Android的hello方法"); 162 | 163 | handler.post(new Runnable() { 164 | @Override 165 | public void run() { 166 | final long time = System.currentTimeMillis(); 167 | mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback() { 168 | @Override 169 | public void onReceiveValue(String value) { 170 | //回调消耗的时间比较久,是使用其他方式的 7 倍时间多. 171 | Log.d("zyh", "返回值为:" + value); 172 | Log.d("zyh", "cost time: " + (System.currentTimeMillis() - time)); 173 | } 174 | }); 175 | // mWebView.loadUrl("javascript:callJS()"); 176 | Log.d("zyh", "cost time: " + (System.currentTimeMillis() - time)); 177 | } 178 | }); 179 | return "你好,1111!"; 180 | } 181 | 182 | @JavascriptInterface 183 | public String test(String msg) { 184 | 185 | return "你好"; 186 | } 187 | 188 | @JavascriptInterface 189 | public void callback(String name, String path) { 190 | 191 | Log.d("zyh", "callback " + path); 192 | 193 | } 194 | } 195 | 196 | /** 197 | * 激活WebView为活跃状态,能正常执行网页的响应 198 | */ 199 | @Override 200 | protected void onResume() { 201 | super.onResume(); 202 | mWebView.onResume(); 203 | } 204 | 205 | /** 206 | * 当页面被失去焦点被切换到后台不可见状态,需要执行onPause 207 | * 通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。 208 | * 通知内核尝试停止所有处理,如动画和地理位置,但是不能停止Js,如果想全局停止Js, 209 | * 可以调用pauseTimers()全局停止Js,调用onResume()恢复。 210 | */ 211 | @Override 212 | public void onPause() { 213 | super.onPause(); 214 | mWebView.onPause(); 215 | } 216 | 217 | /** 218 | * 销毁Webview 219 | * 在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview 220 | * 但是注意:webview调用destory时, webview仍绑定在Activity上 221 | * 这是由于自定义webview构建时传入了该Activity的context对象 222 | * 因此需要先从父容器中移除webview, 然后再销毁webview: 223 | */ 224 | @Override 225 | protected void onDestroy() { 226 | if (mWebView != null) { 227 | ((ViewGroup) mWebView.getParent()).removeView(mWebView);//rootView.removeView(mWebView); 228 | mWebView.destroy(); 229 | mWebView = null; 230 | } 231 | 232 | super.onDestroy(); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /weber-x5-core/src/main/java/vip/ruoyun/webkit/x5/WeBerChromeClient.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit.x5; 2 | 3 | import android.app.Activity; 4 | import android.content.ContentValues; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.provider.MediaStore; 9 | import android.support.v4.app.FragmentActivity; 10 | import android.text.TextUtils; 11 | import com.tencent.smtt.sdk.ValueCallback; 12 | import com.tencent.smtt.sdk.WebChromeClient; 13 | import com.tencent.smtt.sdk.WebView; 14 | import java.io.File; 15 | import vip.ruoyun.helper.avoid.AvoidOnResultHelper; 16 | 17 | /** 18 | * Created by ruoyun on 2019-07-02. 19 | * Author:若云 20 | * Mail:zyhdvlp@gmail.com 21 | * Depiction: 22 | */ 23 | public class WeBerChromeClient extends WebChromeClient implements AvoidOnResultHelper.ActivityCallback { 24 | 25 | private boolean isCapture; 26 | 27 | private File mVFile; 28 | 29 | private ValueCallback uploadFile; 30 | 31 | private ValueCallback uploadFiles; 32 | 33 | private FileChooserIntercept fileChooserIntercept; 34 | 35 | private FragmentActivity fragmentActivity; 36 | 37 | private Uri fileUri; 38 | 39 | public WeBerChromeClient(FragmentActivity fragmentActivity) { 40 | this.fragmentActivity = fragmentActivity; 41 | } 42 | 43 | public interface FileChooserIntercept { 44 | 45 | /** 46 | * @param isCapture 是否是照相功能 47 | * @param acceptType input标签 acceptType的属性 48 | * @param intent 意图 49 | * @return 是否要拦截 50 | */ 51 | boolean onFileChooserIntercept(boolean isCapture, String[] acceptType, Intent intent); 52 | } 53 | 54 | public void setFileChooserIntercept(FileChooserIntercept fileChooserIntercept) { 55 | this.fileChooserIntercept = fileChooserIntercept; 56 | } 57 | 58 | // For Android < 3.0 59 | public void openFileChooser(ValueCallback valueCallback) { 60 | uploadFile = valueCallback; 61 | openFileChooseProcess(new String[]{"*/*"}); 62 | } 63 | 64 | // For Android >= 3.0 65 | public void openFileChooser(ValueCallback valueCallback, String acceptType) { 66 | uploadFile = valueCallback; 67 | if (TextUtils.isEmpty(acceptType)) { 68 | openFileChooseProcess(new String[]{"*/*"}); 69 | } else { 70 | openFileChooseProcess(new String[]{acceptType}); 71 | } 72 | } 73 | 74 | //For Android >= 4.1 75 | @Override 76 | public void openFileChooser(ValueCallback valueCallback, String acceptType, String capture) { 77 | uploadFile = valueCallback; 78 | isCapture = !TextUtils.isEmpty(capture); 79 | if (TextUtils.isEmpty(acceptType)) { 80 | openFileChooseProcess(new String[]{"*/*"}); 81 | } else { 82 | openFileChooseProcess(new String[]{acceptType}); 83 | } 84 | } 85 | 86 | // For Android >= 5.0 87 | @Override 88 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, 89 | FileChooserParams fileChooserParams) { 90 | uploadFiles = filePathCallback; 91 | isCapture = fileChooserParams.isCaptureEnabled(); 92 | if (fileChooserParams.getAcceptTypes() != null && fileChooserParams.getAcceptTypes().length > 0) { 93 | if (fileChooserParams.getAcceptTypes().length == 1 && TextUtils 94 | .isEmpty(fileChooserParams.getAcceptTypes()[0])) { 95 | openFileChooseProcess(new String[]{"*/*"}); 96 | } else { 97 | openFileChooseProcess(fileChooserParams.getAcceptTypes()); 98 | } 99 | } else { 100 | openFileChooseProcess(new String[]{"*/*"}); 101 | } 102 | return true; 103 | } 104 | 105 | /** 106 | * type 类型 :多个 "video/;image/" 单个 "image/*" 107 | * intent.setType(“video/;image/”);//同时选择视频和图片 108 | * i.setType("image/*");//图片 109 | * 110 | * @param acceptType 类型 111 | */ 112 | private void openFileChooseProcess(String[] acceptType) { 113 | StringBuilder typeBuilder = new StringBuilder(); 114 | for (int i = 0; i < acceptType.length; i++) { 115 | typeBuilder.append(acceptType[i]); 116 | if (i < acceptType.length - 1) { 117 | typeBuilder.append(";"); 118 | } 119 | } 120 | String acceptTypeString = typeBuilder.toString(); 121 | try { 122 | Intent intent = new Intent(); 123 | if (isCapture) { 124 | intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);// 启动系统相机 125 | intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 126 | intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 127 | // data/data/[appName]/cache,必须确保文件夹路径存在,否则拍照后无法完成回调 128 | mVFile = new File( 129 | fragmentActivity.getCacheDir() + File.separator + "weber", 130 | System.currentTimeMillis() + ".jpg"); 131 | if (!mVFile.getParentFile().exists()) { 132 | mVFile.getParentFile().mkdirs(); 133 | } 134 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 135 | fileUri = WeBerFileProvider 136 | .getUriForFile(fragmentActivity, 137 | fragmentActivity.getPackageName() + "." + WeBer.authority, 138 | mVFile); 139 | } else { 140 | fileUri = Uri.fromFile(mVFile); 141 | } 142 | intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); 143 | } else { 144 | if (acceptTypeString.contains("video/")) {//默认打开后置摄像头 145 | intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); 146 | intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); 147 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 148 | } else {//图片或者文件 149 | intent.setAction(Intent.ACTION_GET_CONTENT); 150 | intent.addCategory(Intent.CATEGORY_OPENABLE); 151 | intent.setType(acceptTypeString); 152 | intent = Intent.createChooser(intent, "File Chooser"); 153 | } 154 | } 155 | if (fileChooserIntercept != null) { 156 | if (fileChooserIntercept.onFileChooserIntercept(isCapture, acceptType, intent)) { 157 | return; 158 | } 159 | } 160 | AvoidOnResultHelper.startActivityForResult(fragmentActivity, intent, this); 161 | } catch (Exception e) {//当系统没有相机应用的时候该应用会闪退,所以 try catch 162 | e.printStackTrace(); 163 | //h5 的动作要有一致性, uploadFiles 赋值之后必须要有 onReceiveValue(),不然会影响其他功能 164 | if (uploadFiles != null) { 165 | uploadFiles.onReceiveValue(null); 166 | } else if (uploadFile != null) { 167 | uploadFile.onReceiveValue(null); 168 | } 169 | reset(); 170 | } 171 | } 172 | 173 | @Override 174 | public void onActivityResult(int resultCode, Intent data) { 175 | if (resultCode != Activity.RESULT_OK) { 176 | if (uploadFiles != null) { 177 | uploadFiles.onReceiveValue(null); 178 | } else if (uploadFile != null) { 179 | uploadFile.onReceiveValue(null); 180 | } 181 | reset(); 182 | return; 183 | } 184 | if (null == uploadFile && null == uploadFiles) { 185 | reset(); 186 | return; 187 | } 188 | Uri result = data == null ? null : data.getData(); 189 | Uri[] uris = result == null ? null : new Uri[]{result}; 190 | if (fileUri != null) { 191 | // afterOpenCamera(); 192 | result = fileUri; 193 | uris = new Uri[]{fileUri}; 194 | } 195 | if (uploadFiles != null) { 196 | uploadFiles.onReceiveValue(uris); 197 | } else if (uploadFile != null) { 198 | uploadFile.onReceiveValue(result); 199 | } 200 | reset(); 201 | } 202 | 203 | /** 204 | * 解决拍照后在相册中找不到的问题 205 | */ 206 | private void afterOpenCamera() { 207 | ContentValues values = new ContentValues(9); 208 | values.put(MediaStore.Images.Media.TITLE, "Camera"); 209 | values.put(MediaStore.Images.Media.DISPLAY_NAME, mVFile.getName()); 210 | values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); 211 | values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); 212 | values.put(MediaStore.Images.Media.ORIENTATION, 0); 213 | values.put(MediaStore.Images.Media.DATA, mVFile.getAbsolutePath()); 214 | values.put(MediaStore.Images.Media.SIZE, mVFile.length()); 215 | Uri uri = fragmentActivity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 216 | // 通知相册更新 217 | fragmentActivity.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri)); 218 | } 219 | 220 | /** 221 | * 重置操作 222 | */ 223 | private void reset() { 224 | uploadFiles = null; 225 | uploadFile = null; 226 | fileUri = null; 227 | mVFile = null; 228 | isCapture = false; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /weber-core/src/main/java/vip/ruoyun/webkit/WeBerChromeClient.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webkit; 2 | 3 | import android.app.Activity; 4 | import android.content.ContentValues; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.os.Build.VERSION_CODES; 9 | import android.provider.MediaStore; 10 | import android.support.annotation.RequiresApi; 11 | import android.support.v4.app.FragmentActivity; 12 | import android.text.TextUtils; 13 | import android.webkit.ValueCallback; 14 | import android.webkit.WebChromeClient; 15 | import android.webkit.WebView; 16 | import java.io.File; 17 | import vip.ruoyun.helper.avoid.AvoidOnResultHelper; 18 | 19 | /** 20 | * Created by ruoyun on 2019-07-02. 21 | * Author:若云 22 | * Mail:zyhdvlp@gmail.com 23 | * Depiction: 24 | */ 25 | public class WeBerChromeClient extends WebChromeClient implements AvoidOnResultHelper.ActivityCallback { 26 | 27 | private boolean isCapture; 28 | 29 | private File mVFile; 30 | 31 | private ValueCallback uploadFile; 32 | 33 | private ValueCallback uploadFiles; 34 | 35 | private FileChooserIntercept fileChooserIntercept; 36 | 37 | private FragmentActivity fragmentActivity; 38 | 39 | private Uri fileUri; 40 | 41 | public WeBerChromeClient(FragmentActivity fragmentActivity) { 42 | this.fragmentActivity = fragmentActivity; 43 | } 44 | 45 | public interface FileChooserIntercept { 46 | 47 | /** 48 | * @param isCapture 是否是照相功能 49 | * @param acceptType input标签 acceptType的属性 50 | * @param intent 意图 51 | * @return 是否要拦截 52 | */ 53 | boolean onFileChooserIntercept(boolean isCapture, String[] acceptType, Intent intent); 54 | } 55 | 56 | public void setFileChooserIntercept(FileChooserIntercept fileChooserIntercept) { 57 | this.fileChooserIntercept = fileChooserIntercept; 58 | } 59 | 60 | // For Android < 3.0 61 | public void openFileChooser(ValueCallback valueCallback) { 62 | uploadFile = valueCallback; 63 | openFileChooseProcess(new String[]{"*/*"}); 64 | } 65 | 66 | // For Android >= 3.0 67 | public void openFileChooser(ValueCallback valueCallback, String acceptType) { 68 | uploadFile = valueCallback; 69 | if (TextUtils.isEmpty(acceptType)) { 70 | openFileChooseProcess(new String[]{"*/*"}); 71 | } else { 72 | openFileChooseProcess(new String[]{acceptType}); 73 | } 74 | } 75 | 76 | //For Android >= 4.1 77 | public void openFileChooser(ValueCallback valueCallback, String acceptType, String capture) { 78 | uploadFile = valueCallback; 79 | isCapture = !TextUtils.isEmpty(capture); 80 | if (TextUtils.isEmpty(acceptType)) { 81 | openFileChooseProcess(new String[]{"*/*"}); 82 | } else { 83 | openFileChooseProcess(new String[]{acceptType}); 84 | } 85 | } 86 | 87 | // For Android >= 5.0 88 | @RequiresApi(api = VERSION_CODES.LOLLIPOP) 89 | @Override 90 | public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, 91 | FileChooserParams fileChooserParams) { 92 | uploadFiles = filePathCallback; 93 | isCapture = fileChooserParams.isCaptureEnabled(); 94 | if (fileChooserParams.getAcceptTypes() != null && fileChooserParams.getAcceptTypes().length > 0) { 95 | if (fileChooserParams.getAcceptTypes().length == 1 && TextUtils 96 | .isEmpty(fileChooserParams.getAcceptTypes()[0])) { 97 | openFileChooseProcess(new String[]{"*/*"}); 98 | } else { 99 | openFileChooseProcess(fileChooserParams.getAcceptTypes()); 100 | } 101 | } else { 102 | openFileChooseProcess(new String[]{"*/*"}); 103 | } 104 | return true; 105 | } 106 | 107 | /** 108 | * type 类型 :多个 "video/;image/" 单个 "image/*" 109 | * intent.setType(“video/;image/”);//同时选择视频和图片 110 | * i.setType("image/*");//图片 111 | * 112 | * @param acceptType 类型 113 | */ 114 | private void openFileChooseProcess(String[] acceptType) { 115 | StringBuilder typeBuilder = new StringBuilder(); 116 | for (int i = 0; i < acceptType.length; i++) { 117 | typeBuilder.append(acceptType[i]); 118 | if (i < acceptType.length - 1) { 119 | typeBuilder.append(";"); 120 | } 121 | } 122 | String acceptTypeString = typeBuilder.toString(); 123 | try { 124 | Intent intent = new Intent(); 125 | if (isCapture) { 126 | intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);// 启动系统相机 127 | intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 128 | intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 129 | // data/data/[appName]/cache,必须确保文件夹路径存在,否则拍照后无法完成回调 130 | mVFile = new File( 131 | fragmentActivity.getCacheDir() + File.separator + "weber", 132 | System.currentTimeMillis() + ".jpg"); 133 | if (!mVFile.getParentFile().exists()) { 134 | mVFile.getParentFile().mkdirs(); 135 | } 136 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 137 | fileUri = WeBerFileProvider 138 | .getUriForFile(fragmentActivity, 139 | fragmentActivity.getPackageName() + "." + WeBer.authority, 140 | mVFile); 141 | } else { 142 | fileUri = Uri.fromFile(mVFile); 143 | } 144 | intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); 145 | } else { 146 | if (acceptTypeString.contains("video/")) {//默认打开后置摄像头 147 | intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); 148 | intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); 149 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 150 | } else {//图片或者文件 151 | intent.setAction(Intent.ACTION_GET_CONTENT); 152 | intent.addCategory(Intent.CATEGORY_OPENABLE); 153 | intent.setType(acceptTypeString); 154 | intent = Intent.createChooser(intent, "File Chooser"); 155 | } 156 | } 157 | if (fileChooserIntercept != null) { 158 | if (fileChooserIntercept.onFileChooserIntercept(isCapture, acceptType, intent)) { 159 | return; 160 | } 161 | } 162 | AvoidOnResultHelper.startActivityForResult(fragmentActivity, intent, this); 163 | } catch (Exception e) {//当系统没有相机应用的时候该应用会闪退,所以 try catch 164 | e.printStackTrace(); 165 | //h5 的动作要有一致性, uploadFiles 赋值之后必须要有 onReceiveValue(),不然会影响其他功能 166 | if (uploadFiles != null) { 167 | uploadFiles.onReceiveValue(null); 168 | } else if (uploadFile != null) { 169 | uploadFile.onReceiveValue(null); 170 | } 171 | reset(); 172 | } 173 | } 174 | 175 | @Override 176 | public void onActivityResult(int resultCode, Intent data) { 177 | if (resultCode != Activity.RESULT_OK) { 178 | if (uploadFiles != null) { 179 | uploadFiles.onReceiveValue(null); 180 | } else if (uploadFile != null) { 181 | uploadFile.onReceiveValue(null); 182 | } 183 | reset(); 184 | return; 185 | } 186 | if (null == uploadFile && null == uploadFiles) { 187 | reset(); 188 | return; 189 | } 190 | Uri result = data == null ? null : data.getData(); 191 | Uri[] uris = result == null ? null : new Uri[]{result}; 192 | if (fileUri != null) { 193 | // afterOpenCamera(); 194 | result = fileUri; 195 | uris = new Uri[]{fileUri}; 196 | } 197 | if (uploadFiles != null) { 198 | uploadFiles.onReceiveValue(uris); 199 | } else if (uploadFile != null) { 200 | uploadFile.onReceiveValue(result); 201 | } 202 | reset(); 203 | } 204 | 205 | /** 206 | * 解决拍照后在相册中找不到的问题 207 | */ 208 | private void afterOpenCamera() { 209 | ContentValues values = new ContentValues(9); 210 | values.put(MediaStore.Images.Media.TITLE, "Camera"); 211 | values.put(MediaStore.Images.Media.DISPLAY_NAME, mVFile.getName()); 212 | values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); 213 | values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); 214 | values.put(MediaStore.Images.Media.ORIENTATION, 0); 215 | values.put(MediaStore.Images.Media.DATA, mVFile.getAbsolutePath()); 216 | values.put(MediaStore.Images.Media.SIZE, mVFile.length()); 217 | Uri uri = fragmentActivity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 218 | // 通知相册更新 219 | fragmentActivity.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri)); 220 | } 221 | 222 | /** 223 | * 重置操作 224 | */ 225 | private void reset() { 226 | uploadFiles = null; 227 | uploadFile = null; 228 | fileUri = null; 229 | mVFile = null; 230 | isCapture = false; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /app/src/main/java/vip/ruoyun/webviewhelper/WeberActivity.java: -------------------------------------------------------------------------------- 1 | package vip.ruoyun.webviewhelper; 2 | 3 | import android.content.Intent; 4 | import android.graphics.PixelFormat; 5 | import android.os.Bundle; 6 | import android.provider.MediaStore; 7 | import android.support.v4.app.FragmentActivity; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.util.Log; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.webkit.JavascriptInterface; 13 | import com.tencent.smtt.export.external.interfaces.SslError; 14 | import com.tencent.smtt.export.external.interfaces.SslErrorHandler; 15 | import com.tencent.smtt.sdk.CookieSyncManager; 16 | import com.tencent.smtt.sdk.ValueCallback; 17 | import com.tencent.smtt.sdk.WebView; 18 | import com.tencent.smtt.utils.TbsLog; 19 | import vip.ruoyun.webkit.x5.WeBerChromeClient; 20 | import vip.ruoyun.webkit.x5.WeBerView; 21 | import vip.ruoyun.webkit.x5.WeBerViewClient; 22 | import vip.ruoyun.webkit.x5.jsbridge.BridgeHandler; 23 | import vip.ruoyun.webkit.x5.jsbridge.WeBerViewBridgeClient; 24 | 25 | 26 | public class WeberActivity extends AppCompatActivity { 27 | 28 | private TestWeBerChromeClient chromeClient = new TestWeBerChromeClient(this); 29 | 30 | private TestWeBerViewClient viewClient; 31 | 32 | private WeBerView mWeBerView; 33 | 34 | private final String fileUrl = "file:///android_asset/webpage/fileChooser.html"; 35 | 36 | private final String videoUrl = "file:///android_asset/webpage/fullscreenVideo.html"; 37 | 38 | private final String jsbridgeUrl = "file:///android_asset/webpage/jsbridge.html"; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_weber); 44 | getWindow().setFormat(PixelFormat.TRANSLUCENT);//这个对宿主没什么影响,建议声明 45 | 46 | mWeBerView = findViewById(R.id.mWeBerView); 47 | 48 | viewClient = new TestWeBerViewClient(mWeBerView); 49 | 50 | viewClient.registerHandler("submitFromWeb", new BridgeHandler() { 51 | @Override 52 | public void handler(String data, ValueCallback valueCallback) { 53 | Log.i("zyh", "handler = submitFromWeb, data from web = " + data); 54 | valueCallback.onReceiveValue("submitFromWeb exe, response data 中文 from Java"); 55 | } 56 | }); 57 | 58 | // User user = new User(); 59 | // Location location = new Location(); 60 | // location.address = "SDU"; 61 | // user.location = location; 62 | // user.name = "大头鬼"; 63 | // 64 | // viewClient.callHandler("functionInJs", new Gson().toJson(user), new ValueCallback() { 65 | // @Override 66 | // public void onReceiveValue(String data) { 67 | // // TODO Auto-generated method stub 68 | // Log.i("zyh", "reponse data from js " + data); 69 | // } 70 | // }); 71 | 72 | viewClient.callHandler("functionInJs", "data from Java1111", new ValueCallback() { 73 | 74 | @Override 75 | public void onReceiveValue(String data) { 76 | // TODO Auto-generated method stub 77 | Log.i("zyh", "reponse data from js " + data); 78 | } 79 | 80 | }); 81 | 82 | WeBerView.setWebContentsDebuggingEnabled(true); 83 | 84 | mWeBerView.setWebChromeClient(chromeClient); 85 | mWeBerView.setWebViewClient(viewClient); 86 | viewClient.setOnLoadWebViewListener(new WeBerViewClient.OnLoadWebViewListener() { 87 | @Override 88 | public void onPageFinished(boolean isSuccess) { 89 | if (isSuccess) {//如果成功显示成功界面 90 | Log.d("zyh", "成功显示成功界面"); 91 | } else {//失败显示失败界面 92 | Log.d("zyh", "显示失败界面"); 93 | } 94 | } 95 | }); 96 | chromeClient.setFileChooserIntercept(new WeBerChromeClient.FileChooserIntercept() { 97 | @Override 98 | public boolean onFileChooserIntercept(boolean isCapture, String[] acceptType, Intent intent) { 99 | // if (MediaStore.ACTION_VIDEO_CAPTURE.equals(intent.getAction())) {//要使用摄像机 100 | // //要使用摄像机,判断权限 android.permission.CAMERA 101 | // return true;//拦截 102 | // } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(intent.getAction())) {//要使用照相机 103 | // //要使用照相机,判断权限 android.permission.CAMERA 104 | // return true;//拦截 105 | // } 106 | //处理 intent ,修改或者添加参数 107 | return false; 108 | } 109 | }); 110 | long time = System.currentTimeMillis(); 111 | // mWeBerView.loadUrl(WeBerHelper.debugTBSUrl); 112 | mWeBerView.loadUrl(fileUrl); 113 | // mWeBerView.loadUrl(jsbridgeUrl); 114 | TbsLog.d("time-cost", "cost time: " + (System.currentTimeMillis() - time)); 115 | CookieSyncManager.createInstance(this); 116 | CookieSyncManager.getInstance().sync(); 117 | 118 | mWeBerView.getView().setOverScrollMode(View.OVER_SCROLL_ALWAYS); 119 | mWeBerView.addJavascriptInterface(new WebViewJavaScriptFunction(), "Android"); 120 | } 121 | 122 | private class WebViewJavaScriptFunction { 123 | 124 | @JavascriptInterface 125 | public void onX5ButtonClicked() { 126 | PlayVideoFunc.enableX5FullscreenFunc(mWeBerView); 127 | } 128 | 129 | @JavascriptInterface 130 | public void onCustomButtonClicked() { 131 | PlayVideoFunc.disableX5FullscreenFunc(mWeBerView); 132 | } 133 | 134 | @JavascriptInterface 135 | public void onLiteWndButtonClicked() { 136 | PlayVideoFunc.enableLiteWndFunc(mWeBerView); 137 | } 138 | 139 | @JavascriptInterface 140 | public void onPageVideoClicked() { 141 | PlayVideoFunc.enablePageVideoFunc(mWeBerView); 142 | } 143 | } 144 | 145 | /** 146 | * 激活WebView为活跃状态,能正常执行网页的响应 147 | */ 148 | @Override 149 | protected void onResume() { 150 | super.onResume(); 151 | if (mWeBerView != null) { 152 | mWeBerView.onResume(); 153 | mWeBerView.resumeTimers(); 154 | } 155 | } 156 | 157 | /** 158 | * 当页面被失去焦点被切换到后台不可见状态,需要执行onPause 159 | * 通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。 160 | * 通知内核尝试停止所有处理,如动画和地理位置,但是不能停止Js,如果想全局停止Js, 161 | * 可以调用pauseTimers()全局停止Js,调用onResume()恢复。 162 | */ 163 | @Override 164 | public void onPause() { 165 | super.onPause(); 166 | if (mWeBerView != null) { 167 | mWeBerView.onPause(); 168 | mWeBerView.pauseTimers(); 169 | } 170 | } 171 | 172 | /** 173 | * 销毁Webview 174 | * 在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview 175 | * 但是注意:webview调用destory时, webview仍绑定在Activity上 176 | * 这是由于自定义webview构建时传入了该Activity的context对象 177 | * 因此需要先从父容器中移除webview, 然后再销毁webview: 178 | */ 179 | @Override 180 | protected void onDestroy() { 181 | if (mWeBerView != null) { 182 | ((ViewGroup) mWeBerView.getParent()).removeView(mWeBerView);//rootView.removeView(mWebView); 183 | mWeBerView.destroy(); 184 | mWeBerView = null; 185 | } 186 | super.onDestroy(); 187 | } 188 | 189 | @Override 190 | public void onBackPressed() { 191 | if (mWeBerView.canGoBack()) { 192 | mWeBerView.goBack(); 193 | return; 194 | } 195 | super.onBackPressed(); 196 | } 197 | 198 | private class TestWeBerChromeClient extends WeBerChromeClient { 199 | 200 | public TestWeBerChromeClient(FragmentActivity fragmentActivity) { 201 | super(fragmentActivity); 202 | } 203 | 204 | @Override 205 | public void onProgressChanged(WebView view, int newProgress) { 206 | if (newProgress == 100) { 207 | //加载完毕进度条消失 208 | // progressView.setVisibility(View.GONE); 209 | } else { 210 | //更新进度 211 | // progressView.setProgress(newProgress); 212 | } 213 | super.onProgressChanged(view, newProgress); 214 | } 215 | } 216 | 217 | private class TestWeBerViewClient extends WeBerViewBridgeClient { 218 | 219 | 220 | public TestWeBerViewClient(WeBerView weBerView) { 221 | super(weBerView); 222 | } 223 | 224 | @Override 225 | public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) { 226 | handler.proceed(); 227 | 228 | // //1.可以通过 url 来判断是否要接受证书 229 | // if (view.getUrl().contains("http://ruoyun.vip") || view.getUrl().contains("https://ruoyun.vip")) { 230 | // handler.proceed(); 231 | // } else { 232 | // handler.cancel();//super.onReceivedSslError(view, handler, error); 233 | // } 234 | 235 | //2.弹框,让用户来决定 236 | //https://codeday.me/bug/20170927/77276.html 237 | // final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext()); 238 | // builder.setMessage("是否通过 ssl 验证"); 239 | // builder.setPositiveButton("continue", new DialogInterface.OnClickListener() { 240 | // @Override 241 | // public void onClick(DialogInterface dialog, int which) { 242 | // handler.proceed(); 243 | // } 244 | // }); 245 | // builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() { 246 | // @Override 247 | // public void onClick(DialogInterface dialog, int which) { 248 | // handler.cancel(); 249 | // } 250 | // }); 251 | // final AlertDialog dialog = builder.create(); 252 | // dialog.show(); 253 | //3.测试的时候通过 254 | // if (isDebug) {//因为使用抓包工具抓取https时,是需要安装证书的,验证自然无法通过。 255 | // handler.proceed();// 接受所有网站的证书 256 | // } else { 257 | // handler.cancel();//super.onReceivedSslError(view, handler, error); 258 | // } 259 | 260 | } 261 | } 262 | 263 | static class User { 264 | 265 | String name; 266 | 267 | Location location; 268 | 269 | String testStr; 270 | } 271 | 272 | static class Location { 273 | 274 | String address; 275 | } 276 | } 277 | --------------------------------------------------------------------------------