其实所谓这个混合开发,也就是hybird,就是一些简单的,html5和native 代码之间的交互。不少电商之类的app里面都有相似的功能,javascript
这种东西其实仍是蛮重要的,主要就是你有什么功能均可以进行热部署,不须要再从新发版本。下面就简单介绍一下这种技术。html
咱们首先看下面一个场景,咱们打开网易云音乐的app 里面的积分商城,(此时其实是一个webview去加载了一个html界面。)html5
而后在显示出来的界面里面点击一下个人订单,由于咱们没有登陆过,因此此时自动给我弹出了native的登陆界面。你看这就是一个java
典型的html和native 进行交互的一个场景。为了让你们感觉的更深一些,能够看一下下面的gif 操做过程:android
通过简单的抓包,咱们能够知道 这个webview访问的地址是:http://music.163.com/store/m/product/indexgit
咱们在chrome浏览器里 直接打开这个连接 而后也点击个人订单 你会发现:github
因此我么继续查看网页源代码,而且对js进行解压缩之后就会发现下面的代码了:web
1 Js.fg = function(Jt) { 2 var Jv = JC.cr(Jt, "d:action"); 3 switch (Jq.bv(Jv, "action")) { 4 case "gopage": 5 if (!this.fv.userId || this.fv.userId <= 0) { 6 location.href = "orpheus://welfare/login"; 7 return 8 } else { 9 location.href = Jq.bv(Jv, "destination") 10 } 11 break 12 } 13 };
到这应该能够理解了,就是点击了个人订单之后 js的功能把超连接定位成orpheus://welfare/login了。chrome
因此咱们能够继续才想到,网易云音乐的app 就是在这个webview里面 捕捉到了这个超连接的信息之后 而后跳转到浏览器
本身定义的activity!这就是这个功能的实现原理。
那么咱们就依葫芦画瓢来试着仿照一下 可否实现这个功能。咱们主要是在webview 上写一些代码:
1 wb=(WebView)findViewById(R.id.wb); 2 wb.getSettings().setJavaScriptEnabled(true); 3 wb.setWebViewClient(new WebViewClient() { 4 @Override 5 public boolean shouldOverrideUrlLoading(WebView view, String url) { 6 7 if (url.contains("orpheus://welfare/login")) { 8 Intent intent=new Intent(); 9 intent.setClass(TestNetWebViewActivity.this,LoginActivity.class); 10 startActivity(intent); 11 return true; 12 } 13 return super.shouldOverrideUrlLoading(view, url); 14 } 15 }); 16 wb.loadUrl(URL);
而后看一下 是否能像网易云音乐那样实现咱们想要的功能:
看下实际运行的gif:
这个方案能够看到是彻底可行的。可是这个方案 依旧是有缺陷的,你只能适用于这种简单的状况,
并且他的原理实际上就是利用webview 从新访问一个新url的时候 对新的url 进行分析 而后
决定本身下一步该作什么,也就是说这个js---java代码的调用过程彻底依托于对url的字符串的分析。
所谓再复杂一些的场景这个方案就hold不住了!因此咱们须要一个新的方案。能让js 方便愉快的
传值到咱们的java代码里面!
咱们首先在assets这个android路径下面 放一个咱们本身写的html代码:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>JavaScript View</title> 5 6 <script type="text/javascript"> 7 8 function showToast(){ 9 var message = document.getElementById("message").value; 10 var lengthLong = document.getElementById("length").checked; 11 12 /* 13 调用java里的makeToast方法,注意这里的app 就和addJavascriptInterface这个函数里的 14 第二个参数值要保持一致,且大小写敏感 15 */ 16 app.makeToast(message, lengthLong); 17 return false; 18 } 19 20 /* 21 这个很好理解,就是当你这个html加载完成的时候 把表单的submit提交定位到js的 showToast方法里面 22 就理解成方法的重定向便可 23 */ 24 window.onload = function(){ 25 var form = document.getElementById("form"); 26 form.onsubmit = showToast; 27 } 28 </script> 29 </head> 30 31 <body> 32 33 <form id="form"> 34 Message: <input id="message" name="message" type="text"/><br /> 35 Long: <input id="length" name="length" type="checkbox" /><br /> 36 37 <input type="submit" value="Make Toast" /> 38 </form> 39 40 </body> 41 </html>
而后把咱们的java 代码稍做修改:
1 wb = (WebView) findViewById(R.id.wb); 2 wb.getSettings().setJavaScriptEnabled(true); 3 wb.addJavascriptInterface(new WebViewJavaScriptInterface(this), "app"); 4 wb.loadUrl("file:///android_asset/web.html"); 5 class WebViewJavaScriptInterface { 6 private Context context; 7 8 public WebViewJavaScriptInterface(Context context) { 9 this.context = context; 10 } 11 12 @JavascriptInterface 13 public void makeToast(String message, boolean lengthLong) { 14 Toast.makeText(context, message, (lengthLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT)).show(); 15 } 16 17 }
而后看一下跑起来的效果:
能够看出来咱们从js这边完美调用java代码的 方案就成功了。
可是实际上呢,这个addJavascriptInterface 方法在4.2 如下呢,是有一个很严重的安全漏洞的,
咱们上面的代码 你看到了 我是有一个注解在哪里的,可是若是你的手机是4.2如下的系统,这种系统
是不会检测你那个方法是否有注解的,因此原则上来讲 对于4.2如下的系统来讲,这个方法能够调用
任何你手机里的任何方法(固然是经过反射)。有兴趣的同窗能够看一下这个连接:
http://jaq.alibaba.com/blog.htm?id=48
因此除非你作的app 不支持4.2如下的系统,不然咱们认为 这个方案也是有缺陷的。
并且这个方法 还有一个不方便的地方在于,你js是能够调用java了能够调用native代码了,
可是你js调用完java代码之后 没法回调了。我若是想js调用完java代码之后立刻进行回调js代码的操做
就没法作到了。有些人可能不明白 回调js 代码没法起做用是什么意思,能够接着看下面的例子。
首先我定义一个按钮,这个按钮就干一件事 就是经过java代码去调用js代码:
1 bt.setOnClickListener(new View.OnClickListener() { 2 3 @Override 4 public void onClick(View v) { 5 wb.loadUrl("javascript:display_alert()"); 6 } 7 });
而后在咱们js调用java native函数里面 也写一个这样相似的代码:
1 @JavascriptInterface 2 public void makeToast(String message, boolean lengthLong) { 3 Toast.makeText(context, message, (lengthLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT)).show(); 4 wb.loadUrl("javascript:display_alert()"); 5 6 }
下面看下运行效果:
因此你看 直接在按钮那边经过java来调用js是能够的,可是你要是经过js调用java 再在java的代码里回调js代码
那就彻底无效了。
因此咱们下面要解决的问题 主要就是2块:
第一:让js可以安全的调用java代码,主要是对于4.2版本如下的手机来讲
第二:让js调用java之后 依旧能够回调js,这是对于全部手机来讲的。
关于这种状况的解决方案,我也找了好久,调研了好久。基本上都是经过
WebChromeClient.onJsPrompt 来完成对应的功能。
而且流程就是以下几步:
1.咱们假设你js要调用的java native代码 是a 这个类的 a1 a2 a3 3个方法。
2.利用反射机制 把a1 a2 a3 这3个方法 给保存成字符串,存在一个str里面
3.找机会把这个还有对象方法信息的str 转成咱们须要的js代码 而后将这个js 代码注入到webview 要加载的html源码里面!
4.这样js就只能执行 注入后的修改过的html代码里的 ”js代码了“ 也就是说 你没法利用js 调用任何方法,只能经过前面3步 注入的js代码 来调用对应的native方法
原理上隔绝了 前面说的4.2如下的 漏洞。
5.js代码成功注入之后 ,就会经过onpromt方法 来完成jscalljava的这个过程。包括要执行的方法名字,参数类型啥之类的都会检查一遍。再次杜绝了4.2如下的那个漏洞,
而且从原理上 能够在java中任意时间 场景回调咱们的js代码!
那目前来看 基本上全部的hybrid开发 都是上面这个流程,并且要兼容4.2如下的sdk的时候 基本上我反编译了不少app 都是利用的http://www.pedant.cn/2014/07/04/webview-js-java-interface-research/
这篇文章提到的https://github.com/pedant/safe-java-js-webview-bridge 这个开源库。
可是,实际上这个开源库 并不完美,有一点点小缺陷,并且一直没有获得很好的解决,(因此不少人转载文章或者写blog的时候很不负责任,第一我的怎么写他本身就怎么抄 也不验证。)这其中就是由于有一段代码:
1 public void onProgressChanged(WebView view, int newProgress) { 2 //为何要在这里注入JS 3 //1 OnPageStarted中注入有可能全局注入不成功,致使页面脚本上全部接口任什么时候候都不可用 4 //2 OnPageFinished中注入,虽然最后都会全局注入成功,可是完成时间有可能太晚,当页面在初始化调用接口函数时会等待时间过长 5 //3 在进度变化时注入,恰好能够在上面两个问题中获得一个折中处理 6 //为何是进度大于25%才进行注入,由于从测试看来只有进度大于这个数字页面才真正获得框架刷新加载,保证100%注入成功 7 if (newProgress <= 25) { 8 mIsInjectedJS = false; 9 } else if (!mIsInjectedJS) { 10 view.loadUrl(mJsCallJava.getPreloadInterfaceJS()); 11 mIsInjectedJS = true; 12 StopWatch.log(" inject js interface completely on progress " + newProgress); 13 } 14 super.onProgressChanged(view, newProgress); 15 }
你能够看一下 这个注入的时机问题。第七行,这个地方是有问题的,由于你们都知道实际上你webview的性能一直以来都不是太好,还有不少机能不好 或者rom 优化不好的 webview
根本就是一团坑,因此这个里面 相似于 硬编码的 这个注入过程 是不太完美的。在少部分机型 以及少部分场景中,这里会一直注入失败的。致使整个框架都不可用。
因此有代码洁癖的同窗要注意了,这个网上流传最广的开源方案 目前是有缺陷的。要慎用~不过这种开源方案 能cover住百分之95以上的手机 我以为也还行了。
因此目前来看,并无一个特别有效并且安全完美的方案来规避这个问题。有人说微信hybrid 作的不错,实际上微信我看过他的js sdk。实际上啊,微信并非用的咱们所说的prompt方法
他仍是和网易那个同样 经过拦截url 分析url 来执行相应的操做的。native 回调js代码也是走的js里的_handleMessageFromWeixin 这份方法。有兴趣的同窗能够去看下微信的作法。
但你其实想想 微信这个方法也是有缺陷的,由于url是能够伪造的,好在微信本身会在native代码里 验证他的appid。因此必定程度上能够避免大部分的攻击。