前言
其实之前一直都很抵制hybrid开发,因为作为一个Android开发程序员,总是觉得原生的更好(其实是不想丢饭碗),但是一个闲着没事干,就写了一个demo搭了个webview,然后把html文件放到asset下面,一加载惊呆宝宝了,简直跟原生的没有区别啊,体验跟原生基本一样(andrid 5.0以后webview的速度比之前的版本有很大的提升),至此我就走上了学习混合开发的道路.
前期准备 WebView
其实我相信很多跟我一样刚入门混合开发的人,对于应该要学习哪部分知识都会感到迷惑,在这里我先谈谈我的经验:
- html基础,不用说很厉害,但是至少你要知道整个html的体系还有css,div等控件的使用,还有对html节点的一些基本操作
- javaScript基础,这里说的基础就是语法之类的,js这一部分其实挺重要的,跟上面一样,但是js你懂得越多,少走的坑也就真的越少,切身体会啊!!!
- 对一些常见的前段框架的运用,比如jquery,sui-mobile,第三点倒不是很重要
- webview的原理,这一点挺重要的,因为它涉及到webview中的js怎么去与android的native交互的原理,懂得原理你可以在混合开发中更加”自由地”做出你想要的东西
把网页搬到自己的app上面
假如我们的第一个需求是:把一个网页搬到自己的app上面。那此时,我们只需要下面这段代码就行
public class BolgActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bolg);
webView = (WebView) findViewById(R.id.webview);
//这段代码的作用是让webview不要使用系统自带浏览器
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
return false;
}
});
webView.loadUrl("http://www.baidu.com");
}
}
运行结果:
在一般情况下,我们的做法都是把html文件放在app的asset目录下,然后通过下面的代码来加载html文件,这样大大提高了加载速度
mWebView.loadUrl("file:///android_asset/index.html");
native提供帮助
很明显这不能满足我们的需求,这样你的应用和浏览器又有什么区别,那现在我们提高要求。假如现在我们有一个新的需求:
要求网页弹出一个加载框(要求不是网页的加载框,而是android系统原生的加载框)
在完成这个需求之前,就必须先讲讲webview中的页面怎么去和android原生交互了.我们可以先把webview想象成一个容器,那么html页面就是运行在这个容器上面的.
那么我们的webview想要去调用html页面里的js就通过下面这段代码
webView.loadUrl("javascript: log()");
那么js中要调用android里面的代码就是通过webview注入一个对象,然后让js调用,下面我用伪代码表示一下:
{
//假设这里是activity里面
webView.addJavascriptInterface(new JsInterface(), "interface");
class JsInterface{
void showToast(String msg){
Toast.make(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
}
}
}
-------------------------------------------------------
{
//假设这里是在一段JavaScript函数里面
interface.showToast("I'm js");
}
上面讲的是第一种js和native交互的方式,其实我说的很直白,如果看不懂可以看看下面这篇博文:
js与webview的交互(并不是很喜欢这种转载的,但由于原创的那篇打不开,将就一下吧)
进阶 JsBridge
ps:上面我们讲了js与native交互的一种方案,但在实际运用中我发现并不是很好用,js与native的调用一多,会显得代码非常混乱,当然后面也有个方案能解决,甚至比我接下来要讲的方案还要好,这里先讲下我现在在使用的方案。
相信我的这篇blog是你查阅了大量资料以后才无意中看到的,那么你肯定是知道JsBridge了。先看看代码
{
//这是在native中的代码,mWebView为JsBridge封装过的webview
mWebView.callHandler("myInit", "我是数据", new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
}
-----------------------------------------------------------------------------------------------------
{
//这是在Js中的代码,bridge是native注入在js中的对象
bridge.registerHandler("functionInJs", function(data, responseCallback) {
document.getElementById("content").innerHTML = ("data from Java: = " + data);
var responseData = "Javascript Says 我要你的地址!";
responseCallback(responseData);
});
}
上面这段代码什么意思呢?就是在js中通过bridge注册一个函数,然后在native中通过封装好的webview.callHandler函数来调用js中已经注册好的函数,那么反过来Js想调用native的函数,也是同样的道理,只要在native中注册好函数,在js中通过bridge.callHandler来调用就行了。
这样给我们的开发提供了极大的方便,而且避免了很多代码混乱的情况,那么它的基本原理是什么呢?下面我还是用最简单地语言来讲一讲。
- 在我们的webview中,可以设置自定义的WebChromeClient,这个东西就是加载在webview中的html中有弹窗事件的时候会回调的
- WebChromeClient里面有很多种回调,其中有一个回调方法onJsPrompt,这个方法就是我们的突入点(其他方法也都可以,但是可能会干扰到原来的逻辑,这个比较少用,所以我们用这个)
语言是不是很简单暴力?如果你还看不懂,看看下面的实例:
{
//假设这里是在js里面
var uri = "hybrid://objectName:sid/methodName?params";
var value = "xxxxxxx";
window.prompt(uri, value);
}
--------------------------------------------------------------
{
//这是在activity里面
public class InjectedChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
//这里传进来的参数就是从js的window.prompt(uri, value)传过来的参数
return super.onJsPrompt(view, url, message, defaultValue, result);
}
}
}
JsBridge的工作原理就是这样,那么现在看来好像对我们没什么用呀?下面我举个实际应用的例子说一下,就拿上面那个例子来说:要求网页弹出一个加载框(要求不是网页的加载框,而是android系统原生的加载框)
(先不要讲太复杂的代码,从上面JsBridge的知识我们已经知道了js里面能传递数据给webview)
{
//假设这个方法是js中负责发送数据给webview的
function postToNative(data){..省略..}
var data =
{
"tagName":"showLoadingDialog",
"pars":{
"content":"正在加载中",
"title":"请等待",
//这个callback就是当加载框被取消的时候native回调js中的函数
"callback":"onLoadingCancel"
}
};
postToNative(data);
}
{
//假设这个方法实在native中接收webview中的js发过来的数据
void receiveJsData(String data){
//上面js的函数可以看出,js向我们发送了一段json数据
//所以我们只需解析这段数据,便可知道js需要native做什么事
}
}
总结
对比
觉得上面讲的都是废话?
其实网上关于hybrid开发的文章我敢说我基本都看过了,国外的文章也有看了看,在实际开发中,我自己也写了很多demo,下面是我总结的关于一些JsBridge的优缺点:
优点:
- 相比传统的方式方便很多,代码更加简洁
- 其实第一点已经完爆一切了,写不出第二点了哈哈
缺点:
1. 调试困难,不知是否是博主基础太差,过程中有许多意料不到的错(当然是关于js的),不过到后面基本都能解决了
2. 如果要做到一个页面可以兼容所有web页面,那需要注册的函数太多了,因为每个页面需要的功能都不一样
3. 生命周期有点难掌控,假如你在JsBridge未初始化之前就调用,那么会导致JsBridge初始化失败(之前这个坑陷了好久后面才发现的)
实际应用
在实际开发中,我的方法是通过json来灵活地定义每个动作,也就是利用json数据,来告诉native我需要做什么动作。通过这种方式,一个app甚至只要一个activity就能解决所有的页面,此时有人要问了,那我每个页面的ui就都一样了吗?比如说toolbar,alertDialog。
然而并不是这样的,通过我上面将的知识点,可以在js初始化的时候,定义一段json数据,用户初始化ui,例如下面的:
var data =
{
"title":"话题列表",
"right":{
"icon":"R.mipmap.ic_launcher",
"description":"描述",
"callback":"onRightBtnClick"
}
};
然后在native中解析这段数据,来达到初始化ui的效果,这种方式我也已经运用在我的项目中。
相信你看了这边文章,你已经基本知道了webview在实际开发中的应用和各种方案,如果觉得文章有哪些地方写的不好,请留言说明。
ps:代码下次贴出,在写个几个基类供参考,写得还不是很完善,需要的也提出来把!