参考文章:
http://blog.youkuaiyun.com/qq_23547831/article/details/51812985
http://blog.youkuaiyun.com/sbsujjbcy/article/details/50752595
http://blog.youkuaiyun.com/xiangzhihong8/article/details/66970600
这篇博客是通过整理以上文章,并结合自己的思路来写的,感谢这些博主的分享,没有你们的分享,就没有我这篇博文,感恩。
要学习某个东西之前,我们首先要了解这个东西是什么?然后我们要了解这东西有什么用,有什么好处和弊端?最后我们要知道这东西怎么用?
简单点就是 ------是什么?有什么用?怎么用?
那么进入正题
一、什么是Hybrid 开发?
二、存在即合理,Hybrid开发的意义
使用Native开发的方式版本迭代周期慢,每次完成版本升级之后都需要上传到App Store并审核,升级,重新安装等,升级成本高;
使用hybrid开发的方式简单方便,同一套代码既可以在IOS平台使用,也可以在Android平台使用,提高了开发效率与代码的可维护性;
使用hybrid开发的方式升级简单方便,只需要服务器端升级一下就好了,对用户而言完全是透明了,免去了Native升级中的种种不便;
三、如何进行Hybrid 开发?
Native调用JS
在4.4之前,调用的方式:
//ui线程中运行,因为mWebView是UI控件
runOnUiThread(new Runnable() {
@Override
public void run() {
//缺点:无法取得返回值
mWebView.loadUrl("javascript: 方法名('参数,需要转为字符串')");
Toast.makeText(Activity名.this, "调用方法...", Toast.LENGTH_SHORT).show();
}
});
4.4以后(包括4.4),使用以下方式:
mWebView.evaluateJavascript("javascript: 方法名('参数,需要转为字符串')", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//这里的value即为对应JS方法的返回值
}
});
传统的Native调用JS还有个缺点就是不适合传输大量数据(大量数据建议用接口方式获取)。
JS调用Native
WebSettings webSettings = mWebView.getSettings();
//Android容器允许JS脚本
webSettings.setJavaScriptEnabled(true);
//Android容器设置侨连对象,api17前有可被hacker攻击的漏洞,api17(4.4)之后调用需要在调用方法加入加入@JavascriptInterface注解,
//如果代码无此申明,那么也就无法使得js生效,也就是说这样就可以避免恶意网页利用js对安卓客户端的窃取和攻击。
mWebView.addJavascriptInterface(this, "JSBridge");
首先看下这个方法
public void addJavascriptInterface(Object object, String name)
我是在Activity里 写的这段代码,那么这里第一个参数传入this,即Activity实例,第二个参数则是给第一个参数起的别名,方便JS里调用。然后在Activity里暴露两个方法
//在Android4.2以上(api17后),暴露的api要加上注解@JavascriptInterface,否则会找不到方法
@JavascriptInterface
public String getStudentName(){
return "张三";
}
@JavascriptInterface
public String getMyName(final String name){
return "my name is:" + param;
}
然后我们在HTML里调用Native方法
//调用方法一
window.JSBridge.getStudentName(); //返回:'张三'
//调用方法二
window.JSBridge.getMyName('韩梅梅');//返回:'my name is 韩梅梅'
OK,以上就是我们传统的调用方式,但是显而易见,传统的调用存在许多的不足,所以出现了许多好用的第三方框架,那么接下来,我们讲解一个常用框架JSBridge
四、JSBridge
OK,让我们提出我们的疑问,什么是JSBridge?JSBridge有什么好处(相比于传统的Native和JS互调)?JSBridge是怎么设计出来的呢?JSBridge怎么使用呢?
1、什么是JSBridge?
JSBridge就是js和Native之前的桥梁,是用来定义Native和JS通信的,Native只通过一个固定的桥对象调用JS,JS也只通过固定的桥对象调用Native。
2、为什么使用JSBridge?
iOS7以下,JS无法调用Native
JSBridge有一套现有的成熟方案,可以完美兼容各种版本,对以前老版本技术的兼容。
3、JSBridge的原理与实现
jsbridge://className:port/methodName?jsonObj
{
"code":500,
"msg":"method is not exist",
"result":null
}
{
"code":0,
"msg":"ok",
"result":{
"key1":"returnValue1",
"key2":"returnValue2",
"key3":{
"nestedKey":"nestedValue"
"nestedArray":["value1","value2"]
}
}
}
Ok,这样我们在native里调用
mWebView.loadUrl("javascript:JSBridge.onFinish(port,jsonObj);");
即可,在调用JS暴露的方法时顺便把port带上,为什么要带port,可以参考上面一段解释。
(function (win) {
var hasOwnProperty = Object.prototype.hasOwnProperty;
var JSBridge = win.JSBridge || (win.JSBridge = {});
var JSBRIDGE_PROTOCOL = 'JSBridge';
var Inner = {
callbacks: {},
call: function (obj, method, params, callback) {
console.log(obj+" "+method+" "+params+" "+callback);
var port = Util.getPort();
console.log(port);
//重点
this.callbacks[port] = callback;
var uri=Util.getUri(obj,method,params,port);
console.log(uri);
window.prompt(uri, "");
},
onFinish: function (port, jsonObj){
//重点
var callback = this.callbacks[port];
callback && callback(jsonObj);
delete this.callbacks[port];
},
};
var Util = {
getPort: function () {
return Math.floor(Math.random() * (1 << 30));
},
getUri:function(obj, method, params, port){
params = this.getParam(params);
var uri = JSBRIDGE_PROTOCOL + '://' + obj + ':' + port + '/' + method + '?' + params;
return uri;
},
getParam:function(obj){
if (obj && typeof obj === 'object') {
return JSON.stringify(obj);
} else {
return obj || '';
}
}
};
for (var key in Inner) {
if (!hasOwnProperty.call(JSBridge, key)) {
JSBridge[key] = Inner[key];
}
}
})(window);
JSBridge.register("jsName",javaClass.class)
这个javaClass就是满足某种规范的类,该类中有满足规范的方法,我们规定这个类需要实现一个空接口,为什么呢?主要作用就混淆的时候不会发生错误,还有一个作用就是约束JSBridge.register方法第二个参数必须是该接口的实现类。那么我们定义这个接口
public interface IBridge{
}
类规定好了,类中的方法我们还需要规定,为了调用方便,我们规定类中的方法必须是static的,这样直接根据类而不必新建对象进行调用了(还要是public的),然后该方法不具有返回值,因为返回值我们在回调中返回,既然有回调,参数列表就肯定有一个callback,除了callback,当然还有前文提到的js传来的方法调用所需的参数,是一个json对象,在java层中我们定义成JSONObject对象;方法的执行结果需要通过callback传递回去,而java执行js方法需要一个WebView对象,于是,满足某种规范的方法原型就出来了。
public static void methodName(WebView web view,JSONObject jsonObj,Callback callback){
}
public class JSBridge {
private static Map<String, HashMap<String, Method>> exposedMethods = new HashMap<>();
public static void register(String exposedName, Class<? extends IBridge> clazz) {
if (!exposedMethods.containsKey(exposedName)) {
try {
exposedMethods.put(exposedName, getAllMethod(clazz));
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static HashMap<String, Method> getAllMethod(Class injectedCls) throws Exception {
HashMap<String, Method> mMethodsMap = new HashMap<>();
Method[] methods = injectedCls.getDeclaredMethods();
for (Method method : methods) {
String name;
if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (name = method.getName()) == null) {
continue;
}
Class[] parameters = method.getParameterTypes();
if (null != parameters && parameters.length == 3) {
if (parameters[0] == WebView.class && parameters[1] == JSONObject.class && parameters[2] == Callback.class) {
mMethodsMap.put(name, method);
}
}
}
return mMethodsMap;
}
}
简单来说,我们这个方法做的事就是先判断这个类的别名(此处是jsName)是否存在,不存在的话就往里面添加该类暴露出来的方法(三个参数要满足上面的规范的方法)
public class JSBridgeWebChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
result.confirm(JSBridge.callJava(view, message));
return true;
}
}
mWebView.setWebChromeClient(new JSBridgeWebChromeClient());
我们callJava方法里做了什么事呢?其实就是解析url,查找对应的暴露方法调用
public static String callJava(WebView webView, String uriString) {
String methodName = "";
String className = "";
String param = "{}";
String port = "";
if (!TextUtils.isEmpty(uriString) && uriString.startsWith("JSBridge")) {
Uri uri = Uri.parse(uriString);
className = uri.getHost();
param = uri.getQuery();
port = uri.getPort() + "";
String path = uri.getPath();
if (!TextUtils.isEmpty(path)) {
methodName = path.replace("/", "");
}
}
if (exposedMethods.containsKey(className)) {
HashMap<String, Method> methodHashMap = exposedMethods.get(className);
//重点
if (methodHashMap != null && methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) {
Method method = methodHashMap.get(methodName);
if (method != null) {
try {
//重点
method.invoke(null, webView, new JSONObject(param), new Callback(webView, port));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return null;
}
好了,最后,我们当然是来讲Callback啦,看重点注释就可以了,其实就是回调js的onFinish方法。
public class Callback {
private static Handler mHandler = new Handler(Looper.getMainLooper());
//重点
private static final String CALLBACK_JS_FORMAT = "javascript:JSBridge.onFinish('%s', %s);";
private String mPort;
//为了防止内存泄露,这里使用弱引用
private WeakReference<WebView> mWebViewRef;
public Callback(WebView view, String port) {
mWebViewRef = new WeakReference<>(view);
mPort = port;
}
public void apply(JSONObject jsonObject) {
final String execJs = String.format(CALLBACK_JS_FORMAT, mPort, String.valueOf(jsonObject));
if (mWebViewRef != null && mWebViewRef.get() != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
//重点
mWebViewRef.get().loadUrl(execJs);
}
});
}
}
}
public class BridgeImpl implements IBridge {
public static void showToast(WebView webView, JSONObject param, final Callback callback) {
String message = param.optString("msg");
Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show();
if (null != callback) {
try {
JSONObject object = new JSONObject();
object.put("key", "value");
object.put("key1", "value1");
callback.apply(getJSONObject(0, "ok", object));
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static JSONObject getJSONObject(int code, String msg, JSONObject result) {
JSONObject object = new JSONObject();
try {
object.put("code", code);
object.put("msg", msg);
object.putOpt("result", result);
return object;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
}
JSBridge.register("bridge", BridgeImpl.class);
<button οnclick="JSBridge.call('bridge','showToast',{'msg':'Hello JSBridge'},function(res){alert(JSON.stringify(res))})">
接着就是使用WebView将该index.html文件load进来测试了
mWebView.loadUrl("file:///android_asset/index.html");
点击按钮后的结果截图
好了,那我们来试试子线程回调,在在BridgeImpl中加入测试方法
public static void testThread(WebView webView, JSONObject param, final Callback callback) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
JSONObject object = new JSONObject();
object.put("key", "value");
callback.apply(getJSONObject(0, "ok", object));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
}).start();
}
<button οnclick="JSBridge.call('bridge','testThread',{},function(res){alert(JSON.stringify(res))})">
儿子拿回成绩单。
老爸:数学0分?
然后又看了一眼成绩单: 语文..1分?
儿子点点头,颤抖中...
空气凝结,气氛无比恐怖
感觉大事不妙...
老爸深吸一口烟
说道:儿啊,你有点偏文科呀。