Android 混合开发 的一些心得。

其实所谓这个混合开发,也就是hybird,就是一些简单的,html5和native 代码之间的交互。很多电商之类的app里面都有类似的功能,

这种东西其实还是蛮重要的,主要就是你有什么功能都可以进行热部署,不需要再重新发版本。下面就简单介绍一下这种技术。

我们首先看下面一个场景,我们打开网易云音乐的app 里面的积分商城,(此时实际上是一个webview去加载了一个html界面。)

然后在显示出来的界面里面点击一下我的订单,因为我们没有登录过,所以此时自动给我弹出了native的登录界面。你看这就是一个

典型的html和native 进行交互的一个场景。为了让大家感受的更深一些,可以看一下下面的gif 操作过程:

经过简单的抓包,我们可以知道 这个webview访问的地址是:http://music.163.com/store/m/product/index

我们在chrome浏览器里 直接打开这个链接 然后也点击我的订单 你会发现:

所以我么继续查看网页源代码,并且对js进行解压缩以后就会发现下面的代码了:

 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了。

所以我们可以继续才想到,网易云音乐的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。所以一定程度上可以避免大部分的攻击。

时间: 2024-08-12 12:37:15

Android 混合开发 的一些心得。的相关文章

Android混合开发,html5自己主动更新爬过的坑

如今使用混合开发的公司越来越多,尽管出现了一些新技术,比方Facebook的react native.阿里的weex,但依旧阻挡不了一些公司採用h5的决心.当然,这也是从多方面考虑的选择. 在三年前就使用过html5混合开发,当时做的是一款贵金属软件,涨跌五线谱.乾坤交易,还有各个股市的信息,那时候还是上波牛市爆发的前夕,哎... 近期公司让用h5混合开发.一些页面和功能有h5分担,最初时候放在本地assets目录下,后来因为前端同事频繁改动和更新.再加上数据安全方面考虑,决定把包放在serve

android混合开发,webview的java与js互操作

android原生应用,用webview加载应用中的网页,并且java代码与js代码可以互相操作. 这是混合开发的基石,最基本也最重要的东西,实验代码在这里. 概括说说—— java调js:调用webView.load("javascript:someFunction()"); 这样可以调用webView里页面上的全局方法.这不是什么新鲜东西,你在网页中也可以这么做,试试在浏览器地址栏输入javascript:alert("427studio");也可以在浏览器地址

Android 混合开发,html5 自动更新爬过的坑

现在使用混合开发的公司越来越多,虽然出现了一些新技术,比如Facebook的react native.阿里的weex,但依然阻挡不了一些公司采用h5的决心,当然,这也是从多方面考虑的选择. 在三年前就使用过html5混合开发,当时做的是一款贵金属软件,涨跌五线谱.乾坤交易,还有各个股市的信息,那时候还是上波牛市爆发的前夕,哎... 最近公司让用h5混合开发,一些页面和功能有h5分担,最初时候放在本地assets文件夹下,后来由于前端同事频繁修改和更新,再加上数据安全方面考虑,决定把包放在服务器,

不得不看的Flutter与Android混合开发

记得在flutter刚出来时,笔者就开始学习flutter.但由于当时嫌弃flutter复杂的层级组合且未推出稳定版,所以当时就放弃了深入学习,现如今随着flutter的蓬勃发展及大佬们的力推,就又入坑flutter.虽说flutter能够跨平台,但由于现在几乎都是现成的项目,所以不可能用flutter来重头开发,所以目前几乎都是采用native+flutter的混合开发方案.那么该方案该如何实现尼?1.flutter模块的导入首先,切换到native项目的根目录的上一级目录.以笔者项目为例,路

Flutter + Android 混合开发

JIT (Just In Time) 即时编译器, 边执行边编译 程序运行时,JIT 编译器选择将最频繁执行的方法编译成本地代码.运行时才进行本地代码编译而不是在程序运行前进行编译 AOT可以理解为“全时段的编译”(All-Of-the-Time compilation).即在安装的时候,就把所有的运行代码都编译到本地,这样在运行时就可以直接执行机器代码 Android N引入了一种包含编译.解释和JIT(Just In Time)的混合运行时,以便在安装时间.内存占用.电池消耗和性能之间获得最

android驱动开发第十章心得笔记

第十章主要讲了对于复杂的Linux驱动以及HAL等程序库,需要使用各种方法对其进行调试.例如,设置断点.逐步跟踪代码.输出调试信息等. Printk函数的用法与printf函数类似,只不过printk函数运行在内核空间,printf函数运行在用户空间.也就是说,像Linux驱动这样的Linux内核程序只能使用printk函数输出调试信息.Printk函数在printk.c文件中实现. 虽然使用printk函数可以很方便的将消息写入日志文件或控制台.但大量使用printk函数频繁操作日志文件或控制

android驱动开发第九章心得笔记

---恢复内容开始--- 第九章主要讲硬件抽象层:HAL,它是建立在Linux驱动之上的一套程序库.刚开始介绍了为什么要在Android中加入HAL,目的有三个,一,统一硬件的调用接口.二,解决了GPL版权问题.三,针对一些特殊的要求.可以利用位于用户空间的HAL代码来辅助Linux驱动完成一些工作. 下一个小节讲了Android HAL 架构.接下来讲了为led驱动增加HAL,主要是将所有的业务逻辑从LED驱动移到HAL模块,而LED驱动只保留读写寄存器的功能. 涉及的HAL步骤:1.编写Li

android驱动开发第八章心得笔记

第八章介绍了第二个实验:蜂鸣器驱动,使开发板发出声音 将介绍蜂鸣器的实现原理,并实现一个完整的蜂呜器驱动,通过该驱动可以控制蜂鸣器的打开与关闭. 蜂鸣器也称为PWM脉冲宽度调制,基本原理就是通过脉冲来控制蜂鸣器的打开和停止.蜂鸣 器是开发板上带的一个硬件设备,可以通过向寄存器写入特定的值来控制蜂鸣器发出的声音.本节介绍了蜂鸣器的实现原理,并实现一个完整的蜂鸣器驱动,可以打 开和关闭.PWM驱动的实现方式不同于LED驱动,PWM驱动由多个文件组成,这也是大多数Linux驱动的标准实现方式. Lin

8-4 Flutter Android混合开发实战-调试与发布

在flutter的目录下运行命令 第二步,运行原生,点击按钮后,控制台的输出. 同步代码到安卓设备上已经完成.热加载小r 热重启的是大R.请求帮助按h .退出按q 这个时候原生的效果就出来了 原生这里就看到了效果 调试Dart代码 原生运行的app打开.点击加载flutter模块. 加载完成后就出现了调试面板 也可以加断点调试 点击按钮会触发代码 点击后 就在断点这里暂停了. 只要让原生项目和flutter项目监理debug的链接.调试和原有flutter的调试是一样的 发布应用 首先是签名,