混合开发_混合开发框架 - CSDN
精华内容
参与话题
  • 混合开发

    2020-05-15 15:32:47
    混合开发三类App: 一. NativeApp(原生APP) ​ 纯工具类APP ​ 特点? ​ 开发:由Native人员进行开发的 安卓(Android) 苹果(IOS) ​ 语言:Android(Java+XML) IOS(Objective-c / Swift + XML) ​ 更新...

    混合开发三类App:

    • 1、Native App (原生App)
    • 2、Web App
    • 3、Hybrid App

    一. NativeApp(原生APP)

    纯工具类APP  
    
    ​		特点?
    		开发:Android、IOS工程师开发,
    	        Android:java,xml  配置 java jdk  , Android sdk   eclipse编辑器
    	        IOS:xml,Objective-C/swift,  mac,Imac机上    XCODE
    
    ​			更新维护:版本更新、打补丁包   都得需要用户从app store里面进行下载
    
    ​			使用:必须去app-store里面进行下载更新才能使用软件
    	特点和优势:
    			1、流畅度较高
    			2、对网络的依赖性不强,在没有网络的情况下也可能使用部分功能
    			3、可以调用设备的原生功能
    ​	缺点:
    			不能跨平台开发   必须开发两套代码(Android+IOS)
    
    
    			开发成本
    			        开发成本较高,因为需要分开开发,不能跨平台开发,而且开发周期长,
    			        使用成本较高,因为需要下载安装,话费流量,并长期占用手机的内存
    			        更新维护成本高
    			
    		
    

    二. WebApp(移动M站)

    特点:(凡是可以在移动端浏览器里面打开的网站都称之为WebApp)
    使用:无需下载  直接通过用户的手机浏览器打开使用即可
    更新维护:直接无痕更新(需要注意浏览器缓存问题   缓存的产生是基于文件的路径)
    开发: web前端开发工程师
          使用HTML5+CSS3+js,mac、pc上开发
          更新维护成本低
    
    开发成本:
            开发成本较低,可以实现跨域平台,开发周期短(比较于原生)
            使用成本低,不需要下载安装,通过浏览器空间
    
    特点和优势
            1、用户粘度低
            2、流畅度较低
            3、对网络的依赖性强
            4、不能访问设备的原生功能!因为浏览器的安装级别限制
            (同源策略、不能调用设备的原生功能及文件系统)
    缺点:不稳定 流畅度低  用户体验差   不能调用原生设备 
    

    三. Hybrid App(混合App)

    混合开发的app其实从根本上的技术就是在原生的壳子、界面里嵌入H5页面来实现功能
    
    先决技术:H5页面是可以嵌入到原生的webview中的
    开发模式:
    
    1、原生主导
    
        这是最主流的一种方式,也就是说其实现在市场上的应用已经很少有春原生的了而是大多采用了混合开发的模式,一般都是原生主导的,也就是在某个界面中去嵌入h5页面
    
        区分方式:
                    1.长按文字看是否被选中,如果能选中肯定是嵌入的H5页面
                    
                    2.安卓打开开发模式,手机上就会出现很多的线条,线条包裹起来的就是原生的元素
    
                 为什么要这么做:
                    借助H5页面的跨平台属性来降低开发成本。而且,因为内嵌应用电影、外卖,他们的webapp已经存在了,所以可以直接将webapp的页面嵌入进来
    
                什么样的页面适合去嵌入H5页面
                    布局复杂,逻辑简单,经常更新卫华的,比如详情、部分列表等等都适合嵌入H5页面的方式
    
            1.js判断操作系统
                 html中的window.navigator.userAng   
                    
                    
            2.js与native通信
                   native与js交互
                        1.ios可以调用window下面的方法
                                
                                当跳转详情的时候,详情界面就可以调用里面H5页面的JSBridge的方法来传递参数
                        2.页面中发送所有的网络请求,webview
    
                                一把都是用用iframe标签来发送请求,也就是,事先定义号一个url,作为通信的地址,然后创建iframe去发送这个请求 
                        
    
    2、H5主导的模式
    
    
            纯H5开发工程师借助一些工具去搭建native的外壳,这个应用的内部都是H5页面构成
    
        这种开发模式主要以来的是工具,(打包应用、调用设备原生功能的能力)
                        现有的技术栈
                            1、phoneggap(打包调试) + cordova(调用设备原生功能)
                
                            2、DCLOUD/APICLOUD
                                    
                            3、ReactNative/weex(使用类似H5技术开发,但是开发出来的native app)
    
                        
    
                使用DCLOUD产品开发混合APP
                        
                        HBUILDER可以进行应用的开发、调试、打包
                        MUI是一个高效的前端框架,里面继承了很多native的效果
    
    
                  H5+RUNTIE  用到调用设备原生功能的工具
    
                  1、Hbuilder可以创建移动APP(hybrid app)
    
                    2、在真机上进行应用的调试,必须保证手机能够连接上电脑
        
                    Andriod:
                    IOS:itunes   itools
                    
                    依靠基座来运行,第一次会给手机安装一个基座
    
    

    
    是一个app壳内嵌一个html兼容各种设备,减少发版频率,节约开发成本
    通过jsBright操作底层操作权限
    Hybrid一般分为两类,
        1)放在远程,应用变得非常小,服务器更新及时更新,使用必须联网
        2)打包放在自己app内,使用不需要了联网,下载包变大,不能立即更新
    

    混合应用开发:

    • 1.微信公众号:通过jssdk连接native和web

    • 2.微信小程序:内置框架连接native和web

    • 3.普通原生和web交互:jsBridge

    意义:更好地使用第三方平台,更灵活的技术方案选型,具备搭建平台和输出服务的能力

    • jsbridge是实现native端和web端的双向通信的一种机制,通过约定进行通信
      混合开发主流框架
    1.web渲染:cordova(前身phoneGap)
    2.原生渲染:react native .weex
    3.混合开发:微信小程序
    
    • jsbrige实现原理
      将native端的原生接口封装成javascript接口,将web端的javascript接口封装成原生的接口,实现双向通信
      web调用native的方式有两种:
      1.拦截webview请求中的URL schema
      2.向webview注入jsAPI

    • 方式一:兼容性好,但是不直观,url长度有限制

    //实现:native调用web的时候,web端的方法定义:
    window.方法名=参数=>{方法体}
    //web端调用native的时候,web端的代码:
    window.alert('jsbridge://原生方法?参数="xxx')
    document.location="jsbridge://原生方法?参数=xxx"
    
    • 方式二:简单直观,但是在低版本的手机可能会有兼容性的问题
      web端通过调用注入到js中的原生方法调用native
      native通过注入js调用web
    window.注入到webview的方法.原生方法(参数);
    
    • 支持回调的jsbridge
      webview调用原生的时候会有一个callbackid,返回的时候原生会把callbackid返回找到对应的回调.反之原生调用webview也是类似的
      比如:web调用native时候,web端代码实现如下
    window.JSSDK={
    	getNativeValue(callback){
    		const callID=id++;
    		callbackmap[callID]=callback;
    		nativeBridge.原生方法(callID);
    	}
    	receiveMassage(callID,value){//原生返回值的时候出发的方法
    		if(callbackmap[callID]){
    			callbackmap[callID](value)
    		}
    	}
    }
    调用:
    	widow.getNativeValue(value=>{
    		方法体处理返回值数据
    	})
    
    • native调用web的时候,web端的代码实现:
    widow.JSSDK={
    	getwebText(callbackid){
    		nativeBridge.原生方法(callbackid,'web端的值')
    	}
    }
    
    • jsbridge的开源实现
    DSBridge,可以查看官方文档.原理一样
    
    展开全文
  • 混合 APP 开发(Hybrid App)

    千次阅读 2019-04-20 21:18:37
    混合 App Html5简介 UIWebView 和 WKWebView UIWebView 和 JS 交互 WKWebView 和 JS 交互 JS 调用 Native 相机 一. 混合 APP Hybrid Mobile App 可以理解为通过 Web 网络技术(如 HTML,CSS 和 JavaScript)...

    目录

    • 混合 App
    • Html5简介
    • UIWebView 和 WKWebView
    • UIWebView 和 JS 交互
    • WKWebView 和 JS 交互
    • JS 调用 Native 相机

     

    一. 混合 APP

    Hybrid Mobile App 可以理解为通过 Web 网络技术(如 HTML,CSS 和 JavaScript)与 Native 相结合的混合移动应用程序。

    H5用于大体界面的编写,如:需要一些基本的输入框、单选按钮、普通按钮、以及下拉选择框等。

    CSS3则是主要用于对整体界面细节化的修饰。比如:一个普通按钮,输入框边角默认是直角,那我们可以用CSS来改变其形状。

    还可以用来设置不同的样式。

    JS主要是要跟服务端打交道,实现数据交互。JS中的数据交互,主要以JSON格式跟XML格式这两种格式实现。

    总体来说,H5+CSS3负责界面的搭建,JS负责数据的交互。

     

     

    二. HTML5简介

     

    下面简述一下 Hybrid 的发展史:

     

    1.H5 发布

     

    Html5 是在 2014 年 9 月份正式发布的,这一次的发布做了一个最大的改变就是“从以前的 XML 子集升级成为一个独立集合”。

     

     

     

    2.H5 渗入 Mobile App 开发

     

    Native APP 开发中有一个 webview 的组件(Android 中是 webview,iOS 有 UIWebview和 WKWebview),这个组件可以加载 Html 文件。

    在 H5 大行其道之前,webview 加载的 web 页面很单调(因为只能加载一些静态资源),自从 H5 火了之后,前端猿们开发的 H5 页面在 webview 中的表现不俗使得 H5 开发慢慢渗透到了 Mobile App 开发中来。

     

     

    3.Hybrid 现状

     

    虽然目前已经出现了 RN 和 Weex 这些使用 JS 写 Native App 的技术,但是 Hybrid 仍然没有被淘汰,市面上大多数应用都不同程度的引入了 Web 页面。

     

    三. UIWebView 和 WKWebView

    做浏览器首先要选个好的基础。iOS8提供两类浏览组件:UIWebViewWKWebView

    UIWebView是iOS传统的浏览控件,绝大多数浏览器都采用这个控件作为基础, 如Chrome,Firefox,Safari。UIWebView比较封闭,很多API都不开放,但却一度是唯一的选择。好处是,这个控件使用时间比较长,有很多方案可以参考。

    WKWebView是苹果在iOS8和 OS X Yosemite 中新推出的WebKit中的一个组件。

    它代替了 UIKit 中的UIWebView和AppKit中的WebView,提供了统一的跨双平台 API。支持HTML5的特性, 占用内存可能只有UIWebView的1/3 ~ 1/4, 拥有 60fps 滚动刷新率、内置手势、高效的app和web信息交换通道、和Safari相同的JavaScript引擎, 增加了加载进度属性, 比UIWebView性能更加强大。

    但WKWebView也不是那么完美:如没有控制Cookie的API,  对读取本地html文件的支持也不好等。

     

    四. UIWebView 和 JS 交互

     

    JavaScriptCore介绍

     

    JavaScriptCore 这个库是 Apple 在 iOS 7 之后加入到标准库的,它对 iOS Native 与 JS 做交互调用产生了划时代的影响。

    JavaScriptCore 大体是由 4 个类以及 1 个协议组成的:

     

     

    • JSContext JS 执行上下文,你可以把它理解成 JavaScriptCore 包装出来的 JS 运行的环境。
    • JSValue 是对 JavaScript 值的引用,任何 JS 中的值都可以被包装为一个 JSValue
    • JSManagedValue 是对 JSValue 的包装,加入了“conditional retain”
    • JSVirtualMachine 可以理解为JS 虚拟机JSVirtualMachine中可以创建多个 JSContext 实例他们都是可以独立运行的 JavaScript 执行环境。
    • JSExport 协议:我们可以使用这个协议暴露原生对象,实例方法,类方法,和属性给JavaScript,这样JavaScript就可以调用相关暴露的方法和属性。

     

    Native 调用 JS:

     

    • WebView 直接注入 JS 并执行
    • JavaScriptCore 方法
    WebView 直接注入 JS 并执行
    
    self.webView.stringByEvaluatingJavaScript(from: “jsFuncName()”)
    
    注意:
    这个方法会返回运行 JS 的结果(nullable NSString *),它是一个同步方法,会阻塞当前线程!尽管此方法不被弃用,但最佳做法是使用 WKWebView 类的 evaluateJavaScript:completionHandler:method。注意:
    这个方法会返回运行 JS 的结果(nullable NSString *),它是一个同步方法,会阻塞当前线程!尽管此方法不被弃用,但最佳做法是使用 WKWebView 类的 evaluateJavaScript:completionHandler:method。

     

    JavaScriptCore 方法
    // 导入 JavaScriptCore 库
    
    JavaScriptCore 库提供的 JSValue 类,是对 JavaScript 值的引用。 您可以使用 JSValue 类来转换 JavaScript 和 Objective-C 或 Swift 之间的基本值(如数字和字符串),以便在本机代码和 JavaScript 代码之间传递数据。
    
    Native 代码: 
    self.context = webView.value(forKeyPath: “documentView.webView.mainFrame.javaScriptContext")
    
    let jsValue: JSValue = self.context.objectForKeyedSubscript(“jsFuncName()”)
            jsValue.call(withArguments: ["param1" ,"param2"])
    
    JS 代码: 
    function jsFuncName(param1, param2){
    
    }
    

     

     

     

    JS 调用 Native :

     

    • 拦截 URL 请求
    • Block 方法
    • 模型注入(JavaScriptCore 的 JSExport 协议)
    拦截 URL 请求
    
    用JS 发起一个假的 URL 请求, 然后在 shouldStartLoadWith 代理方法中拦截这次请求, 做出相应处理.
    注意: 
    这里在JS 中自定义一个loadURL 方法发起请求,而不是直接使用 window.location.href
    如果要传递参数, 可以拼接在 URL 上
    
    Native 代码:
    func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
            if request.url?.scheme == "haleyAction" {
                // to do something
                return false
            }
           return true
     }
            
    JS 代码:
    function loadURL(url) {
        var iFrame;
        iFrame = document.createElement("iframe");
                iFrame.setAttribute("src", url);
                iFrame.setAttribute("style", "display:none;");
                iFrame.setAttribute("height", "0px");
                iFrame.setAttribute("width", "0px");
                iFrame.setAttribute("frameborder", "0");
                document.body.appendChild(iFrame);
                // 发起请求后这个 iFrame 就没用了,所以把它从 dom 上移除掉
                iFrame.parentNode.removeChild(iFrame);
                iFrame = null;
            }
        
            function firstClick() {
                //要传递参数时, 可以拼接在url上
                loadURL("haleyAction://shareClick?title=测试分享的标题&content=测试分享的内容&url=http://www.baidu.com");
            }

     

    Block 方法
    
    使用 block 在js中运行原生代码, 将自动与JavaScript方法建立桥梁
    注意: 这种方法仅仅适用于 OC 的 block, 并不适用于swift中的闭包, 为了公开闭包,      
    我们将进行如下两步操作:
    (1)使用 @convention(block) 属性标记闭包,来建立桥梁成为 OC 中的 block
    (2)在映射 block 到 JavaScript方法调用之前,我们需要 unsafeBitCast 函数将block 转成为 AnyObject
    
    Native 代码:
    // JS调用了无参数swift方法
    let closure1: @convention(block) () ->() = {
                
    }
    self.context.setObject(unsafeBitCast(closure1, to: AnyObject.self),   
    forKeyedSubscript: "test1" as NSCopying & NSObjectProtocol)
    
    // JS调用了有参数swift方法
    let closure2: @convention(block) () ->() = {
                
    }
    self.context.setObject(unsafeBitCast(closure2, to: AnyObject.self), 
    forKeyedSubscript: "test2" as NSCopying & NSObjectProtocol)
    
    JS 代码:
    function JS_Swift1(){
        test1();
    }
    function JS_Swift2(){
        test2('oc','swift');
    }注意: 这种方法仅仅适用于 OC 的 block, 并不适用于swift中的闭包, 为了公开闭包,      
    我们将进行如下两步操作:
    (1)使用 @convention(block) 属性标记闭包,来建立桥梁成为 OC 中的 block
    (2)在映射 block 到 JavaScript方法调用之前,我们需要 unsafeBitCast 函数将block 转成为 AnyObject
    
    Native 代码:
    // JS调用了无参数swift方法
    let closure1: @convention(block) () ->() = {
                
    }
    self.context.setObject(unsafeBitCast(closure1, to: AnyObject.self),   
    forKeyedSubscript: "test1" as NSCopying & NSObjectProtocol)
    
    // JS调用了有参数swift方法
    let closure2: @convention(block) () ->() = {
                
    }
    self.context.setObject(unsafeBitCast(closure2, to: AnyObject.self), 
    forKeyedSubscript: "test2" as NSCopying & NSObjectProtocol)
    
    JS 代码:
    function JS_Swift1(){
        test1();
    }
    function JS_Swift2(){
        test2('oc','swift');
    }

     

     

    模型注入(JavaScriptCore  JSExport 协议)
    
    步骤一: 自定义协议服从 JSExport协议
    可以使用该协议暴露原生对象,实例方法,类方法,和属性给JavaScript,这样JavaScript就可以调用相关暴露的方法和属性。遵守JSExport协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来
    
    注意:
    如果js是多个参数的话  我们代理方法的所有变量前的名字连起来要和js的方法名字一样比如: js方法为  OCModel.showAlertMsg('js title', 'js message’),他有两个参数 那么我们的代理方法 就是把js的方法名 showAlertMsg 任意拆分成两段作为代理方法名
    
    第一个参数的 argumentLabel 用 "_" 隐藏
    @objc protocol JavaScriptSwiftDelegate: JSExport {
    
        func callNoParam()
        
        func showAlert(_ title: String, msg: String)
    }
    
    步骤二: 自定义模型服从自定义协议, 实现协议方法
    
    @objc class JSObjCModel: NSObject, JavaScriptSwiftDelegate {
        weak var controller: UIViewController?
        weak var jsContext: JSContext?
        
        func callNoParam() {
            let jsFunc = self.jsContext?.objectForKeyedSubscript("jsFunc");
            _ = jsFunc?.call(withArguments: []);
        }
        
        func showAlert(_ title: String, msg: String) {
            let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "确定", style: .default, handler: nil))
            self.controller?.present(alert, animated: true, completion: nil)
        }
    }
    
    步骤三: 将模型对象注入 JS
    
    // 模型注入
    let model = JSObjCModel()
    model.controller = self
    model.jsContext = context
    // 这一步是将OCModel这个模型注入到JS中,在JS就可以通过OCModel调用我们暴露的方法了
    context.setObject(model, forKeyedSubscript: "OCModel" as NSCopying & NSObjectProtocol)
    let url = Bundle.main.url(forResource: "WebView", withExtension: "html")
    context.evaluateScript(try? String.init(contentsOf: url!, encoding: .utf8))
    context.exceptionHandler = { [unowned self](con, except) in
                self.context.exception = except
    }
    
    JS 代码:
    <div class='btn-button' onclick="OCModel.callNoParam()">JS调用Native方式三无参</div>
    <div class='btn-button' onclick="OCModel.showAlertMsg('js title', 'js message’)">JS调用Native方式三有参</div>(JavaScriptCore  JSExport 协议)
    
    步骤一: 自定义协议服从 JSExport协议
    可以使用该协议暴露原生对象,实例方法,类方法,和属性给JavaScript,这样JavaScript就可以调用相关暴露的方法和属性。遵守JSExport协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来
    
    注意:
    如果js是多个参数的话  我们代理方法的所有变量前的名字连起来要和js的方法名字一样比如: js方法为  OCModel.showAlertMsg('js title', 'js message’),他有两个参数 那么我们的代理方法 就是把js的方法名 showAlertMsg 任意拆分成两段作为代理方法名
    
    第一个参数的 argumentLabel 用 "_" 隐藏
    @objc protocol JavaScriptSwiftDelegate: JSExport {
    
        func callNoParam()
        
        func showAlert(_ title: String, msg: String)
    }
    
    步骤二: 自定义模型服从自定义协议, 实现协议方法
    
    @objc class JSObjCModel: NSObject, JavaScriptSwiftDelegate {
        weak var controller: UIViewController?
        weak var jsContext: JSContext?
        
        func callNoParam() {
            let jsFunc = self.jsContext?.objectForKeyedSubscript("jsFunc");
            _ = jsFunc?.call(withArguments: []);
        }
        
        func showAlert(_ title: String, msg: String) {
            let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "确定", style: .default, handler: nil))
            self.controller?.present(alert, animated: true, completion: nil)
        }
    }
    
    步骤三: 将模型对象注入 JS
    
    // 模型注入
    let model = JSObjCModel()
    model.controller = self
    model.jsContext = context
    // 这一步是将OCModel这个模型注入到JS中,在JS就可以通过OCModel调用我们暴露的方法了
    context.setObject(model, forKeyedSubscript: "OCModel" as NSCopying & NSObjectProtocol)
    let url = Bundle.main.url(forResource: "WebView", withExtension: "html")
    context.evaluateScript(try? String.init(contentsOf: url!, encoding: .utf8))
    context.exceptionHandler = { [unowned self](con, except) in
                self.context.exception = except
    }
    
    JS 代码:
    <div class='btn-button' οnclick="OCModel.callNoParam()">JS调用Native方式三无参</div>
    <div class='btn-button' οnclick="OCModel.showAlertMsg('js title', 'js message’)">JS调用Native方式三有参</div>

     

    五. WKWebView JS 交互

     

    WKWebView 的配置
    
    //导入 WebKit
    //创建配置类
    let confirgure = WKWebViewConfiguration()
                 
    //WKUserContentController: 内容交互控制器
    confirgure.userContentController = WKUserContentController()
            
    //创建WKWebView
    wkWebView = WKWebView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height), configuration: confirgure)
            
    //配置代理
    wkWebView.navigationDelegate = self as WKNavigationDelegate
    wkWebView.uiDelegate = self as WKUIDelegate

     

    Native 调用 JS

     

    • WebView 直接注入 JS 并执行

     

    不同于 UIWebView,WKWebView 注入并执行 JS 的方法不会阻塞当前线程。因为考虑到 webview 加载的 web content 内 JS 代码不一定经过验证,如果阻塞线程可能会挂起 App。
    
    self.wkWebView.evaluateJavaScript(“jsFuncName()") { (result, error) in
                print(result, error)
    }
    
    注意: 
    方法不会阻塞线程,而且它的回调代码块总是在主线程中运行。注意: 
    方法不会阻塞线程,而且它的回调代码块总是在主线程中运行。

     

    JS 调用 Native 

     

    • 拦截 URL 请求
    • Webkit 的 WKUIDelegate协议
    • 模型注入(Webkit 的 WKScriptMessageHandler协议)
       
    拦截 URL 请求
    拦截请求的代理方法为 WebKit 中 WKNavigationDelegate 协议的
    
     func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: ) 方法
    
    , 其它同 WebView

     

    Webkit 的 WKUIDelegate协议
    
    WKUIDelegate 协议包含一些函数用来监听 web JS 想要显示 alert 或 confirm 时触发。我们如果在 WKWebView 中加载一个 web 并且想要 web JS 的 alert 或 confirm 正常弹出,就需要实现对应的代理方法。
    
    以JS 弹出Confirm 为例, 下面是在 WKUIDelegate 监听 web 要显示 confirm 的代理方法中用 Native UIAlertController 替代 JS 中的 confirm 显示的 例子: 
    
    //通过 message 得到JS 端所传的数据,在 ios 端显示原生 alert 得到 true/false 后通过 completionHandler 回调给 JS
    
    Native 代码:
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
            let alert = UIAlertController(title: "Confirm", message: message, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) -> Void in
                completionHandler(true)
            }))
            alert.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: { (_) -> Void in
                completionHandler(false)
            }))
            self.present(alert, animated: true, completion: nil)
    }
    
    JS 代码:
    function callJsConfirm() {
            if (confirm('confirm', 'Objective-C call js to show confirm')) {
                d ocument.getElementById('jsParamFuncSpan').innerHTML = 'true';
            }else {
                 document.getElementById('jsParamFuncSpan').innerHTML = 'false';
            }
    }
    

     

    模型注入(Webkit 的 WKScriptMessageHandler协议)
    
    注意: 
    对象注入写在 viewWillAppear 中, 防止循环引用
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            //注入对象名称 APPModel, 当 JS 通过 APPModel 调用时, 可以在 WKScriptMessageHandler 代理方法中接收到
            wkWebView.configuration.userContentController.add(self, name: "APPModel")
        }
        
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            wkWebView.configuration.userContentController.removeScriptMessageHandler(forName: "APPModel")
              }
    
    JS 通过 AppModel 给 Native 发送数据,会在该方法中收到
    JS调用iOS的部分, 都只能在此处使用, 我们也可以注入多个名称(JS对象), 用于区分功能
    
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            if message.name == "APPModel" {
                //传递的参数只支持NSNumber, NSString, NSDate, NSArray,NSDictionary, and NSNull类型
                let alert = UIAlertController(title: "MessageHandler", message: message.name, preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) -> Void in
                    
                }))
                self.present(alert, animated: true, completion: nil)
            }
        }
    
    JS 代码:
    function messageHandlers() {
            //APPModel 是我们注入的对象
            window.webkit.messageHandlers.APPModel.postMessage({body: 'messageHandlers'});
    }
    注意: 
    对象注入写在 viewWillAppear 中, 防止循环引用
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            //注入对象名称 APPModel, 当 JS 通过 APPModel 调用时, 可以在 WKScriptMessageHandler 代理方法中接收到
            wkWebView.configuration.userContentController.add(self, name: "APPModel")
        }
        
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            wkWebView.configuration.userContentController.removeScriptMessageHandler(forName: "APPModel")
              }
    
    JS 通过 AppModel 给 Native 发送数据,会在该方法中收到
    JS调用iOS的部分, 都只能在此处使用, 我们也可以注入多个名称(JS对象), 用于区分功能
    
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            if message.name == "APPModel" {
                //传递的参数只支持NSNumber, NSString, NSDate, NSArray,NSDictionary, and NSNull类型
                let alert = UIAlertController(title: "MessageHandler", message: message.name, preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (_) -> Void in
                    
                }))
                self.present(alert, animated: true, completion: nil)
            }
        }
    
    JS 代码:
    function messageHandlers() {
            //APPModel 是我们注入的对象
            window.webkit.messageHandlers.APPModel.postMessage({body: 'messageHandlers'});
    }
    

     

    六. JS 通过 Native 调用iOS 硬件(相机)

    JS 调用 iOS 硬件, 本质上还是通过以上介绍的 JS 调用 Native 方法调用 Native接口,

    再由 Native 调用本地硬件, 具体实现看 demo , 这里不再赘述.

     

     

    参考链接:

     

    拦截 URL:

    https://www.jianshu.com/p/d19689e0ed83

    https://blog.csdn.net/wanglei0918/article/details/78141890

    WKWebView 和 JS 交互:

    https://github.com/marcuswestin/WebViewJavascriptBridge

    http://www.cocoachina.com/ios/20171024/20895.html

    https://blog.csdn.net/baihuaxiu123/article/details/51674726

    WebView 和 JS 交互:

    https://www.jianshu.com/p/c11f9766f8d5

    https://www.jianshu.com/p/8f3c47c24e29

    https://blog.csdn.net/longshihua/article/details/51645575

     

    Github地址: 点击打开链接

    https://github.com/LeeJoey77/WebView_H5Demo.git
    
     
    https://github.com/LeeJoey77/WebView_H5Demo.gi

     

     

     

     

     

     

     

     

    展开全文
  • 谈谈App混合开发

    千次阅读 2019-05-24 10:56:07
    混合开发的App(Hybrid App)就是在一个App中内嵌一个轻量级的浏览器,一部分原生的功能改为Html 5来开发,这部分功能不仅能够在不升级App的情况下动态更新,而且可以在Android或iOS的App上同时运行,让用户的体验更...

    混合开发的App(Hybrid App)就是在一个App中内嵌一个轻量级的浏览器,一部分原生的功能改为Html 5来开发,这部分功能不仅能够在不升级App的情况下动态更新,而且可以在Android或iOS的App上同时运行,让用户的体验更好又可以节省开发的资源。

    下面来谈谈Hybrid App开发中的技术问题。iOS方面的我不太了解,我就主要谈谈Android开发中的,其中可能会有很多说错的,请大家轻喷

    Hybrid开发中关键问题是什么

    想要在一个App中显示一个Html 5网页的功能,其实很简单,只要一个WebView就可以了。你可以点击链接来跳转网页。像这样的功能就能叫做Hybrid 开发了嘛?显然不是的。

    我觉得一个Hybrid开发的App中必须要要有的功能就是Html 5页面和Native App怎么进行交互。比如,我点了一个Html 5页面上的一个按钮或链接,我能不能够跳转到Native App的某个页面;比如我点了Html 5页面上的分享按钮,我能不能调用Native App的分享功能;比如Html加载的时候能不能获取Native App的用户信息等等。

    看下图,在网易云音乐中进入这个Html 5页面时,你点击作者:空虚小编你会进入他的主页,这个主页是Native页面,而你点击上面那个播放按钮时,云音乐会启动Native的播放界面播放音乐,你点击评论时,你会进入Native的评论页

    此处输入图片的描述

    Html 5和Native的交互

    WebView本来就支持js和Java相互调用,你只需要开启WebView的JavaScript脚本执行,然后通过代码mWebView.addJavascriptInterface(new JsBridge(), "bxbxbai");向Html 5页面时注入一个Java对象,然后就可以在Html 5页面中调用Native的功能了

    微信怎么做的

    微信应该是Hybrid 开发做的最好的App之一,它是怎么做交互的呢?

    答案就是微信JS-SDK,去微信开发者文档中可以看到,微信JS-SDK封装了各种微信的功能,比如分享到朋友圈,图像接口,音频接口,支付接口地理位置接口等等。开发者只需要调用微信JS-SDK中的函数,然后统一由JS-SDK来调用微信中的功能,这样好处就是我写了一个Html 5的应用或网页,在Android和iOS的微信中都可以正常运行了

    下面会详细讲到

    网易云音乐怎么做的

    那么网易云音乐是怎么做的呢?我用黑科技知道了上图云音乐的界面Activity是CommonSubjectActivity(名字好奇怪,如果要我从代码里找,我肯定找不到,因为还有一个类叫做EmbedBrowserActivity),我就在反编译后的云音乐代码中找相应的功能实现代码,实在没找到。不过我拿到了那个Html 5页面的地址:http://music.163.com/m/topic/194001

    用Chrome打开后发现和App中显示的不一样,然后我用Charles截了进入那个Html 5的请求,发现云音乐加载的地址是http://music.163.com/m/topic/194001?type=android ,就是加了手机系统类型

    然后在我自己的App中加载这个Html 5页面就可以看到下图,@小比比说这样的文字是可以点击跳转到个人,点击播放按钮是可以播放音乐的

    此处输入图片的描述

    从Html源代码中可以看到如下信息:

    此处输入图片的描述

    也就是说,当我点击一个用户名的时候就请求跳转到orpheus://user/30868859,因为WebView可以拦截跳转的url,所以App在拦截每一个url,如果host是orpheus的话就启动用户首页

    反编译代码后,在云音乐的代码中找到了this.mWebView.setWebViewClient(new cf(this));这么一句代码,进入cf类,发现下面代码:

    public boolean shouldOverrideUrlLoading(WebView webView, String url) {
        if (url.startsWith("orpheus://")) {
            RedirectActivity.a(this.activity, url);
            return true;
        }
        if ((url.toLowerCase().startsWith("http://")) || (url.toLowerCase().startsWith("https://"))) {
            return false;
        }
        try {
            this.activity.startActivity(new Intent("android.intent.action.VIEW", Uri.parse(url)));
            return true;
        } catch (ActivityNotFoundException localActivityNotFoundException) {
            localActivityNotFoundException.printStackTrace();
        }
        return true;
    }
    

    果然如此,再进入RedirectActivity,这是一个没有任何界面的Activity,专门用于处理页面跳转信息,它会调用一个方法NeteaseMusicUtils.redirect(this, getIntent().getData().toString(), false)来处理url,redirect方法的名字是我自己写的,部分代码如下:

    此处输入图片的描述

    可以看到orpheus://user/30868859中的用户id被传入了ProfileAcvitiy,因此启动了用户首页显示了用户信息

    然后我自己写了代码拦截Html 5的跳转,打印出的Log如下:

    此处输入图片的描述

    可以看到Html 5页面可以跳转到各种页面,比如用户首页、播放音乐、MV界面、评论页、电台节目等等

    总结

    一般来讲,也是我目前知道的两种主流的方式就是

    1. js调用Native中的代码
    2. Schema:WebView拦截页面跳转

    第2种方式实现起来很简单,但是一个致命的问题就是这种交互方式是单向的,Html 5无法实现回调。像云音乐App中这种点击跳转到具体页面的功能,Schema的方式确实可以简单实现,而且也非常适合。如果需求变得复杂,假如Html 5需要获取Native App中的用户信息,那么最好使用js调用的方式。

    js和Native进行交互

    上面讲到WebViewbe本身就是支持js调用Native代码的,不过WebView的这个功能在Android 4.2(API 17)一下存在高危的漏洞。这个漏洞的原理就是Android系统通过WebView.addJavascriptInterface(Object o, String interface)方法注册可供js调用的Java对象,但是系统并没有对注册的Java对象方法调用做限制。导致攻击者可以利用反射调用未注册的其他任何Java对象,攻击者可以根据客户端的能力做任何事情。这篇文章详细的介绍了这个漏洞

    出于安全考虑,Android 4.2以后的系统规定允许被js调用的Java方法必须以@JavascriptInterface进行注解

    Cordova的解决方案

    Cordova是一个广泛使用的Hybrid开发框架,它提供了一套js和Native交互规范

    在Cordova的SystemWebViewEngine类中可以看到

    private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
        if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
            Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
            // Bug being that Java Strings do not get converted to JS strings automatically.
            // This isn't hard to work-around on the JS side, but it's easier to just
            // use the prompt bridge instead.
            return;
        }
        webView.addJavascriptInterface(new SystemExposedJsApi(bridge), "_cordovaNative");
    }
    

    因此当Android系统高于4.2时,Cordova还是使用addJavascriptInterface这种方式,因为这个方法在高版本上安全而且简单,低于4.2的时候,用什么方法呢?

    答案是WebChromeClient.onJsPrompt方法

    WebView可以设置一个WebChromeClient对象,它可以处理js的3个方法

    • onJsAlert
    • onJsConfirm
    • onJsPrompt

    这3个方法分别对应js的alertconfirmprompt方法,因为只有prompt接收返回值,所以js调用一个Native方法后可以等待Native返回一个参数。下面是cordova.js中的一段代码:

    /**
    * Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
    * This is used pre-JellyBean, where addJavascriptInterface() is disabled.
    */
    module.exports = {
        exec: function(bridgeSecret, service, action, callbackId, argsJson) {
            return prompt(argsJson, 'gap:'+JSON.stringify([bridgeSecret, service, action, callbackId]));
        },
        setNativeToJsBridgeMode: function(bridgeSecret, value) {
            prompt(value, 'gap_bridge_mode:' + bridgeSecret);
        },
        retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
            return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
        }
    };
    

    然后只要在onJsPrompt方法中使用CordovaBridge来处理js的prompt调用

    /**
     * Tell the client to display a prompt dialog to the user. If the client returns true, WebView will assume that the client will handle the prompt dialog and call the appropriate JsPromptResult method.
     * <p/>
     * Since we are hacking prompts for our own purposes, we should not be using them for this purpose, perhaps we should hack console.log to do this instead!
     */
    @Override
    public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
        String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
        if (handledRet != null) {
            result.confirm(handledRet);
        } else {
            dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
                @Override
                public void gotResult(boolean success, String value) {
                    if (success) {
                        result.confirm(value);
                    } else {
                        result.cancel();
                    }
                }
            });
        }
        return true;
    }
    

    一种开源的解决方案

    Cordova是Apache的一个开源解决方案,不过它需要xml配置CordovaPlugin信息,使用会比较麻烦,而且这个框架很重,具体请自行搜索Cordova使用教程

    下面这个开源项目是我个人觉得比较合理的解决方案,也比较轻量级,下图就是一个Demo

    https://github.com/pedant/safe-java-js-webview-bridge

    此处输入图片的描述

    这个项目的原理就是使用WebChromeClient.onJsPrompt方法来进行交互,本质上都是js调用prompt函数,传输一些参数,onJsPrompt方法拦截到prompt动作,然后解析数据,最后调用相应的Native方法

    HostJsScope类中定义了所有可以被js调用的方法,这些方法都必须是静态方法,并且所有的方法第一个参数必须是WebView

    /**
    * HostJsScope中需要被JS调用的函数,必须定义成public static,且必须包含WebView这个参数
    */
    public class HostJsScope {
        /**
        * 短暂气泡提醒
        * @param webView 浏览器
        * @param message 提示信息
        * */
        public static void toast(WebView webView, String message) {
            Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show();
        }
    
        /**
        * 系统弹出提示框
        * @param webView 浏览器
        * @param message 提示信息
        * */
        public static void alert(WebView webView, String message) {
            // 构建一个Builder来显示网页中的alert对话框
            AlertDialog.Builder builder = new AlertDialog.Builder(webView.getContext());
            builder.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            });
            builder.setTitle("Hello world")
                .setMessage(message)
                .setCancelable(false)
                .create()
                .show();
        }
    
        // 其他代码
    
    }
    

    上面代码列举了最基本的点击Html 5按钮弹出对话框的功能

    这个库中一个最关键的叫做JsCallJava,这个实现的就是js来调用Java方法的功能,这个类只用于InjectedWebChromeClient

    public class InjectedChromeClient extends WebChromeClient {
    
        private JsCallJava mJsCallJava;
        private boolean mIsInjectedJS;
    
        public InjectedChromeClient(String injectedName, Class injectedCls) {
            this(new JsCallJava(injectedName, injectedCls));
        }
    
        public InjectedChromeClient(JsCallJava jsCallJava) {
            mJsCallJava = jsCallJava;
        }
    
        // 处理Alert事件
        @Override
        public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
            result.confirm();
            return true;
        }
    
        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            //为什么要在这里注入JS
            //1 OnPageStarted中注入有可能全局注入不成功,导致页面脚本上所有接口任何时候都不可用
            //2 OnPageFinished中注入,虽然最后都会全局注入成功,但是完成时间有可能太晚,当页面在初始化调用接口函数时会等待时间过长
            //3 在进度变化时注入,刚好可以在上面两个问题中得到一个折中处理
            //为什么是进度大于25%才进行注入,因为从测试看来只有进度大于这个数字页面才真正得到框架刷新加载,保证100%注入成功
            if (newProgress <= 25) {
                mIsInjectedJS = false;
            } else if (!mIsInjectedJS) {
                view.loadUrl(mJsCallJava.getPreloadInterfaceJS());
                mIsInjectedJS = true;
                StopWatch.log(" inject js interface completely on progress " + newProgress);
            }
            super.onProgressChanged(view, newProgress);
        }
    
        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            result.confirm(mJsCallJava.call(view, message));
            StopWatch.log("onJsPrompt: " + view.toString() +", " + url +", " + message +", " + defaultValue + ", " + result) ;
            return true;
        }
    }   
    

    这个InjectedWebChromeClient是设给WebView的,这里一个非常重要的细节需要注意一下,在onProgressChange方法中,向WebView注入了一段js代码,这段js代码如下:

    javascript: (function(b) {
        console.log("HostApp initialization begin");
        var a = {
            queue: [],
            callback: function() {
                var d = Array.prototype.slice.call(arguments, 0);
                var c = d.shift();
                var e = d.shift();
                this.queue[c].apply(this, d);
                if (!e) {
                    delete this.queue[c]
                }
            }
        };
        a.alert = a.alert = a.alert = a.delayJsCallBack = a.getIMSI = a.getOsSdk = a.goBack = a.overloadMethod = a.overloadMethod 
            = a.passJson2Java = a.passLongType = a.retBackPassJson = a.retJavaObject = a.testLossTime = a.toast = a.toast = function() {
            var f = Array.prototype.slice.call(arguments, 0);
            if (f.length < 1) {
                throw "HostApp call error, message:miss method name"
            }
            var e = [];
            for (var h = 1; h < f.length; h++) {
                var c = f[h];
                var j = typeof c;
                e[e.length] = j;
                if (j == "function") {
                    var d = a.queue.length;
                    a.queue[d] = c;
                    f[h] = d
                }
            }
            var g = JSON.parse(prompt(JSON.stringify({
                method: f.shift(),
                types: e,
                args: f
            })));
            if (g.code != 200) {
                throw "HostApp call error, code:" + g.code + ", message:" + g.result
            }
            return g.result
        };
        //有时候,我们希望在该方法执行前插入一些其他的行为用来检查当前状态或是监测
        //代码行为,这就要用到拦截(Interception)或者叫注入(Injection)技术了
        /**
         * Object.getOwnPropertyName 返回一个数组,内容是指定对象的所有属性
         *
         * 其后遍历这个数组,分别做以下处理:
         * 1. 备份原始属性;
         * 2. 检查属性是否为 function(即方法);
         * 3. 若是重新定义该方法,做你需要做的事情,之后 apply 原来的方法体。
         */
        Object.getOwnPropertyNames(a).forEach(function(d) {
            var c = a[d];
            if (typeof c === "function" && d !== "callback") {
                a[d] = function() {
                    return c.apply(a, [d].concat(Array.prototype.slice.call(arguments, 0)))
                }
            }
        });
        b.HostApp = a;
        console.log("HostApp initialization end")
    })(window);
    

    那么这段js代码是如何生成的呢?答案就在JsCallJava类的构造函数方法中,这个构造方法做的事情就是解析HostJsScope类中的方法,把每一个方法的签名都保持到private Map<String, Method> mMethodsMap中,再看上面那段js代码中

    a.alert = a.alert = a.alert = a.delayJsCallBack = a.getIMSI = a.getOsSdk = a.goBack = a.overloadMethod = a.overloadMethod
    = a.passJson2Java = a.passLongType = a.retBackPassJson = a.retJavaObject = a.testLossTime = a.toast = a.toast = function()

    这些都是HostJsScope类中定义的方法名

    那么这个库的整个执行流程是这样的:

    1. JsCallJava类解析了HostJsScope类中所有的静态方法,将它们放到一个Map中,并且生成一段js代码
    2. 向WebView设置InjectedChromeClient,在onProgressChanged方法中将那段js代码注入到Html5页面中,这个过程通俗点讲就是,Native告诉Html 5页面,我开放了什么功能给你,你就来调用我
    3. 这样js就可以调用Native提供的这些方法,那段js代码还会将js执行的方法转换成一段json字符串,通过js的prompt方法传到onJsPrompt方法中,JsCallJava调用call(WebView view, String msg)解析json字符串,包括要执行的方法名字参数类型方法参数,其中还会验证json中的方法参数类型和HostJsScope中同名方法参数类型是否一致等等。
    4. 最后,如果方法正确执行,call方法就返回一个json字符串code=200,否则就传code=500,这个信息会通过prompt方法的返回值传给js,这样Html 5 代码就能知道有没有正确执行了

    以上就是这个开源库的整个原理,我个人觉得非常适合用于Hybrid开发,这个解决方案中js可以收到Native的返回值,而且没有使用addJavascriptInterface方法,在低版本手机上也不会有安全问题,这个方法比Cordova的实现和配置简单

    那么当我点击Html 5页面上的一个按钮,比如弹出对话框,这个过程的整体流程是怎么样的呢

    微信的解决方案?

    什么?你问我微信是怎么解决的?我也反编译了微信的代码,想研究一下他们是解决的,其实我非常好奇微信的这种js 调用Native,并且又返回的调用方法

    首先,我去微信的js sdk官网看了一下js sdk提供的功能,提供了各种强大的功能,各位可以自己去看一下。那么问题来了,微信是怎么做到js 调用Native并且能够成功返回的呢?

    带着疑问我反编译了微信Android客户端,在assers/jsapi中看到了wxjs.js文件,我想这个就是微信js sdk的源码了吧。。。

    我首先说一下,我不太懂js的代码, 我只能连蒙带猜的看微信的js代码,如果有js大神对这方面也感兴趣,希望可以一起(jian)探(fei)讨(zao)

    wxjs.js中看到了一下代码,我猜微信就是用这个__WeixinJSBridge当时js和Native进行通信的数据结构吧?

    var __WeixinJSBridge = {
        // public
        invoke:_call,
        call:_call,
        on:_onfor3rd,
        env:_env,
        log:_log,
        // private
        // _test_start:_test_start,
        _fetchQueue: _fetchQueue,
        _handleMessageFromWeixin: _handleMessageFromWeixin,
        _hasInit: false,
        _continueSetResult: _continueSetResult
    };
    

    然后我又看到了下面的代码,我想应该是提供分享内容到朋友圈功能的吧

    // share timeline
    _on('menu:share:timeline',function(argv){
      _log('share timeline');
    
      var data;
      if (typeof argv.title === 'string') {
        data = argv;
        _call('shareTimeline',data);
      }else{
        data = {
            // "img_url": "",
            // "img_width": "",
            // "img_height": "",
            "link": document.documentURI || _session_data.init_url,
            "desc": document.documentURI || _session_data.init_url,
            "title": document.title
        };
    
        var shareFunc = function(_img){          
          if (_img) {
              data['img_url'] = _img.src;
              data['img_width'] = _img.width;
              data['img_height'] = _img.height;                        
          }
    
          _call('shareTimeline',data);
        };
    
        getSharePreviewImage(shareFunc);
      }
    });
    

    请注意最后这句:_call('shareTimeline',data);,在看看__WeixinJSBridge中的call属性,接着我找到了_call方法。

    function _call(func,params,callback) {
        var curFuncIdentifier = __WeixinJSBridge.call;
        if (curFuncIdentifier !== _callIdentifier) {
            return;
        }
        if (!func || typeof func !== 'string') {
            return;
        };
        if (typeof params !== 'object') {
            params = {};
        };
    
        var callbackID = (_callback_count++).toString();
    
        if (typeof callback === 'function') {
          _callback_map[callbackID] = callback;
        };
    
        var msgObj = {'func':func,'params':params};
        msgObj[_MESSAGE_TYPE] = 'call';        
        msgObj[_CALLBACK_ID] = callbackID;
    
        _sendMessage(JSON.stringify(msgObj));
    }
    

    大致意思应该就是:就是将这个东西_call('shareTimeline',data);转换成一个json字符串吧,从这里看到微信的做法和上面那个开源库非常类似,简单并且安全。_call方法最后调用_sendMessage方法发送消息

    //将消息添加到发送队列,iframe的准备队列为weixin://dispatch_message/
    function _sendMessage(message) {
        _sendMessageQueue.push(message);
        _readyMessageIframe.src = _CUSTOM_PROTOCOL_SCHEME + '://' + _QUEUE_HAS_MESSAGE;
    
        // var ifm = _WXJS('iframe#__WeixinJSBridgeIframe')[0];
        // if (!ifm) {
        //   ifm = _createQueueReadyIframe(document);
        // }
        // ifm.src = _CUSTOM_PROTOCOL_SCHEME + '://' + _QUEUE_HAS_MESSAGE;
    };
    

    从上面代码可以看到微信的js sdk也是将js的方法调用换成一个类似weixin://dispatch_message/这样的url,上面说的json封装的数据。那么我猜测微信的做法是类似网易云音乐的拦截url吗?如果真的是这样的话,就非常不安全了,随便一个Html 5页面可以伪造一个类似:weixin://dispatch_message/这样的url来调用微信的功能了,不过好在微信对每个js调用都必须带上appid。

    在反编译后的微信代码,我看到了下面代码:

    image

    我想这写就是微信想Html 5开放的接口吧?不过对比了一下微信js sdk的官网,我看到好多App提供的功能在js sdk中并没有找到,这样也没有太大关系,以为微信只要升级js sdk就可以使用其他功能了,因为Native已经开放了嘛~

    从上面__WeixinJSBridge可以看到有一个熟悉_handleMessageFromWeixin,这个就是js来处理Native的回调接口,我用这个字符串在微信代码中搜索,结果如下:

    image

    因此,我大致猜测,微信中的js调Native功能是用拦截url的方式,而Native回调的话是使用evaluateJavascript方法

    我也在js sdk中找到了相应的函数:

    function _handleMessageFromWeixin(message) {
        var curFuncIdentifier = __WeixinJSBridge._handleMessageFromWeixin;
        if (curFuncIdentifier !== _handleMessageIdentifier) {
            return '{}';
        }
    
        var ret;
        var msgWrap
        if (_isUseMd5 === 'yes') {
          var realMessage = message[_JSON_MESSAGE];
          var shaStr = message[_SHA_KEY];
          var arr = new Array;
          arr[0] = JSON.stringify(realMessage);
          arr[1] = _xxyy;
          var str = arr.join("");
          var msgSha = '';
            var shaObj = CryptoJS.SHA1(str);
            msgSha = shaObj.toString();
            if (msgSha !== shaStr) {
                _log('_handleMessageFromWeixin , shaStr : ' + shaStr + ' , str : ' + str + ' , msgSha : ' + msgSha);
                return '{}';
    
            }
            msgWrap = realMessage;
        }
        //省略很多代码
    

    微信的做法应该说非常基础,使用了原生的功能,但是安全,由于微信客户端对每一个js调用都有验证(appid),因此这也增加了一定的安全性

    以上说的都是建立在我的分析正确的情况下。

    一些个人的想法

    现在各种新的技术也在想办法解决Native开发的效率问题,想用技术来解决一套代码运行在Android和iOS客户端,我相信随着技术的发展这些问题都会解决。我也好期待Facebook即将推出的React Native Android

    Hybrid开发适用于哪些功能

    本文讲的Hybrid开发就是Native客户端中嵌入了Html App的功能,这方面微信应该是做的最好的,由于Html 5的效率以及耗电问题,我个人觉得用户是不能满足Web App的体验的,Hybrid App也只适用于某些场景。一些基础的功能,比如调用手机的摄像头,获取地理位置,登录注册功能等等,做成Native的功能,让Html 5来调用更好,这样的体验也更好。

    如果你把一个登录和注册功能也做成Html 5,在弱网络环境下,这个体验应该会非常的差,或许你等半天还没加载出页面。你可能会说,我可以预先加载Html 5的代码,打开App时直接加载,那么我说你在给自己找麻烦,如果要这样的话,Native开发或许更快一点。

    那么什么情况适合Html 5开发呢?像一些活动页面,比如秒杀、团购等适合做Html 5,因为这些页面可能涉及的非常炫而且复杂,Html 5开发或许会简单点,关键是这些页面时效性短,更新更快,因为一个活动说不定就一周时间,下周换活动,如果这样的话,你还做Native是肯定不行的

    总结

    有那么一句古老的箴言

    如果你手里有一把锤子,所有东西看上去都想钉子

    千万不要以为Hybrid开发能够夸平台运行,就使用Hybrid开发任何功能。其实Facebook早期也是这么想的,后来就是因为WebView渲染效率底下,把整个应用改为Native开发,请看这里

    引用Facebook的一段话:

    Today, we’re releasing a new version of Facebook for Android that’s been rebuilt in native code to improve speed and performance. To support the unique complexity of Facebook stories across devices, we’re moving from a hybrid native/webview to pure native code, allowing us to optimize the Facebook experience for faster loading, new user interfaces, disk cache, and so on.

    本文主要还是从技术上谈谈Hybrid开发中js和Native交互的技术实现原理。抛砖引玉,写的估计也有很多错的,希望技术大牛指出。

    最后,我觉得那个开源的库是一个非常不错的解决方案,解决办法巧妙、简单而且安全。当时我debug了半天弄明白其中的原理后,我一拍大腿,这办法真好啊!!网易云音乐的解决办法适用于它的场景,不需要回调,Native只需要处理相应的信息,然后来实现页面跳转、播放音乐、播放MV等功能,这个方法也简单好用。

    展开全文
  • 移动端混合开发

    千次阅读 2019-10-14 19:10:45
    到现在做移动混合开发一年多了,做了3个项目,也算是个老手了。虽然只干了一年,但花了很多时间研究,所以感觉至少有2年多的经验。 框架选择:综合上手难度、普及率、资料查询难度等选择了ionic(框架自带整合...

    到现在做移动混合开发一年多了,做了3个项目,也算是个老手了。虽然只干了一年,但花了很多时间研究,所以感觉至少有2年多的经验。

    框架选择:综合上手难度、普及率、资料查询难度等选择了ionic(框架自带整合angularjs+cordova)。官方地址:https://ionicframework.com/docs/

    个人觉得ionic做的页面非常美,没图你说个什么?下面先看看效果

    花了7天做的一个演示demo,地址是http://renrenxiuka.whty.com.cn/app/ICBC/index.html

    是不是被惊艳到了?

    ionic优点:

    1.节约成本、页面好维护

       这是混合开发的共同特点,原生开发ios开发ios的,android开发android的,即耗人力,页面做的也会有差异。混合开发一个人就可以开发跨平台且共用的页面

    2.控件多

      常用的控件官网上都可以查询到,使用方便,比如广告轮播、日期选择等控件。如果控件没有,也可以使用html+css+js的方式实现。如何引用自定义js?后面的博客我会写到。

    3.便捷的插件

     什么是插件?我的理解,插件就是一些封装好的原生功能,在页面上调用。

     ionic采用了cordova来调用原生的功能,自身提供了很多写好的插件,也可以自定义插件,非常方便。

     4.资料查询

      百度搜到关于ionic的资料寥寥无几,要在github上去搜索,在官网上也可查询。

      当需要调整某个控件的颜色或宽度时,在官网上查不到或者觉得慢怎么办?在浏览器上打开调试模式即可看到各种html元素和css样式,找到那个样式再修改即可。

     5.上手简单

      从一个html小白到上手只需几天的时间即可

     6.开发速度快

      用原生写一个list列表,要activity+adapter+viewholder+xml,明明很简单的东西要写一大堆代码,数据请求到了还要notify刷新页面

      现在recyleListview的item分割线甚至还要手动写代码。

      用ionic几分钟可以搞定,页面与ts文件(类似js)数据双向绑定

    ps:还有好多优点,比如丰富的图标库等等

     

    缺点

    1.兼容性

     这是所有混合开发的通病吧,毕竟是基于html

     如ion-list分割线某些机型不显示、某些机型不支持var关键字等,这些都要靠自己慢慢积累。

     2.好像没啥缺点了。。

     

    扫码关注我吧,获取学习视频

    展开全文
  • 前端混合开发总结

    千次阅读 2018-12-07 10:52:04
    名称 React Native Weex Flutter uni-app 支持 Facebook Alibaba Google ... 需针对iOS、Android编写2份代码(需要会Java,oc) 只需要编写一份代码,即可运行在Web、iOS、Android上 ...
  • 简单的混合开发经验

    2019-04-05 09:39:46
    Native App(原生app)主要是Android和iOS开发 Web App(前端开发) 一般说来都是需要用到网络的,它们利用设备上的浏览器(比如iPhone的Safari)来运行,而且它们不需要在设备上下载后安装。 Hybrid App(前端结合Android...
  • 混合开发(APP)

    2019-03-04 00:30:35
    1.由于混合开发,(登录app负责),那么在页面完成后对接接口的时候需要传一个token值,都知道token是登录时生成的,此时就需要APP将该值传给我,app大佬表示在url中有给传,截取token传值后token为null,绑到input...
  • Hybrid App 开发快速指南

    万次阅读 2018-08-14 00:42:57
    关于混合应用是什么、为什么使用的问题,相信大家都有自己的答案,但如何开发混合应用,从现有资料中却很难找到一个系统、全面的回答。 刚上手或准备上手混合应用的开发者,经常苦于没有一套经过验证的最佳实践来...
  • App混合开发(英文名:Hybrid App),是指在开发一款App产品的时候为了提高效率、节省成本即利用了原生的开发技术还应用了HTML5开发技术,是原生和HTML5技术的混合应用。目前App的开发主要包含三种方式:原生开发、...
  • 不过对于移动开发者来说更关心的问题是如何低成本、周期短开发出体验效果好的App,所以当下用html5远比赌html5更现实,因此跨平台开发至关重要...今天小编就给大家推荐5个好用的html5混合式App开发工具。1、Appceler...
  • Vue+原生App混合开发

    千次阅读 2019-01-25 15:58:32
    转载一篇 Vue+原生App混合开发 源地址 : https://www.cnblogs.com/undefined000/p/vue-nativeApp-development.html  
  • vue与原生混合开发

    万次阅读 2018-07-06 10:09:08
    前段时间,做了一个混合开发的项目,主要是以vue框架开发h5页面,使用cordova作为中间沟通桥梁,实现了h5与安卓、iOS的混合开发,由于从事iOS开发,h5也是刚接触不久,很多深入原理还不太清楚,只说下我们这个个项目...
  • 移动开发-混合App介绍

    万次阅读 2018-06-25 08:56:38
    3种开发类型的原理和对比 什么是混合App(Hybrid App) Hybrid App是指介于web-app(网页APP,如京东web)、native-app(原生应用,如手机上面的APP应用)这两者之间的app,它虽然看上去是一个Native App,但...
  • 移动端app开发,原生开发与混合开发的区别

    千次阅读 多人点赞 2019-09-26 18:47:16
    目前市场上主流的APP分为三种:原生APP、Web APP(即HTML5)和混合APP三种,相对应的定制开发就是原生开发、H5开发和混合开发。那么这三种开发模式究竟有何不同呢?下面我们就分别从这三者各自的优劣势来区分比较吧...
  • 移动端的混合开发是什么意思?我是学H5的我想知道,我们H5做的移动端的页面如何转化成APP?
  • Web应用本质上是为移动浏览器设计的基于Web的应用,它们是用普通Web开发语言开发的,可以在各种智能手机浏览器上运行。 优点: 支持设备广泛;较低的开发成本;可即时上线;无内容限制;用户可以直接使用最新...
  • 最简单的混合开发教程:APICloud.

    千次阅读 2016-09-26 00:25:44
    现在react-native越来越火了,阿里也推出了weex,微信的应用号也开始内测了,未来的世界将会是混合开发的天下。 所以现在还没有接触过混合开发的前端朋友们就要看过来了,这是个最简单的混合开发框架 。 那些已经在做...
  • android h5 js 混合开发解决webview加载白屏,不加载网页的问题(硬件加速网页渲染,优化体验)
  • 5个好用的混合式App开发工具

    千次阅读 2015-09-08 17:11:10
    在残酷的移动互联网竞争环境下, HTML5技术一直受到各方关注,“HTML5颠覆原生 App”的争论也从未...于是,一个一直被提及但是从没有占据过统治地位的概念又一次走进了移动开发者们的视野,那就是跨平台开发。 AD:
  • App混合开发(英文名:Hybrid App),是指在开发一款App产品的时候为了提高效率、节省成本即利用了原生的开发技术还应用了HTML5开发技术,是原生和HTML5技术的混合应用。目前App的开发主要包含三种方式:原生开发、...
1 2 3 4 5 ... 20
收藏数 234,064
精华内容 93,625
关键字:

混合开发