├── 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 | 请长按一下html元素
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 |
15 |
16 |
17 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_web.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/weber-test/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/weber-core/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/weber-x5-core/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/weber-x5-jsbridge/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/weber-core/src/androidTest/java/vip/ruoyun/webkit/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.webkit;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("vip.ruoyun.webkit.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/vip/ruoyun/webviewhelper/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.webviewhelper;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("vip.ruoyun.webviewhelper", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/weber-test/src/androidTest/java/vip/ruoyun/weber/test/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.weber.test;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 |
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("vip.ruoyun.weber.test.test", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/weber-x5-core/src/androidTest/java/vip/ruoyun/webkit/x5/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.webkit.x5;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("vip.ruoyun.webkit.x5.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/weber-x5-jsbridge/src/androidTest/java/vip/ruoyun/webkit/x5/jsbridge/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.webkit.x5.jsbridge;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("vip.ruoyun.webkit.x5.jsbridge.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/assets/webpage/websocket.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | WebSocket 测试
9 |
10 |
11 |
12 |
34 |
--------------------------------------------------------------------------------
/weber-test/src/main/assets/javascript.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 你好
7 |
8 |
32 |
33 |
34 |
35 |
36 |
37 |
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 |
21 |
22 |
31 |
32 |
--------------------------------------------------------------------------------
/weber-test/src/main/java/vip/ruoyun/weber/test/WeBerView.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.weber.test;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.os.Build;
6 | import android.support.annotation.RequiresApi;
7 | import android.util.AttributeSet;
8 | import android.webkit.WebView;
9 |
10 | import java.util.HashMap;
11 |
12 | public class WeBerView extends WebView {
13 |
14 |
15 | public HashMap objectMap = new HashMap<>();
16 |
17 | public WeBerView(Context context) {
18 | super(context);
19 | }
20 |
21 | public WeBerView(Context context, AttributeSet attrs) {
22 | super(context, attrs);
23 | }
24 |
25 | public WeBerView(Context context, AttributeSet attrs, int defStyleAttr) {
26 | super(context, attrs, defStyleAttr);
27 | }
28 |
29 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
30 | public WeBerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
31 | super(context, attrs, defStyleAttr, defStyleRes);
32 | }
33 |
34 | public WeBerView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) {
35 | super(context, attrs, defStyleAttr, privateBrowsing);
36 | }
37 |
38 | @SuppressLint("JavascriptInterface")
39 | @Override
40 | public void addJavascriptInterface(Object object, String name) {
41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
42 | super.addJavascriptInterface(object, name);
43 | } else {
44 | objectMap.put(name, object);
45 | }
46 | }
47 |
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/weber-x5-core/src/main/java/vip/ruoyun/webkit/x5/WeBerViewClient.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.webkit.x5;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import com.tencent.smtt.sdk.WebView;
6 | import com.tencent.smtt.sdk.WebViewClient;
7 |
8 | public class WeBerViewClient extends WebViewClient {
9 |
10 | private boolean isReceivedError = false;
11 |
12 | private OnLoadWebViewListener onLoadWebViewListener;
13 |
14 | public void setOnLoadWebViewListener(OnLoadWebViewListener onLoadWebViewListener) {
15 | this.onLoadWebViewListener = onLoadWebViewListener;
16 | }
17 |
18 | public interface OnLoadWebViewListener {
19 | void onPageFinished(boolean isSuccess);
20 | }
21 |
22 | /**
23 | * 防止加载网页时调起系统浏览器
24 | */
25 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
26 | view.loadUrl(url);
27 | return true;
28 | }
29 |
30 |
31 | @Override
32 | public void onPageStarted(WebView view, String url, Bitmap favicon) {
33 | super.onPageStarted(view, url, favicon);
34 | isReceivedError = false;
35 | }
36 |
37 | @Override
38 | public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
39 | super.onReceivedError(view, errorCode, description, failingUrl);
40 | isReceivedError = true;
41 | }
42 |
43 | @Override
44 | public void onPageFinished(WebView view, String url) {
45 | super.onPageFinished(view, url);
46 | if (onLoadWebViewListener != null) {
47 | onLoadWebViewListener.onPageFinished(!isReceivedError);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/assets/webpage/fileChooser.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | file chooser
7 |
24 |
31 |
32 |
33 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "vip.ruoyun.webviewhelper"
7 | minSdkVersion 15
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | ndk {
13 | abiFilters "armeabi-v7a"
14 | }
15 | }
16 | buildTypes {
17 |
18 | debug {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro',
21 | 'proguard-rules-x5-web.pro'
22 | }
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro',
26 | 'proguard-rules-x5-web.pro'
27 | }
28 | }
29 | lintOptions {
30 | abortOnError false
31 | }
32 |
33 | }
34 |
35 | dependencies {
36 | implementation fileTree(dir: 'libs', include: ['*.jar'])
37 | implementation 'com.android.support:appcompat-v7:28.0.0'
38 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
39 | implementation 'com.android.support:webkit:28.0.0'
40 | implementation 'com.android.support:support-v4:28.0.0'
41 | testImplementation 'junit:junit:4.12'
42 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
43 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
44 | implementation project(path: ':weber-core')
45 | implementation project(path: ':weber-x5-jsbridge')
46 | implementation project(path: ':weber-x5-core')
47 | // implementation 'vip.ruoyun.webkit:weber-x5-core:1.0.7'
48 | }
49 |
--------------------------------------------------------------------------------
/weber-test/src/main/java/vip/ruoyun/weber/test/generate/WeBerBridgeImp.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.weber.test.generate;
2 |
3 | import android.text.TextUtils;
4 | import vip.ruoyun.weber.test.WeBerDemo.Android2JS;
5 | import vip.ruoyun.weber.test.WeBerDemo.AndroidtoJs;
6 | import vip.ruoyun.weber.test.WeBerView;
7 |
8 | import java.lang.reflect.Method;
9 | import java.util.HashMap;
10 |
11 |
12 | public class WeBerBridgeImp {
13 |
14 | public static void eve(Object object, String method, String callback, String value, WeBerView weBerView) {
15 | switch (object.getClass().getSimpleName()) {
16 | case "AndroidtoJs":
17 | AndroidtoJs androidtoJs = (AndroidtoJs) object;//获取对象
18 | switch (method) {
19 | case "hello":
20 | androidtoJs.hello(value);
21 | break;
22 | case "test":
23 | if (TextUtils.isEmpty(callback)) {
24 | String s = androidtoJs.test(value);
25 | weBerView.loadUrl("javascript:callJS(\"" + s + "\")");
26 | }
27 | break;
28 | }
29 | break;
30 | case "Android2JS":
31 | Android2JS android2JS = (Android2JS) object;//获取对象
32 | switch (method) {
33 | case "test":
34 | if (TextUtils.isEmpty(callback)) {
35 | String s = android2JS.test(value);
36 | weBerView.loadUrl("javascript:callJS(\"" + s + "\")");
37 | }
38 | break;
39 | }
40 | break;
41 | default:
42 | break;
43 | }
44 | }
45 |
46 |
47 |
48 | private HashMap methodMap = new HashMap<>();
49 |
50 |
51 |
52 |
53 |
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/vip/ruoyun/webviewhelper/App.java:
--------------------------------------------------------------------------------
1 | package vip.ruoyun.webviewhelper;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import com.tencent.smtt.sdk.QbSdk;
6 | import com.tencent.smtt.sdk.QbSdk.PreInitCallback;
7 | import com.tencent.smtt.sdk.TbsListener;
8 | import vip.ruoyun.webkit.x5.WeBer;
9 |
10 | public class App extends Application {
11 |
12 | @Override
13 | public void onCreate() {
14 | super.onCreate();
15 |
16 | WeBer.with()
17 | .multiProcessOptimize(true)//可选,接⼊TBS SDK后,解决⾸次启动卡顿问题
18 | .interceptor(new WeBer.Interceptor() { //在初始化之前做一些配置
19 | @Override
20 | public void beforeInit(final Context context) {
21 | //QbSdk 设置
22 | QbSdk.setDownloadWithoutWifi(true);
23 | QbSdk.setTbsListener(new TbsListener() {
24 | @Override
25 | public void onDownloadFinish(int i) {
26 | //tbs内核下载完成回调
27 | }
28 |
29 | @Override
30 | public void onInstallFinish(int i) {
31 | //内核安装完成回调,
32 | }
33 |
34 | @Override
35 | public void onDownloadProgress(int i) {
36 | //下载进度监听
37 | }
38 | });
39 | }
40 | })
41 | .preInitCallBack(new PreInitCallback() {
42 | @Override
43 | public void onCoreInitFinished() {
44 |
45 | }
46 |
47 | @Override
48 | public void onViewInitFinished(final boolean b) {
49 |
50 | }
51 | })
52 | .authority("provider")
53 | .build(this);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/weber-x5-core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
33 |
34 |
35 |
40 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/weber-test/src/main/assets/02.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 你好
7 |
8 |
68 |
69 |
70 |
71 |
72 |
73 |
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 |
--------------------------------------------------------------------------------