前言:
HybridApp在过去的两年中已经成为移动界的核心话题,但是作为一名Web开发者来说要如何站在移动互联网的浪潮之巅呢?是选择学习原生开发,研究Java、Object-C、C#等语言,还是选择继续使用网页开发,容忍HTML5功能的局限性?就在开发者左右为难的情况下HybridApp作为一个折中的解决方案诞生了
概念:
|
Web App(网页应用) |
Hybrid App(混合应用) |
Native App(原生应用) |
开发成本 |
低 |
中 |
高 |
维护更新 |
简单 |
简单 |
复杂 |
体验 |
差 |
中 |
优 |
Store或market认可 |
不认可 |
认可 |
认可 |
安装 |
不需要 |
需要 |
需要 |
跨平台 |
优 |
优 |
差 |
APK size |
无 |
轻量 |
大 |
Hybrid App 融合 Web App的原理就是引入一个WebView组件,可以在这个组件中载入HTML页面做UI呈现(部分的),通过JsBridge 实现业务层与界面层的数据通讯、逻辑调用。
技术实现:
自定义JsBridgeWebView 继承自android.webkit.WebView 并内部定义一个BridgeHandler接口
@SuppressLint("SetJavaScriptEnabled") public class JsBridgeWebView extends WebView { private ListmListName = new ArrayList (); private class BridgeHandlerHolder implements BridgeHandler { private BridgeHandler mBridgeHandler; public BridgeHandlerHolder(BridgeHandler in) { mBridgeHandler = in; } @Override @JavascriptInterface public String handler(String data0, String data1, String o) { return null != mBridgeHandler ? mBridgeHandler.handler(data0, data1, o) : null; } } public interface BridgeHandler { public String handler(String data0, String data1, String o); } public void callback(Object... args) { if (args.length > 0) { String jsFunctionName = (String) args[0]; StringBuilder jsFunctionParam = new StringBuilder(); if (!TextUtils.isEmpty(jsFunctionName)) { for (int i = 1; i < args.length; i++) { if (i == 1) { jsFunctionParam.append(String.format("'%s'", args[i])); } else if (i > 1) { jsFunctionParam.append(String.format(",'%s'", args[i])); } } String js = String.format("javascript:%s(%s)", jsFunctionName, jsFunctionParam); this.loadUrl(js); } } } public JsBridgeWebView(Context context) { super(context); initSetting(); } public JsBridgeWebView(Context context, AttributeSet attrs) { super(context, attrs); initSetting(); } public JsBridgeWebView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initSetting(); } public void initSetting() { WebSettings webSettings = this.getSettings(); webSettings.setJavaScriptEnabled(true); webSettings.setSupportZoom(true); } public void unRegisterHandlerAll() { if (null != mListName) { for (int i = 0; i < mListName.size(); i++) { this.removeJavascriptInterface(mListName.get(i)); } mListName.clear(); } } public void unRegisterHandler(String name) { if (null == mListName) { mListName = new ArrayList (); } if (null != mListName && mListName.contains(name)) { mListName.remove(name); this.removeJavascriptInterface(name); } } public boolean registerHandler(String name, BridgeHandler bridgeHandler) { boolean bReturn = true; if (null == mListName) { mListName = new ArrayList (); } if (null != mListName && mListName.contains(name)) { bReturn = false; } if (bReturn) { this.addJavascriptInterface(new BridgeHandlerHolder(bridgeHandler), name); if (null != mListName) { mListName.add(name); } } return bReturn; } }
Layout XML 使用:
<RelativeLayout>
<com.example.qinghua_liu.myapplication.customview.JsBridgeWebView android:id="@+id/JsBridgeWebView" android:layout_width="match_parent" android:layout_height="match_parent"/> RelativeLayout>Activity Code:public class ActivityMain4Activity extends Activity { private RelativeLayout mainLayout; private JsBridgeWebView jsBridgeWebView; private Context mContext; private WebViewHandler mWebViewHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main4); mContext = this; jsBridgeWebView = (JsBridgeWebView) findViewById(R.id.JsBridgeWebView); mWebViewHandler = new WebViewHandler(jsBridgeWebView); //jsBridgeWebView.initSetting(); mainLayout = (RelativeLayout) findViewById(R.id.main_layout); jsBridgeWebView.registerHandler("Register", new JsBridgeWebView.BridgeHandler() { @Override public String handler(String data0, String data1, String o) { Toast toast = Toast.makeText(mContext, String.format("register data. Name:%s ,Psw:%s", data0, data1), Toast.LENGTH_SHORT); toast.show(); final Object jscallbackFuncName = o; new Thread(new Runnable() { @Override public void run() { Message message = mWebViewHandler.obtainMessage(); message.obj = jscallbackFuncName; mWebViewHandler.sendMessageDelayed(message, 3000); } }).start(); return ""; } }); jsBridgeWebView.loadData("", "text/html", null); jsBridgeWebView.loadUrl("file:///android_asset/www/register.html"); jsBridgeWebView.setWebViewClient(new WebViewClient() { }); } private static class WebViewHandler extends Handler { WeakReferencemJsBridgeWebView; public WebViewHandler(JsBridgeWebView view) { mJsBridgeWebView = new WeakReference<>(view); } public void handleMessage(Message msg) { final JsBridgeWebView webview = mJsBridgeWebView.get(); if (null != webview) { String jscallbackFuncName = msg.obj.toString(); webview.callback(jscallbackFuncName, "1001"); } //handle you message here! } } } register.html:
html> <html> <head> <base target="_blank"/> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style type="text/css"> *{margin:0;padding: 0;} .lanren{width: 221px;border: solid #E5E5E5; border-width: 1px 0 0 1px;margin:50px auto;} .lanren ul{overflow: hidden;} .lanren ul li{float:left;width:200px;height:100px;padding:15px 10px 10px 10px;border-right:1px solid #EDEDED;border-bottom:1px solid #EDEDED; overflow: hidden;position: relative;list-style: none;} .lanren ul li img{float: left;margin-right:10px;} .lanren ul li p{text-align: left;color: #666;font-size: 12px;margin-bottom: 5px;line-height: 140%;max-height: 32px;overflow: hidden;} .lanren ul li b{color: #e12228;font-size: 16px;font-weight: 700;} style> head> <body> <br/> <div style="margin-left:15px;"> 用户名:<br/> <input type="text" id ="namec"/> <br/> <br/> 密码: <br/> <input type="password" id ="psw"/> <br/><br/><br/> <input type="button" id="play" value="注册" οnclick="callAndroidRe();"> <br/><br/><div id="res"> div><br/><br/>广告推广div> <div class="lanren"> div> <script src="jquery.min.js">script> <script> $(function(){ $('.lanren li').hover(function(){ $(this).find('img').stop().animate({ 'margin-left':'-7px', 'margin-right':'17px' }) },function(){ $(this).find('img').stop().animate({ 'margin-left':'0', 'margin-right':'10px' }) }) }) script> body> html> <script language="JavaScript" type="text/javascript"> var res = document.getElementById('res'); var namec = document.getElementById('namec'); var psw = document.getElementById('psw'); function jsCallback (userid){ res.innerHTML= '注册成功! 您的ID是:'+userid; } var Person = function (userid) { this.userid = userid; }.method('getName', function () { res.innerHTML= '注册成功! 您的ID是:'+this.userid; }).method('setName', function (userid) { this.userid = userid; return this; }); function callAndroidRe(){ //alert('qh'); res.innerHTML= '注册中'; var id = window.Register.handler(namec.value,psw.value,'jsCallback'); //res.innerHTML= '注册成功! 您的ID是:'+id; } script>HTML 写的注册界面:调用JsBridge,注册成功:总结:两个注意点:1.如果你的程序目标平台是17或者是更高,你必须要在暴露给网页可调用的方法(这个方法必须是公开的)加上
@JavascriptInterface注释。如果你不这样做的话,在4.2以以后的平台上,网页无法访问到你的方法。
上面例子也可认为是个人实现的一个JSbridge 的微型框架。回到Hybrid APP框架话题上来,都说混合开发把UI的开发2. webView 的方法调用必须要在同一线程(例子中是主线程)所以用WorkThread 消息给主线程的Handler 进行webView 的方法调用。Handler 使用过程中注意内存泄露的问题。延伸:交给H5,业务部分给移动开发去做,曾经有牛人问我一个这样的问题,说有没有考虑过,让移动开发去做UI(流畅),而把业务逻辑交给html(ajax)去做,仔细想想,虽然技术上也可实现,但是现实意义在哪里,我还真没想透。如果您有见解,欢迎留言。