最近老板组织了一下技术分享,我分享了两点东西,最后老板的点评还是挺有意思的,多点图,尽量深入浅出,吃透了才能深入浅出,说得云里雾里的,多半是自己还没理解到位。我整理成博客吧。
什么是Hybrid app?
(图片来自https://zhuanlan.zhihu.com/p/21387961 :侵立删)
(摘自百度百科:Hybrid App同时使用网页语言与程序语言开发,通过应用商店区分移动操作系统分发,用户需要安装使用的移动应用)
为什么要用Hybrid app?
牺牲一定的交互体验,交换开发的时间成本(我们之前用的一直是android原生的东西,后来用户需求很强烈,加了IOS(swift)的东西,因为我要写两个平台,很麻烦。所以加入了web的东西来跨平台。归根结底还是移动端开发。)
现在我们是怎么做的?
+ HTML5 (ASP.NET MVC4)
+ webkit ( android:webview , IOS :UIWebview[IOS7] , WKWebview[IOS8+])
回到题目,如何优化原生支持?
有几个点:
1、导航栏要不要做成web的?
问题图中的navigation是原生还是web的。很不幸这么基础的问题我们也遇到过,也实践过。答案是做成原生的比较好。因为web的loading可能会比较久,如果挂了,就很糟糕了。我们做完一段时间后,市面上的hybrid app才多了起来,基本上也是用这种方式。
2、WEB上面有几个页面,如何加载,如何返回?
用android举例,以前我们的页面的概念是基于Activity,每跳转一个页面,就新开一个Activity,好处当然是能利用android框架的东西,保存页面信息。那在WEB上有没有这个概念?一开始我也是直接用webview的堆栈信息,通过拦截“返回”按钮,调用webview里面的返回,直到webview中history堆栈的为空,则退出。
后来遇到几个问题:
i、(IOS、android)列表进去,返回上一页,页面必须重新加载,用户必须等待页面加载完才能继续查看,而且如果是页面中有很长的列表,那么用户就要重新滑动过去,体验很差。
ii、android的对html5的location.replace(url)支持缺失,对页面需要更新连接等操作造成了很大的困扰。
基于这两个原因,我拦截了新打开的连接,手动识别连接地址是否发生变化,发生变化了就用新的activity/fragment /controller 打开。
具体怎么拦截呢?
android
iWebView.setWebViewClient(new MyWebViewClient()); private class MyWebViewClient extends WebViewClient{ //why Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB //http://tanghaibo001.blog.163.com/blog/static/9068612020121023113646644/ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { //lastHttpUrl if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB && (url.startsWith("http:") || url.startsWith("https:"))){ lastHttpUrl = url; } if(!onShouldOverrideUrlLoading(view,url)){ if(url.startsWith("http:") || url.startsWith("https:")){ view.loadUrl(url); } } return true; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon){ super.onPageStarted(view, url, favicon); if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB && (url.startsWith("http:") || url.startsWith("https:"))){ if(!url.equals(lastHttpUrl)){ if(onShouldOverrideUrlLoading(view,url)){ view.stopLoading(); if(view.canGoBack()){view.goBack();} return; } } } if(url.startsWith("http:") || url.startsWith("https:")){showLoading();} if(delegate != null){delegate.onTitleButtonChange("", "");} } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if(isActivityFinishinig()){ return; } if(view != null){ runJs(UrlProtocol.ValidProtocol); if(!view.getSettings().getLoadsImagesAutomatically()) { view.getSettings().setLoadsImagesAutomatically(true); } } onPageFinish(view); stopLoading(); } @Override public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); onError(ConstVal.WebError.net); } @Override public void onLoadResource(WebView view, String url) { super.onLoadResource(view, url); runJs(UrlProtocol.ValidProtocol); } } private boolean onShouldOverrideUrlLoading(WebView view, String url){ if(HandleUrlByDelegate(url)){ return true;} if(HandleUrlByProtocol(view,url)){return true;} if(HandleUrlByReplace(url)){return true;} return false; }
如上文代码可见,就是在onShouldOverrideUrlLoading中判断是不是activity中扩展的回掉要用到?是不是我们自定义的那些协议?最后一个就是要不要跳转,要的话就打开新页面跳转。
这里有个小坑,就是onShouldOverrideUrlLoading不一定时时有效,具体原因已经给连接了,怎么处理也在代码中有体现了。
IOS:
上一篇博客有给具体实现:
http://www.voidcn.com/article/p-giglocoz-da.html
是在 webViewDidStartLoad 中做的处理,代码都很类似。
3、web和原生如何进行双向交流?
原生到web:
这个是最简单的:
//android public void runJs(String action){ if(iWebView != null){ iWebView.loadUrl("javascript:"+action);; } } //IOS func runJs(action:String?){ if #available(iOS 8.0, *) { if let wkweb = view as? WKWebView { wkweb.runJs(action) } } else { if let uiweb = view as? UIWebView{ uiweb.runJs(action) } } }其中,action就是要执行的javascript代码。
web到原生:
有两种方式:
i、原生提供接口给web回掉
举个例子:
//android: webview.addJavascriptInterface(new JsObject(), "nativeJava"); public class JsObject implements nativeJava{ @JavascriptInterface public void clearHistory(){ //let it empty } } //javascript window.nativeJava.clearHistory()IOS也有提供类似的方法: https://developer.apple.com/reference/webkit/wkscriptmessagehandler
ii、通过2中的连接拦截(onShouldOverrideUrlLoading / webViewDidStartLoad )进行处理。
我用了这种方式,因为可以自定义协议,达到原生和html5的解耦。
所谓一流的公司定义协议,二流的公司遵守协议,三流的公司不懂协议。牛逼吹大了,就不详细写了。
还有很多细节点,下次有时间再接着写吧。