Hybrid app开发从了解到深入

来源:互联网 发布:威客圈淘宝单 编辑:程序博客网 时间:2024/06/01 08:11

参考文章:

http://blog.csdn.net/qq_23547831/article/details/51812985

http://blog.csdn.net/sbsujjbcy/article/details/50752595

http://blog.csdn.net/xiangzhihong8/article/details/66970600

这篇博客是通过整理以上文章,并结合自己的思路来写的,感谢这些博主的分享,没有你们的分享,就没有我这篇博文,感恩。


要学习某个东西之前,我们首先要了解这个东西是什么?然后我们要了解这东西有什么用,有什么好处和弊端?最后我们要知道这东西怎么用?
简单点就是 ------是什么?有什么用?怎么用?

那么进入正题

一、什么是Hybrid 开发?

Hybrid App开发(混合模式移动应用开发)是指开发介于web-app、native-app这两者之间的app,这种app兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。


二、存在即合理,Hybrid开发的意义

首先,我们看下Hybrid app和web-app、native-app之间的一些区别


可见Hybrid app开发相比于其他开发还是好处多多的,那么它相比于Native开发的好处有哪些呢?

使用Native开发的方式人员要求高,只是一个简单的功能就需要iOS程序员和Android程序员各自完成;

使用Native开发的方式版本迭代周期慢,每次完成版本升级之后都需要上传到App Store并审核,升级,重新安装等,升级成本高;

使用hybrid开发的方式简单方便,同一套代码既可以在IOS平台使用,也可以在Android平台使用,提高了开发效率与代码的可维护性;

使用hybrid开发的方式升级简单方便,只需要服务器端升级一下就好了,对用户而言完全是透明了,免去了Native升级中的种种不便;


三、如何进行Hybrid 开发?

Hybrid 开发可以使用第三方框架(此处不细讲,有兴趣的朋友可以自行了解),也可以用Android自带的webView来加载H5
那么我们来讲讲android 自带的webView和Js之间的互调吧。

首先,别忘记加网络权限和引入webView.

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

Native中通过addJavascriptInterface添加暴露出来的JS桥对象,然后再该对象内部声明对应的API方法。

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?

Android4.2以下,addJavascriptInterface方式有安全漏掉
iOS7以下,JS无法调用Native
JSBridge有一套现有的成熟方案,可以完美兼容各种版本,对以前老版本技术的兼容。


3、JSBridge的原理与实现

我们知道,Native要调用JS是非常简单的,只要使用WebView.loadUrl(“JavaScript:function()”)即可。那么JS怎么调Native呢?很明显,上面讲的已经行不通了,毕竟有兼容性问题和安全问题。那么怎么办呢?我们得寻找一个突破口吧,大家仔细回一下,WebView有一个方法,叫setWebChromeClient,可以设置WebChromeClient对象,而这个对象中有三个方法,分别是onJsAlert,onJsConfirm,onJsPrompt,当js调用window对象的对应的方法,即window.alertwindow.confirmwindow.prompt,WebChromeClient对象中的三个方法对应的就会被触发,我们是不是可以利用这个机制,自己做一些处理?答案是肯定的。
至于js这三个方法的区别,可以详见w3c JavaScript 消息框 。一般来说,我们是不会使用onJsAlert的,为什么呢?因为js中alert使用的频率还是非常高的,一旦我们占用了这个通道,alert的正常使用就会受到影响,而confirm和prompt的使用频率相对alert来说,则更低一点。那么到底是选择confirm还是prompt呢,其实confirm的使用频率也是不低的,比如你点一个链接下载一个文件,这时候如果需要弹出一个提示进行确认,点击确认就会下载,点取消便不会下载,类似这种场景还是很多的,因此不能占用confirm。而prompt则不一样,在Android中,几乎不会使用到这个方法,就是用,也会进行自定义,所以我们完全可以使用这个方法。该方法就是弹出一个输入框,然后让你输入,输入完成后返回输入框中的内容。因此,占用prompt是再完美不过了。

OK,找到突破口后,我们就开始实现通信,那么怎么实现呢?既然要通信,那么我们是不是得有通信协议呢?我们可以参考http 的样式http://host:port/path?param=value 来 规定一个属于我们自己的协议:
jsbridge://className:port/methodName?jsonObj

这样我们Native拿到这串字符串后,通过解析,就可以知道调用哪个类的哪个方法,此处jsonObj用来封装请求的参数。

