├── .gitignore ├── README.md ├── app ├── .gitignore ├── app-release.apk ├── build.gradle ├── proguard-rules.pro ├── simple.jks └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bridge │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── bridge.js │ │ ├── bridge_demo.html │ │ └── connect_error.html │ ├── java │ │ └── com │ │ │ └── bridge │ │ │ └── view │ │ │ ├── IInvokeJS.java │ │ │ ├── JavaInterfaces4JS.java │ │ │ ├── MainActivity.java │ │ │ ├── ResponseStatus.java │ │ │ └── fragment │ │ │ └── WebViewFragment.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── content_main.xml │ │ └── fragment_webview.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── bridge │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── simplejsjavabridgeLib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── simplejsjavabridge │ └── lib │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── assets │ └── js_native_bridge.js ├── java │ └── com │ │ └── simplejsjavabridge │ │ └── lib │ │ ├── IJavaCallback2JS.java │ │ ├── MethodHandler.java │ │ ├── Params.java │ │ ├── RequestResponseBuilder.java │ │ ├── SimpleJavaJSWebChromeClient.java │ │ ├── SimpleJavaJsBridge.java │ │ ├── annotation │ │ ├── InvokeJSInterface.java │ │ ├── JavaCallback4JS.java │ │ ├── JavaInterface4JS.java │ │ ├── Param.java │ │ ├── ParamCallback.java │ │ └── ParamResponseStatus.java │ │ └── exception │ │ └── SimpleJSBridgeException.java └── res │ └── values │ └── strings.xml └── test └── java └── com └── simplejsjavabridge └── lib └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .svn 2 | .svn/* 3 | *.class 4 | #*.apk 5 | bin/ 6 | bin/* 7 | gen/ 8 | gen/* 9 | lint.xml 10 | .DS_Store 11 | .settings/ 12 | .settings/* 13 | .gradle/* 14 | *.iml 15 | build/* 16 | import-summary.txt 17 | .idea/* 18 | local.properties 19 | .git 20 | obj/ 21 | 22 | .gradle/ 23 | .idea/ 24 | build/ 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #前言 2 | 最近接触android中js与java交互的东西很多,当然它们之间的交互方式有几种,但是我觉得这几种交互方式都存在一定的不足,这是我决定编写SimpleJavaJsBridge这个库的关键原因。 3 | 4 | 我会按以下顺序进行本文章: 5 | 6 | 1. 现有js与java通信方案及不足 7 | 2. js与java完美通信方案设计 8 | 3. SimpleJavaJsBridge 9 | 10 | 现在进入正题 11 | #1. 现有js与java通信方案及不足 12 | 先来说明一点js与java通信,指的是js既可以给java发送消息,同时java也可以给js发送消息。那就来屡屡它们之间的通信方案。 13 | 14 | **1.1 java给js发送消息** 15 | 16 | 官方唯一指定方法是通过webview的loadUrl(String)方法进行的,看下的伪代码: 17 | 18 | //例子:调用js的test(param)方法 19 | webView.loadUrl("javascript:test(1)"); 20 | 21 | 22 | 23 | 调用方法非常的简单,"javascript:"+js方法的名字+方法的参数值拼接成一个字符串就可以给js发送消息了,犹如是在直接调用js的方法。 24 | 25 | **1.2 js给java发送消息** 26 | 27 | js给java发送消息实际上只有2种方案,依次来分析下这2种方案。 28 | 29 | **1.2.1 官方方法** 30 | 31 | 先看下伪代码: 32 | 33 | //该类封装了提供给js调用的方法 34 | public class JSBridge{ 35 | //提供给js的方法 36 | public void invokeByJs(String msg){ 37 | 38 | } 39 | } 40 | 41 | //把JSBridge对象注入WebView中,同时起一个别名 42 | webView.addJavascriptInterface(new JSBridge(),"jsBridge"); 43 | 44 | //js调用java方法 45 | window.jsBridge.invokeByJs('hello java'); 46 | 47 | 48 | 49 | 这种方法其实是把一个对象注入到WebView中,js给java发送消息的方式是 50 | 51 | window.注入WebView的java对象的所对应name值.javaMethod(param...); 52 | 53 | 这其实也犹如在java代码中调用java的方法,因为java提供给js的方法名,方法参数是啥,js在发送消息时,方法名与参数必须保持一致,这也是这些java代码不能进行混淆的原因。 54 | 55 | 但是这种方法存在一个严重的漏洞,虽然官方在android4.4的时候给出了相应的解决方案,但是android4.4以下的版本还得解决该漏洞,因此一些巨人们就开始琢磨着解决这个坑,第二种方法由此诞生。 56 | 57 | **1.2.2 js传递约定好的字符串给java** 58 | 59 | 这种方案的主要原理是: 60 | - 找到一个js可以给java发送消息的入口(这个入口有onJsPrompt,onJsAlert等等) 61 | - js通过入口把消息按既定好的规则拼接成字符串传递给java 62 | - java按照既定好的规则对字符串进行解析 63 | - 根据解析数据,通过反射来调用自己相应方法 64 | 65 | 这种方法使用起来要比官方方法(第一种方法)麻烦。 66 | 67 | **1.3 存在的不足** 68 | 69 | 上面介绍了js与java的通信方法,那我就来分析下我认为存在的不足。 70 | 71 | **1.3.1 java给js发送消息方法和js给java发送消息的官方方法存在的不足** 72 | 73 | **1.3.1.1 强依赖** 74 | 75 | java给js发送消息的方法,和js给java发送消息的官方方法都存在着**强依赖**的问题,都要高度依赖对方的方法名字,方法参数。强依赖发生于同一模块内,个人觉得不是问题甚至是高内聚的体现。但是java与js可以说是处于两个不同的模块或者叫两个不同世界,只要js提供给java的方法发生变化,java也得改动,同理java提供给js的方法也如此。处于两个不同模块知道对方的细节越少越好,这样耦合性就会降低,耦合性降低的好处就不用说了。 76 | 77 | **1.3.1.2 强依赖导致js需要兼容不同的系统** 78 | 79 | 先看段伪代码: 80 | 81 | function location(){ 82 | //是ios系统,采用给ios发送消息的方法 83 | if(isIOS){ 84 | 给ios发送消息; 85 | }else if(isAndroid){ 86 | 给android发送消息; 87 | } 88 | } 89 | 上面的代码展示的是js使用native的定位功能的代码,因为js在给不同的系统发送消息的方式不一样,就会出现if else if 这样的兼容语句。当前js代码只被ios和android使用,假如还会被wp或pc来使用,那if else if岂不是要恶心死。产生该问题的主要原因是:js代码在针对不同的系统自己独有的通信方式进行通信。 90 | 91 | **1.3.1.3 给不存在的接口发送消息没反馈** 92 | 93 | java在给js的一个不存在接口发送消息时,java根本不知道该接口不存在,java只会傻傻的等待。同理js在给java的一个不存在接口发送消息时,js是可以通过捕获异常来知道该接口不存在,但是这不是最好的解决方案。 94 | 给不存在接口发送消息没反馈会导致js代码充斥着if else if语句,看段伪代码: 95 | 96 | //调用java的定位方法 97 | function location(){ 98 | //1.1版本以上才会调用定位功能 99 | if(androidAppVersion > '1.1'){ 100 | 发送消息给java; 101 | }else{ 102 | 给用户提示,暂不支持定位功能; 103 | } 104 | } 105 | 106 | 这是一段调用java进行定位的js代码,android app在版本1.1的时候才增加了定位的功能,因此对于1.1以下版本是不支持这功能的,因此js代码里面非常有必要根据版本号进行判断。这只是由于版本问题导致if else if的一个小小的缩影。还有一些其他情况导致if else if的产生比如一份js代码被多个业务使用。 107 | 108 | **1.3.2 js给java发送消息的第二种方法存在不足** 109 | 110 | 上文提到的js给java发送消息的第二种方法,它解决了存在的漏洞,但是这种方法,使用起来要比第一种方法复杂,java会多做以下工作: 111 | - 解析js传递给java的字符串,把调用的接口,参数解析出来 112 | - 把调用的接口,参数映射到相应的方法 113 | 114 | 不论js传递给java的字符串是json格式还是其他格式,解析这样的字符串肯定是一件无趣的重复的体力劳动。 115 | 116 | 117 | 若想解决以上的问题,我们有必要设计一套完美的通信方案。 118 | 119 | #2. js与java完美通信方案设计 120 | **2.1 一套完美的js与java的通信方案应满足以下几点:** 121 | 122 | - js与java知道对方的细节越少越好,越少它们的耦合性越低。那到底多少为好呢?我个人觉得互相暴漏给对方一个接口足矣。这样js与native的通信就类似于通过一个管道通信或者说类似于socket通信(降低强依赖) 123 | 124 | - js与java之间通信,需要定义好一套通信协议或者叫通信规则,在管道之间传递通信协议。这样它们之间的通信是针对一套定义好的协议进行的,而不是针对每个系统自己独有的通信方式(好处js就不会出现兼容不同的系统的if else if代码) 125 | 126 | - 主动发送消息给对方时,对方必须对该消息予以反馈,即使主动发送消息者对反馈消息不感兴趣,(反馈信息可以去掉由于版本兼容等带来的if else if兼容代码) 127 | 128 | 129 | **2.2 那我们就开始设计js与java之间的通信方案** 130 | 131 | **2.2.1 互相暴漏给对方一个接口** 132 | 133 | - js为java提供一个唯一的接口,这个接口可以是在java端写死的,也可以是js传递过来的(这样更灵活)。所有发送给js的消息(请求消息和反馈消息)都通过该接口 134 | - java为js提供的一个唯一的接口,因为官方的方法存在漏洞,我们采用在onJsPrompt方法中接收js发送的所有消息,当然大家还可以选择其他方法来接收js的消息,这不是重点。 135 | 136 | **2.2.2 js与java之间通信协议的制定** 137 | 138 | js与java之间的通信特别类似于网络请求,主动发起消息的行为可以称为request(请求消息),对该消息的反馈可以称为response(响应消息)。 139 | 140 | **request** 141 | 142 | 一个request封装了请求对方的哪个接口,以及该接口所需要的参数。 143 | 144 | **response** 145 | 146 | 一个response封装了状态信息(可以知道处理的结果是成功还是失败)和处理结果。 147 | 148 | **如何接收对方发送的response消息?** 149 | 150 | 大家都应该都会想到,在发送消息的时候传递一个回调接口就行了,但是因为js与java之间是跨语言的,尤其是java是不可能把回调接口传递给js,js虽然可以传递过来但是会有问题,所以这时候有一种解决办法: 151 | - 在给对方发送request消息时,为回调接口生成一个唯一的id值,把id值存入request中发出。 152 | - 同时把回调接口缓存起来。 153 | - 在接收到response时,从response解析这个id值,根据id值查找到回调接口。 154 | 155 | 因此request和response中还得包含回调id这个值。 156 | 157 | **通信协议的格式** 158 | 159 | request数据格式: 160 | 161 | { 162 | //接口名称 163 | "interfaceName":"test", 164 | //回调id值 165 | "callbackId":"c_111111", 166 | //传递的参数 167 | "params":{ 168 | .... 169 | } 170 | } 171 | 172 | response数据格式: 173 | 174 | { 175 | //回调id,同时这也是response的标志 176 | "responseId":"c_111111", 177 | //response数据 178 | "data":{ 179 | //状态数据 180 | "status":"1", 181 | "msg":"ok", 182 | //response的处理结果数据 183 | "values":{ 184 | ...... 185 | } 186 | } 187 | } 188 | 189 | 到此通信协议就已经定义好了。 190 | 191 | **2.2.3 让繁琐的无趣的重复的苦力活儿不再有** 192 | 193 | 大家可以看到通信协议request和response都是json格式,从json中解析数据或者把数据封装为json都是重复的苦力活儿。 194 | 这也是我一直想着力解决的痛点,解决之道是从retrofit中获得启发的,应用注解来解决以上问题。 195 | 196 | 关于js与java完美通信的设计思想到此为止,这也是SimpleJavaJsBridge这个库的核心思想,那我们就来看下SimpleJavaJsBridge。 197 | 198 | #3. SimpleJavaJsBridge 199 | SimpleJavaJsBridge我为什么要起一个这样的名字,首先它解决了上文中提到的**让繁琐的无趣的重复的苦力活儿不再有**的问题,对于不管是从json中解析数据还是把数据封装成json,使用者都不需要关心,让使用者很省心;并且它使用起来也非常的简单,在稍后的例子中大家会体会到,所以用了simple这个词儿。通过它java可以给js发送消息,并且接收js的响应消息;同时js也可以给java发送消息,同样接收java的响应消息。因此它是java与js之间通信的桥梁,因此它的名字叫为SimpleJavaJsBridge。 200 | 201 | **3.1 如何解决繁琐的无趣的重复的苦力活儿?** 202 | 203 | 解决这个问题思路来自于鼎鼎有名的Retrofit,Retrofit通过注解的方式解决了构建request和解析response的问题,因此注解也可以解决我现在遇到的问题。那我们就来认识下这些注解。 204 | **InvokeJSInterface**用来标注java给js发送消息的方法,它的value值代表js提供的功能的接口名字 205 | 206 | **JavaCallback4JS**用来标注java提供给js的回调方法的 207 | 208 | **JavaInterface4JS**用来标注java提供给js的接口,它的value值代表功能的接口名字 209 | 210 | **Param**用来标注参数或者类的实例属性,它的value值代表参数被存入json中的key值,它的needConvert代表当前的参数是否需要进行转换,因为通过JsonObject类往json中存放的数据是有要求的,JsonObject中只能存放基本数据和JsonObject和JsonArray这些数据类型,对于其他的类型就得进行转换了。因此只要是不能直接通过JsonObject存放的类型该值必须为true 211 | 212 | **ParamCallback**用来标注回调类型的参数,比如发送request给js的方法中,需要有一个回调参数,那这个参数必须用它来标注 213 | 214 | **ParamResponseStatus**用来标注响应状态类型的参数,比如:statusCode,StatusMsg这些参数,它的value值是json中的key值。 215 | 216 | 217 | **3.2 SimpleJavaJsBridge使用** 218 | 219 | **3.2.1 构建一个SimpleJavaJsBridge实例** 220 | 221 | SimpleJavaJsBridge instance = new SimpleJavaJsBridge.Builder() 222 | .addJavaInterface4JS(javaInterfaces4JS) 223 | .setWebView(webView) 224 | .setJSMethodName4Java("_JSBridge._handleMessageFromNative") 225 | .setProtocol("niu","receive_msg").create(); 226 | 227 | 通过SimpleJavaJsBridge.Builder来构建一个SimpleJavaJsBridge对象, 228 | - addJavaInterface4JS用来添加java提供给js的接口 229 | - setWebView 设置WebView这是必须进行设置的 230 | - setJSMethodName4Java 设置js为java唯一暴漏的方法名字 231 | - setProtocol设置协议字段,这也是必须的,这个字段主要是为了ios而设置的 232 | 233 | 当然还可以调用其他的一些方法对SimpleJavaJsBridge进行设置 234 | 235 | **3.2.2 js给java发送消息** 236 | 237 | **js给java的无参接口发送消息** 238 | 239 | /** * 给js发送响应消息的接口*/ 240 | public interface IResponseStatusCallback { 241 | void callbackResponse(@ParamResponseStatus("status") int status, @ParamResponseStatus("msg") String msg); 242 | } 243 | 244 | //java提供给js的"tes4"接口,@ParamCallback标注的是给js发送消息的回调 245 | @JavaInterface4JS("test4") 246 | public void test3(@ParamCallback IResponseStatusCallback jsCallback) { 247 | 进行相应处理...; 248 | //给js发送响应消息 249 | jsCallback.callbackResponse(1, "ok"); 250 | } 251 | 252 | //下面是js代码,js给java的"test4"接口发送消息 253 | _JSNativeBridge._doSendRequest("test4", {}, function(responseData){ 254 | 255 | }); 256 | 257 | **js给java的有参接口发送消息** 258 | 259 | /** * 给js发送响应消息的接口*/ 260 | public interface IResponseStatusCallback { 261 | void callbackResponse(@ParamResponseStatus("status") int status, @ParamResponseStatus("msg") String msg); 262 | } 263 | 264 | /** * 必须有无参构造函数 ,只有被@Param注解的属性才会存入json中*/ 265 | public static class Person { 266 | @Param("name") 267 | String name; 268 | 269 | @Param("age") 270 | public int age; 271 | 272 | public Person() { } 273 | 274 | public Person(String name, int age) { 275 | this.name = name; 276 | this.age = age; 277 | } 278 | } 279 | 280 | //java提供给js的“test1”接口,Person是无法直接往JsonObject中存放的, 281 | //所以needConvert必须为true,会自动把Person中用注解标注的属性放入json中 282 | @JavaInterface4JS("test1") 283 | public void test(@Param(needConvert = true) Person personInfo, @ParamCallback IResponseStatusCallback jsCallback) { 284 | 对收到的数据进行处理....; 285 | jsCallback.callback(1, "ok"); 286 | } 287 | 288 | //下面是js代码,js给java的"test1"接口发送消息 289 | _JSNativeBridge._doSendRequest("test1", {"name":"niu","age":10}, function(responseData){ 290 | 291 | }); 292 | 293 | **3.2.3 java给js发送消息** 294 | 295 | //给js发送消息的方法要定义在一个interface中,这个过程是模仿Retrofit的 296 | public interface IInvokeJS { 297 | 298 | //复杂类型,只有用@Param标注的属性才会放入json中 299 | public static class City{ 300 | @Param("cityName") 301 | public String cityName; 302 | 303 | @Param("cityProvince") 304 | public String cityProvince; 305 | 306 | public int cityId; 307 | } 308 | 309 | //给js的“exam”接口发送数据,参数是需要传递的数据 310 | @InvokeJSInterface("exam") 311 | void exam(@Param("test") String testContent, @Param("id") int id,@ParamCallback IJavaCallback2JS iJavaCallback2JS); 312 | 313 | //给js的“exam1”接口发送数据,参数同样也是需要传递的数据 314 | @InvokeJSInterface("exam1") 315 | void exam1(@Param(needConvert = true) City city, @ParamCallback IJavaCallback2JS iJavaCallback2JS); 316 | } 317 | 318 | 319 | //使用,使用方式和Retrofit一样,先使用SimpleJavaJsBridge的 320 | //createInvokJSCommand实例方法生成一个IInvokeJS实例 321 | IInvokeJS invokeJs = simpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 322 | 323 | //给js的"exam"发送消息,发送的是基本数据类型 324 | invokeJs.exam("hello js",20, new IJavaCallback2JS{ 325 | //接收js发送的响应数据的回调方法,该方法的名字可以任意,但必须用@JavaCallback4JS标注 326 | @JavaCallback4JS 327 | public void callback(@ParamResponseStatus("msg")String statusMsg,@Param("msg") String msg) { 328 | 329 | } 330 | }); 331 | 332 | City city = new City(); 333 | city.cityName = "长治"; 334 | city.cityId = 11; 335 | city.cityProvince = "山西"; 336 | //给js的“exam1”发送消息,city是一个复杂对象 337 | invokeJs.exam1(city, new IJavaCallback2JS{ 338 | @JavaCallback4JS 339 | public void callback(@ParamResponseStatus("msg")String statusMsg,@Param("msg") String msg) { 340 | 341 | } 342 | }); 343 | 344 | #总结 345 | SimpleJavaJsBridge库在js与java的通信中带来以下优点: 346 | - js代码中不再有由于系统或者app版本甚至业务原因产生的if else if的兼容语句 347 | - java不需要再关心数据封装为json或者从json中解析数据的繁琐工作 348 | - 让js与java之间的通信更简单 349 | 350 | 若你动心了可以下载试用:[SimpleJavaJsBridge](https://github.com/niuxiaowei/SimpleJavaJsBridge.git) 351 | 352 | 参考:大头鬼的[https://github.com/lzyzsd/JsBridge.git](https://github.com/lzyzsd/JsBridge.git) 353 | 354 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/f1a32a32753ccf0f697faf2a66651ffe729f1972/app/app-release.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.bridge" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | 15 | signingConfigs { 16 | config { 17 | keyAlias 'SimpleJavaJs' 18 | keyPassword 'simple' 19 | storeFile file('simple.jks') 20 | storePassword 'simple' 21 | } 22 | 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | signingConfig signingConfigs.config 29 | manifestPlaceholders = [APP_NAME: "SimpleJavaJs_release"] 30 | 31 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | } 35 | 36 | dependencies { 37 | compile fileTree(include: ['*.jar'], dir: 'libs') 38 | testCompile 'junit:junit:4.12' 39 | compile 'com.android.support:appcompat-v7:23.4.0' 40 | compile 'com.android.support:design:23.4.0' 41 | compile project(':simplejsjavabridgeLib') 42 | } 43 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/niuxiaowei/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/simple.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niuxiaowei/SimpleJavaJsBridge/f1a32a32753ccf0f697faf2a66651ffe729f1972/app/simple.jks -------------------------------------------------------------------------------- /app/src/androidTest/java/com/bridge/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.bridge; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/assets/bridge.js: -------------------------------------------------------------------------------- 1 | // Uses Node, AMD or browser globals to create a module. This example creates 2 | // a global even when AMD is used. This is useful if you have some scripts 3 | // that are loaded by an AMD loader, but they still want access to globals. 4 | // If you do not need to export a global for the AMD case, 5 | // see returnExports.js. 6 | 7 | // If you want something that will work in other stricter CommonJS environments, 8 | // or if you need to create a circular dependency, see commonJsStrictGlobal.js 9 | 10 | // Defines a module "returnExportsGlobal" that depends another module called 11 | // "b". Note that the name of the module is implied by the file name. It is 12 | // best if the file name and the exported global have matching names. 13 | 14 | // If the 'b' module also uses this type of boilerplate, then 15 | // in the browser, it will create a global .b that is used below. 16 | 17 | 18 | //document.write(''); 19 | 20 | 21 | 22 | 23 | (function (root, factory) { 24 | root.JsBridge = factory(root); 25 | }(typeof window == "undefined" ? this : window, function (win) { 26 | if (!win.document) { return {};} 27 | 28 | 29 | var doc = win.document, 30 | title = doc.title, 31 | ua = navigator.userAgent.toLowerCase(), 32 | platform = navigator.platform.toLowerCase(), 33 | isMacorWin = !(!platform.match("mac") && !platform.match("win")), 34 | isandroid = -1 != ua.indexOf("android"), 35 | isphoneorpad = -1 != ua.indexOf("iphone") || -1 != ua.indexOf("ipad"), 36 | JsBridge = { 37 | usable: false, 38 | init: function (bridge) { 39 | /*注册提供给native的接口*/ 40 | bridge.registerHandler("exam",function(message, responseCallback){ 41 | 42 | var result=document.getElementById("result"); 43 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 44 | responseCallback({ 45 | status: "1", 46 | msg: "ok", 47 | values:{ 48 | msg: "js回调native" 49 | } 50 | }); 51 | }); 52 | 53 | bridge.registerHandler("exam1",function(message, responseCallback){ 54 | 55 | var result=document.getElementById("result"); 56 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 57 | responseCallback({ 58 | status: "1", 59 | msg: "ok", 60 | values:{ 61 | cityName:message.cityName, 62 | cityProvince: message.cityProvince 63 | } 64 | }); 65 | }); 66 | 67 | bridge.registerHandler("exam2",function(message, responseCallback){ 68 | 69 | var result=document.getElementById("result"); 70 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 71 | responseCallback({ 72 | status: "1", 73 | msg: "ok", 74 | values:{ 75 | city:{ 76 | cityName:message.cityName, 77 | cityProvince: message.cityProvince 78 | } 79 | 80 | } 81 | }); 82 | }); 83 | 84 | bridge.registerHandler("exam3",function(message, responseCallback){ 85 | 86 | var result=document.getElementById("result"); 87 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 88 | responseCallback({ 89 | status: "1", 90 | msg: "ok", 91 | values:{ 92 | cityName:'北京', 93 | cityProvince: '北京' 94 | 95 | } 96 | }); 97 | }); 98 | 99 | bridge.registerHandler("exam4",function(message, responseCallback){ 100 | 101 | var result=document.getElementById("result"); 102 | result.innerHTML = 'native传递的数据:'+JSON.stringify(message); 103 | responseCallback({ 104 | status: "1", 105 | msg: "ok", 106 | values:{ 107 | 108 | } 109 | }); 110 | }); 111 | 112 | return this; 113 | }, 114 | checkUsable: function (methodName, params, cb) { 115 | var _this = this; 116 | if (!window._JSNativeBridge) { 117 | //JS not be injected success 118 | cb({ 119 | status: "-1", 120 | msg: "window._JSNativeBridge is undefined" 121 | }, {}); 122 | return; 123 | } 124 | 125 | try { 126 | window._JSNativeBridge._doSendRequest(methodName, params, cb); 127 | } catch (e) { 128 | cb({status: "-1", msg: e},{}); 129 | } 130 | }, 131 | 132 | test: function(params,cb){ 133 | this.checkUsable("test",{ 134 | "msg": "js发送的请求" 135 | },cb); 136 | }, 137 | 138 | test1: function(params,cb){ 139 | this.checkUsable("test1",{ 140 | "age": 10, 141 | "name":"wangwu" 142 | },cb); 143 | }, 144 | 145 | test2: function(params,cb){ 146 | this.checkUsable("test2",{ 147 | person:{ 148 | "age": 10, 149 | "name":"wangwu" 150 | } 151 | 152 | },cb); 153 | }, 154 | 155 | test3: function(params,cb){ 156 | this.checkUsable("test3",{ 157 | "jiguan":"北京", 158 | person:{ 159 | "age": 10, 160 | "name":"wangwu" 161 | } 162 | 163 | },cb); 164 | }, 165 | test4: function(params,cb){ 166 | this.checkUsable("test4",{ 167 | 168 | },cb); 169 | } 170 | 171 | 172 | }; 173 | 174 | if (window._JSNativeBridge) { 175 | console.log("native injection js success!"); 176 | window._JSNativeBridge.protocol.scheme = 'niu'; 177 | window._JSNativeBridge.protocol.host = 'receive_msg'; 178 | window._JSNativeBridge.debug = true; 179 | JsBridge.init(window._JSNativeBridge); 180 | } else { 181 | console.log("native injection js wrong!"); 182 | document.addEventListener( 183 | 'JsBridgeInit', 184 | function(event) { 185 | console.log('------------------bridge'); 186 | window._JSNativeBridge.protocol.scheme = 'niu'; 187 | window._JSNativeBridge.protocol.host = 'receive_msg'; 188 | event.bridge.debug = true; 189 | JsBridge.init(event.bridge); 190 | }, 191 | false 192 | ); 193 | } 194 | 195 | return JsBridge; 196 | })); 197 | -------------------------------------------------------------------------------- /app/src/main/assets/bridge_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | bridge demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /app/src/main/assets/connect_error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 链接失败 6 | 7 | 8 | 9 |
链接失败,请检查网络
10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/bridge/view/IInvokeJS.java: -------------------------------------------------------------------------------- 1 | package com.bridge.view; 2 | 3 | 4 | import com.simplejsjavabridge.lib.IJavaCallback2JS; 5 | import com.simplejsjavabridge.lib.annotation.InvokeJSInterface; 6 | import com.simplejsjavabridge.lib.annotation.Param; 7 | import com.simplejsjavabridge.lib.annotation.ParamCallback; 8 | 9 | /** 10 | * Created by niuxiaowei on 16/8/28. 11 | */ 12 | public interface IInvokeJS { 13 | 14 | 15 | public static class City{ 16 | @Param("cityName") 17 | public String cityName; 18 | 19 | @Param("cityProvince") 20 | public String cityProvince; 21 | 22 | public int cityId; 23 | 24 | 25 | } 26 | 27 | @InvokeJSInterface("exam") 28 | void exam(@Param("test") String testContent, @Param("id") int id,@ParamCallback IJavaCallback2JS iJavaCallback2JS); 29 | 30 | @InvokeJSInterface("exam1") 31 | void exam1(@Param City city, @ParamCallback IJavaCallback2JS iJavaCallback2JS); 32 | 33 | @InvokeJSInterface("exam2") 34 | void exam2(@Param City city, @Param("contry") String contry,@ParamCallback IJavaCallback2JS iJavaCallback2JS); 35 | 36 | @InvokeJSInterface("exam3") 37 | void exam3(@Param(value = "city") City city, @Param("contry") String contry,@ParamCallback IJavaCallback2JS iJavaCallback2JS); 38 | 39 | @InvokeJSInterface("exam4") 40 | void exam4(@ParamCallback IJavaCallback2JS iJavaCallback2JS); 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/bridge/view/JavaInterfaces4JS.java: -------------------------------------------------------------------------------- 1 | package com.bridge.view; 2 | 3 | 4 | import com.bridge.view.fragment.WebViewFragment; 5 | import com.simplejsjavabridge.lib.annotation.JavaInterface4JS; 6 | import com.simplejsjavabridge.lib.annotation.Param; 7 | import com.simplejsjavabridge.lib.annotation.ParamCallback; 8 | import com.simplejsjavabridge.lib.annotation.ParamResponseStatus; 9 | 10 | 11 | /** 12 | * java提供给js的接口 13 | * Created by niuxiaowei on 16/7/22. 14 | */ 15 | public class JavaInterfaces4JS { 16 | 17 | 18 | private WebViewFragment mWebViewFragment; 19 | 20 | public JavaInterfaces4JS(WebViewFragment webViewFragment) { 21 | mWebViewFragment = webViewFragment; 22 | } 23 | 24 | 25 | /** 26 | * 必须有无参构造函数 27 | */ 28 | public static class Person { 29 | @Param("name") 30 | String name; 31 | @Param("age") 32 | public int age; 33 | 34 | public Person() { 35 | } 36 | 37 | public Person(String name, int age) { 38 | this.name = name; 39 | this.age = age; 40 | } 41 | 42 | 43 | } 44 | 45 | /** 46 | * 发送响应状态的接口 47 | */ 48 | public interface IResponseStatusCallback { 49 | void callbackResponse(@ParamResponseStatus ResponseStatus responseStatus); 50 | } 51 | 52 | public interface ITestJSCallback extends IResponseStatusCallback { 53 | void callback(@ParamResponseStatus ResponseStatus responseStatus, @Param("content") String content); 54 | } 55 | 56 | public interface ITest1JSCallback extends IResponseStatusCallback { 57 | void callback(@ParamResponseStatus ResponseStatus responseStatus, @Param Person person); 58 | } 59 | 60 | @JavaInterface4JS("test") 61 | public void test(@Param("msg") String msg, @ParamCallback ITestJSCallback jsCallback) { 62 | mWebViewFragment.setResult("js传递数据: " + msg); 63 | jsCallback.callbackResponse(ResponseStatus.FAILED); 64 | } 65 | 66 | 67 | 68 | @JavaInterface4JS("test1") 69 | public void test(@Param Person personInfo, @ParamCallback ITest1JSCallback jsCallback) { 70 | 71 | if (personInfo != null) { 72 | mWebViewFragment.setResult("native的test1接口被调用,js传递数据: " + "name=" + personInfo.name + " age=" + personInfo.age); 73 | 74 | } 75 | jsCallback.callback(ResponseStatus.OK, new Person("niuxiaowei", 30)); 76 | } 77 | 78 | 79 | @JavaInterface4JS("test2") 80 | public void test2(@Param(value = "person") Person personInfo, @ParamCallback ITest1JSCallback jsCallback) { 81 | 82 | if (personInfo != null) { 83 | mWebViewFragment.setResult("native的test2接口被调用,js传递数据: " + "name=" + personInfo.name + " age=" + personInfo.age); 84 | 85 | } 86 | jsCallback.callback(ResponseStatus.OK, new Person("niuxiaowei", 30)); 87 | } 88 | 89 | @JavaInterface4JS("test3") 90 | public void test3(@Param("jiguan") String jiguan, @Param(value = "person") Person personInfo, @ParamCallback ITest1JSCallback jsCallback) { 91 | 92 | if (personInfo != null) { 93 | mWebViewFragment.setResult("native的test3接口被调用,js传递的数据: " + "jiguan=" + jiguan + " name=" + personInfo.name + " age=" + personInfo.age); 94 | 95 | } 96 | jsCallback.callback(ResponseStatus.OK, new Person("niuxiaowei", 30)); 97 | } 98 | 99 | @JavaInterface4JS("test4") 100 | public void test3(@ParamCallback IResponseStatusCallback jsCallback) { 101 | 102 | mWebViewFragment.setResult("native的test4无参接口被调用"); 103 | 104 | jsCallback.callbackResponse(ResponseStatus.OK); 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/bridge/view/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.bridge.view; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.app.FragmentManager; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.View; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | 14 | import com.bridge.R; 15 | import com.bridge.view.fragment.WebViewFragment; 16 | 17 | public class MainActivity extends AppCompatActivity { 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 24 | setSupportActionBar(toolbar); 25 | 26 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 27 | fab.setVisibility(View.GONE); 28 | 29 | initView(); 30 | } 31 | 32 | public void initView() { 33 | String url = "file:///android_asset/bridge_demo.html"; 34 | FragmentManager fm = getSupportFragmentManager(); 35 | Fragment fragment = fm.findFragmentById(R.id.webview_layout); 36 | if (fragment == null || !(fragment instanceof WebViewFragment)) { 37 | fragment = (WebViewFragment.createWebViewFragment(url)); 38 | } 39 | fm.beginTransaction().replace(R.id.webview_layout, fragment).commit(); 40 | 41 | 42 | 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/bridge/view/ResponseStatus.java: -------------------------------------------------------------------------------- 1 | package com.bridge.view; 2 | 3 | /** 4 | * java发送给js的响应状态 5 | * 6 | * Created by niuxiaowei on 16/10/21. 7 | */ 8 | public enum ResponseStatus { 9 | 10 | OK(1,"ok"), FAILED(0,"failed"), FAILED_METHOD_NOT_DEFINED(-1,"method not defined"), FAILED_PARAM_ERROR(-2,"param error") 11 | , FAILED_TIME_OUT(-3,"time out"), FAILED_USER_CANCEL(-4,"user cancel"); 12 | 13 | 14 | private int status; 15 | 16 | private String msg; 17 | 18 | ResponseStatus(int status, String msg) { 19 | this.status = status; 20 | this.msg = msg; 21 | } 22 | 23 | public int getStatus() { 24 | return status; 25 | } 26 | 27 | public String getMsg() { 28 | return msg; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/bridge/view/fragment/WebViewFragment.java: -------------------------------------------------------------------------------- 1 | package com.bridge.view.fragment; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.text.TextUtils; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.webkit.WebResourceRequest; 12 | import android.webkit.WebResourceResponse; 13 | import android.webkit.WebSettings; 14 | import android.webkit.WebView; 15 | import android.webkit.WebViewClient; 16 | import android.widget.TextView; 17 | 18 | 19 | import com.bridge.R; 20 | 21 | import com.bridge.view.JavaInterfaces4JS; 22 | import com.bridge.view.IInvokeJS; 23 | import com.simplejsjavabridge.lib.IJavaCallback2JS; 24 | import com.simplejsjavabridge.lib.SimpleJavaJsBridge; 25 | import com.simplejsjavabridge.lib.annotation.JavaCallback4JS; 26 | import com.simplejsjavabridge.lib.annotation.Param; 27 | import com.simplejsjavabridge.lib.annotation.ParamResponseStatus; 28 | 29 | 30 | /** 31 | * Created by niuxiaowei on 16/5/25. 32 | */ 33 | public class WebViewFragment extends Fragment { 34 | 35 | public static final String WEBVIEW_URL = "webview_url"; 36 | 37 | private WebView mWebView; 38 | private String mUrl; 39 | private TextView mResultView; 40 | 41 | private SimpleJavaJsBridge mSimpleJavaJsBridge; 42 | 43 | 44 | @Nullable 45 | @Override 46 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 47 | View view = inflater.inflate(R.layout.fragment_webview, container, false); 48 | return view; 49 | } 50 | 51 | 52 | @Override 53 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 54 | parseArguments(); 55 | initView(); 56 | initData(); 57 | super.onViewCreated(view, savedInstanceState); 58 | } 59 | 60 | public static Bundle createBundle(String url) { 61 | Bundle args = new Bundle(); 62 | args.putString(WEBVIEW_URL, url); 63 | 64 | return args; 65 | } 66 | 67 | public static WebViewFragment createWebViewFragment(Bundle args) { 68 | WebViewFragment instance = new WebViewFragment(); 69 | instance.setArguments(args); 70 | return instance; 71 | } 72 | 73 | public static WebViewFragment createWebViewFragment(String url) { 74 | return createWebViewFragment(createBundle(url)); 75 | } 76 | 77 | protected void parseArguments() { 78 | Bundle args = getArguments(); 79 | if (args == null) { 80 | return; 81 | } 82 | mUrl = args.getString(WEBVIEW_URL); 83 | 84 | } 85 | 86 | public void initData() { 87 | JavaInterfaces4JS javaInterfaces4JS = new JavaInterfaces4JS(this); 88 | mSimpleJavaJsBridge = new SimpleJavaJsBridge.Builder().addJavaInterface4JS(javaInterfaces4JS) 89 | .setWebView(mWebView) 90 | .setJSMethodName4Java("_JSNativeBridge._handleMessageFromNative") 91 | .setProtocol("niu","receive_msg").create(); 92 | } 93 | 94 | private void showLoading(){ 95 | mResultView.setText("正在调用js的接口......"); 96 | } 97 | 98 | public void setResult(String result){ 99 | if(result != null){ 100 | mResultView.setText(result); 101 | } 102 | } 103 | 104 | public void initView() { 105 | initWebView(); 106 | 107 | mResultView = (TextView)getView().findViewById(R.id.result); 108 | getView().findViewById(R.id.invoke_js_exam).setOnClickListener(new View.OnClickListener() { 109 | @Override 110 | public void onClick(View v) { 111 | showLoading(); 112 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 113 | invokeJS.exam("你好啊js", 7,new IJavaCallback2JS() { 114 | 115 | @JavaCallback4JS 116 | public void test(@ParamResponseStatus("msg")String statusMsg, @Param("msg") String msg) { 117 | mResultView.setText(" 状态信息="+statusMsg+" msg="+msg); 118 | } 119 | 120 | }); 121 | } 122 | }); 123 | 124 | getView().findViewById(R.id.invoke_js_exam1).setOnClickListener(new View.OnClickListener() { 125 | @Override 126 | public void onClick(View v) { 127 | showLoading(); 128 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 129 | IInvokeJS.City city = new IInvokeJS.City(); 130 | city.cityId = 10; 131 | city.cityName = "长治"; 132 | city.cityProvince="山西"; 133 | invokeJS.exam1(city, new IJavaCallback2JS() { 134 | 135 | @JavaCallback4JS 136 | public void test(@Param IInvokeJS.City city1) { 137 | mResultView.setText("js返回信息: cityName="+city1.cityName+" cityProvince="+city1.cityProvince); 138 | } 139 | 140 | }); 141 | } 142 | }); 143 | getView().findViewById(R.id.invoke_js_exam2).setOnClickListener(new View.OnClickListener() { 144 | @Override 145 | public void onClick(View v) { 146 | showLoading(); 147 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 148 | IInvokeJS.City city = new IInvokeJS.City(); 149 | city.cityId = 10; 150 | city.cityName = "长治"; 151 | city.cityProvince="山西"; 152 | invokeJS.exam2(city,"中国", new IJavaCallback2JS() { 153 | 154 | @JavaCallback4JS 155 | public void test(@Param(value = "city") IInvokeJS.City city1) { 156 | mResultView.setText("js返回信息: cityName="+city1.cityName+" cityProvince="+city1.cityProvince); 157 | } 158 | 159 | }); 160 | } 161 | }); 162 | getView().findViewById(R.id.invoke_js_exam3).setOnClickListener(new View.OnClickListener() { 163 | @Override 164 | public void onClick(View v) { 165 | showLoading(); 166 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 167 | IInvokeJS.City city = new IInvokeJS.City(); 168 | city.cityId = 10; 169 | city.cityName = "长治"; 170 | city.cityProvince="山西"; 171 | invokeJS.exam3(city,"中国", new IJavaCallback2JS() { 172 | 173 | @JavaCallback4JS 174 | public void test(@Param IInvokeJS.City city1) { 175 | mResultView.setText("js返回信息: cityName="+city1.cityName+" cityProvince="+city1.cityProvince); 176 | } 177 | 178 | }); 179 | } 180 | }); 181 | 182 | getView().findViewById(R.id.invoke_js_exam4).setOnClickListener(new View.OnClickListener() { 183 | @Override 184 | public void onClick(View v) { 185 | showLoading(); 186 | IInvokeJS invokeJS = mSimpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); 187 | 188 | invokeJS.exam4( new IJavaCallback2JS() { 189 | 190 | @JavaCallback4JS 191 | public void test(@ParamResponseStatus("status") String status, @ParamResponseStatus("msg") String statusMsg) { 192 | mResultView.setText("js返回信息: status="+status+" statusMsg="+statusMsg); 193 | } 194 | 195 | }); 196 | } 197 | }); 198 | 199 | 200 | } 201 | 202 | private void initWebView() { 203 | mWebView = (WebView) getView().findViewById(R.id.webView); 204 | 205 | mWebView.setWebViewClient(new WebViewClient() { 206 | 207 | 208 | @Override 209 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 210 | // mWebViewCallBack.shouldOverrideUrlLoading(view, url); 211 | return super.shouldOverrideUrlLoading(view, url); 212 | } 213 | 214 | @Override 215 | public void onReceivedError(WebView view, int errorCode, 216 | String description, String failingUrl) { 217 | super.onReceivedError(view, errorCode, description, failingUrl); 218 | 219 | } 220 | 221 | 222 | @Override 223 | public void onPageStarted(WebView view, String url, Bitmap favicon) { 224 | super.onPageStarted(view, url, favicon); 225 | // mWebViewCallBack.onPageStarted(view, url, favicon); 226 | 227 | } 228 | 229 | @Override 230 | public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 231 | return super.shouldInterceptRequest(view, request); 232 | } 233 | }); 234 | 235 | 236 | WebSettings settings = mWebView.getSettings(); 237 | settings.setJavaScriptEnabled(true); 238 | settings.setPluginState(WebSettings.PluginState.ON); 239 | settings.setBuiltInZoomControls(true); 240 | settings.setBlockNetworkImage(false); 241 | settings.setUseWideViewPort(true); 242 | settings.setLoadWithOverviewMode(true); 243 | settings.setDefaultTextEncodingName("UTF-8"); 244 | settings.setLoadsImagesAutomatically(true); 245 | 246 | // settings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); 247 | 248 | /* 解决空白页问题 */ 249 | settings.setDomStorageEnabled(true); 250 | settings.setCacheMode(WebSettings.LOAD_DEFAULT); 251 | settings.setJavaScriptCanOpenWindowsAutomatically(false); 252 | 253 | 254 | if (!TextUtils.isEmpty(mUrl)) { 255 | mWebView.loadUrl(mUrl); 256 | } 257 | } 258 | 259 | 260 | } 261 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_webview.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 14 | 15 | 16 | 20 | 25 | 26 |