前言:
HybridApp在过去的两年中已经成为移动界的核心话题,但是作为一名Web开发者来说要如何站在移动互联网的浪潮之巅呢?是选择学习原生开发,研究Java、Object-C、C#等语言,还是选择继续使用网页开发,容忍HTML5功能的局限性?就在开发者左右为难的情况下HybridApp作为一个折中的解决方案诞生了
概念:
HybridApp是同时采用网页语言与程序语言进行开发,通过不同的应用商店进行打包与分发,应用的特性更接近原生应用而且又区别与Web应用。但是在开发过程中同时使用了网页语言,所以开发成本与难度大大降低。也就是说HybridApp兼具了NativeApp与WebApp两者的诸多优点。 对比:
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 List<String> mListName = new ArrayList<String>();
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<String>();
}
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<String>();
}
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 {
WeakReference<JsBridgeWebView> mJsBridgeWebView;
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:
<!DOCTYPE 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>
<!--代码部分end-->
</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>
总结:
两个注意点:
1.如果你的程序目标平台是17或者是更高,你必须要在暴露给网页可调用的方法(这个方法必须是公开的)加上
@JavascriptInterface注释。如果你不这样做的话,在4.2以以后的平台上,网页无法访问到你的方法。
2. webView 的方法调用必须要在同一线程(例子中是主线程)所以用WorkThread 消息给主线程的Handler 进行
webView 的方法调用。Handler 使用过程中注意内存泄露的问题。
延伸:
上面例子也可认为是个人实现的一个JSbridge 的微型框架。回到Hybrid APP框架话题上来,都说混合开发把UI的开发
交给H5,业务部分给移动开发去做,曾经有牛人问我一个这样的问题,说有没有考虑过,让移动开发去做UI(流畅),而把
业务逻辑交给html(ajax)去做,仔细想想,虽然技术上也可实现,但是现实意义在哪里,我还真没想透。如果您有见解,欢迎留言。