细心的朋友一点发现了里面有个port,那么这个port是拿来干什么的呢?
其实js层调用native层方法后,native需要将执行结果返回给js层,不过你会觉得通过WebChromeClient对象的onJsPrompt方法将返回值返回给js不就好了吗,其实不然,如果这么做,那么这个过程就是同步的,如果native执行异步操作的话,返回值怎么返回呢?这时候port就发挥了它应有的作用,我们在js中调用native方法的时候,在js中注册一个callback,然后将该callback在指定的位置上缓存起来,然后native层执行完毕对应方法后通过WebView.loadUrl调用js中的方法,回调对应的callback。那么js怎么知道调用哪个callback呢?于是我们需要将callback的一个存储位置传递过去,那么就需要native层调用js中的方法的时候将存储位置回传给js,js再调用对应存储位置上的callback,进行回调。

上面是js向native的通信协议,那么另一方面,native向js的通信协议也需要制定,一个必不可少的元素就是返回值,这个返回值和js的参数做法一样,通过json对象进行传递,该json对象中有状态码code提示信息msg,以及返回结果result,如果code为非0,则执行过程中发生了错误,错误信息在msg中,返回结果result为null,如果执行成功,返回的json对象在result中。下面是两个例子,一个成功调用,一个调用失败。
{    "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,可以参考上面一段解释。

好了,那么我们可以根据以上的内容先来设计JS了(看注释的重点)
(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);

我们在call()方法里随机生成领域各port后将callback存入callbacks数组中的port位置,根据之前我们定义的协议生成uri,调用window.prompt(uri,"");
我们还需要一个方法来处理js回调,所以有了onFinish方法,方法里面做的事:首先根据port从callbacks中取出callback执行,然后将callbacks的port位置元素删除。

好了,JS这边处理完了,那我们native这边是不是要暴露方法给JS调用呢? 没错,我们需要一个类似android 原生的@javascriptInterface引用,就是暴露给JS调用的方法,所以我们有了如下的方法
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){}

OK,那我们看看这个register方法究竟做了些什么事吧
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)是否存在,不存在的话就往里面添加该类暴露出来的方法(三个参数要满足上面的规范的方法)

好了,我们暴露出去方法了,那么这时候js调用后,从我们的突破口,没错,就是onJsPrompt方法里,我们要做的事就是调用我们的native方法,所以有了以下代码
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;    }}
别忘了给webView设置
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);

JS这边我只放重点的那行代码
<button onclick="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();    }

JS中加入
<button onclick="JSBridge.call('bridge','testThread',{},function(res){alert(JSON.stringify(res))})">

点击后,3秒弹出




有些框架里面的突破口不在onJsPrompt方法里,在shouldOverrideUrlLoading方法里,不过原理应该差不多。

好了,完结,撒花,放鞭炮,收工!

谢谢观看,由于本人技术有限,写的不好的或者有错的,大神多多指导一下



轻松一刻:

儿子拿回成绩单。
老爸:数学0分?

然后又看了一眼成绩单: 语文..1分?

儿子点点头,颤抖中...
空气凝结,气氛无比恐怖
感觉大事不妙...

老爸深吸一口烟
说道:儿啊,你有点偏文科呀。















原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 社保卡号弄错了怎么办 社保名字写错了怎么办 档案和身份证年龄姓名不一样怎么办 档案年龄与身份证年龄不一样怎么办 户口本身份证和档案不一样怎么办 如果档案姓名与身份证不符怎么办 感冒吃了白参怎么办 吃辣的嗓子疼怎么办 美团客户更改地址怎么办 忘记steam的账户名称怎么办 重置手机忘了密码怎么办 sp下行短信费扣怎么办 hr公司业务员招不到人怎么办 卖房中介被房倒压房子怎么办 电脑放不了dvd光盘怎么办 股东迟迟不交齐股本金怎么办 wps转pdf就乱了怎么办 被有用分期骗了怎么办 找不到以前有用分期的账号怎么办 打工去韩国不懂韩语怎么办? 想去韩国整容没钱怎么办 专接本没接上怎么办 抄写经文写错了怎么办 在外地修车被宰怎么办 国外汇款公司名称写错了怎么办 增值税专票没有机器编码怎么办 发票右上角的编码打不全怎么办 税票名称开错了怎么办 开票名称开错了怎么办 退休党员不交党费怎么办 cad打不出来字怎么办 用cad打不出来字怎么办 打字总打错字母怎么办 mac做ppt卡住了怎么办 mac的ppt卡住了怎么办 淘宝店铺被屏蔽了怎么办 淘宝申请售后卖家拒绝怎么办 淘宝投诉卖家入口关闭怎么办 遇见最喜欢孩子的父母怎么办 房屋备案表丢了怎么办 淘宝发布商品没有品牌怎么办