美文网首页
iOS Android与JS交互通用解决方案

iOS Android与JS交互通用解决方案

作者: 东了个尼 | 来源:发表于2018-10-10 10:02 被阅读0次

    最近公司有一个新需求,安卓和iOS使用webview加载HTMl展示账单模块的页面,由于没有JS端没有混合开发的经验,所以无法适配安卓和IOS。所以iOS这边使用的 WebViewJavascriptBridge。JS端无法获取处理回调。没办法,最后安卓和iOS只能被动的去适配JS端。无奈舍弃性能使用UIWebview和JSContext,实现了对应的需求。

    JSContext简介:

    JSContext是JavaScriptCore框架中的东西。JavaScriptCore是iOS7中新加入
    的框架,用来处理JavaScript。JavaScriptCore 是苹果 Safari 浏览器的 JavaScript 引擎,
    JavaScriptCore在 OS X 平台上很早就存在的,而在 iOS 平台,直到IOS7才对外开放,
    并提供了 Objective-C 的接口。该框架让Objective-C和JavaScript代码直接的交互变得更加的简单方便。
    简单的一句话:苹果官方提供的JSContext可以实现Objective-C和JavaScript代码的交互。
    

    JSContext在UIWebView中可以拿到,但是在WKWebView中却是取不到了,所以只能用在UIWebView中。除此以外Android里也有类似的一个东西,所以使用JSContext就有了在JS端多平台统一的可能。
    JSContext的原理就是iOS暴露出去一个遵守<JSExport>协议的对象给JS,JS可以直接调用该对象的public方法。

    JSContext的基本使用

    1.使用JSContext 首先需要在项目中导入JavaScriptCorek框架。

    JSContext: 代表JavaScript的执行环境。你可以创建JSContent在OC环境中执行JavaScript脚本,同时也可以在JavaScript脚本中访问OC中的值或者方法。

    JSValue:是OC和JavaScript值相互转化的桥梁。他提供了很多方法把OC和JavaScript的数据类型进行相互转化。其一一对应关系如下表所示:

    1479346865178641.png

    JSManagedValue:JSValue的包装类。JS和OC对象的内存管理辅助对象。由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。

    JSVirtualMachine: JS运行的虚拟机。可以支持并行的JavaScript执行,管理JavaScript和OC转换中的内存管理。

    JSExport:一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。

    下面我们通过实例案例来学习JSCore的用法。

    二、OC中调用JS方法

    案例一:我在js中定义了一个函数add(a,b),我们需要在OC中进行调用。

    -(void)OCCallJS{
        self.context = [[JSContext alloc] init];
     
        NSString *js = @"function add(a,b) {return a+b}";
        [self.context evaluateScript:js];
        JSValue *addJS = self.context[@"add"];
     
        JSValue *sum = [addJS callWithArguments:@[@(10),@(17)]];
        NSInteger intSum = [sum toInt32];
        NSLog(@"intSum: %zi",intSum);
    }
    

    三、JS中调用OC方法
    JS中调用OC有两种方法,第一种为block调用,第二种为JSExport protocol。

    案例二:我们在OC中定义了一个如下方法,我们需要在JS中对它进行调用

    -(NSInteger)add:(NSInteger)a and:(NSInteger)b{    
        return  a+b;
    }
    
    3.1、block调用
    -(void)JSCallOC_block{
        self.context = [[JSContext alloc] init];
     
        __weak typeof(self) weakSelf = self;
        self.context[@"add"] = ^NSInteger(NSInteger a, NSInteger b){
            return [weakSelf add:a and:b];
        };
        JSValue *sum = [self.context evaluateScript:@"add(4,5)"];
        NSInteger intSum = [sum toInt32];
        NSLog(@"intSum: %zi",intSum);
    }
    
    3.2、JSExport protocol
    
    第一步:定义一个遵守JSExport的AddJSExport协议。
    @protocol AddJSExport <jsexport>
    //用宏转换下,将JS函数名字指定为add;
    JSExportAs(add, - (NSInteger)add:(NSInteger)a and:(NSInteger)b);
    @property (nonatomic, assign) NSInteger sum;
    @end</jsexport>
    
    第二步:新建一个对象AddJSExportObj,去实现以上协议。
    
    AddJSExportObj.h
    @interface AddJSExportObj : NSObject<addjsexport>
    @property (nonatomic, assign) NSInteger sum;
    @end
    AddJSExportObj.m
    @implementation AddJSExportObj
    -(NSInteger)add:(NSInteger)a and:(NSInteger)b{
        return a+b;
    }
    @end</addjsexport>
    
    第三步:在VC中进行JS调用
    
    -(void)JSCallOC_JSExport{
        self.context = [[JSContext alloc] init];
     
        //异常处理
        self.context.exceptionHandler = ^(JSContext *context, JSValue *exception){
            [JSContext currentContext].exception = exception;
            NSLog(@"exception:%@",exception);
        };
     
        self.addObj = [[AddJSExportObj alloc] init];
     
        self.context[@"OCAddObj"] = self.addObj;//js中的OCAddObj对象==>OC中的AddJSExportObj对象
        [self.context evaluateScript:@"OCAddObj.sum = OCAddObj.add(2,30)"];
        NSLog(@"%zi",self.addObj.sum);
    }
    

    四、一个从服务端下发JS脚本,执行本地方法的实现思路

    案例三:本地定义了一系列方法,可以通过服务端下发js脚本去控制具体去执行那些方法。这样就可以在远端实现对于客户端的控制。

    第一步:预置本地方法

    -(void)initJS{
        __weak typeof(self) weakSelf = self;
        self.context[@"execute1"] = ^(){
            [weakSelf execute1];
        };
        self.context[@"execute2"] = ^(){
            [weakSelf execute2];
        };
    }
    -(void)execute1{
        NSLog(@"execute1");
    }
     
    -(void)execute2{
        NSLog(@"execute2");
    }
    
    第二步:服务端下发脚本
    
    -(NSString *)getJS{    
        //可以从服务端下发
        //return @"execute1()";
        return @"execute2()";
    }
    第三步:根据服务端下发脚本执行
    
    
    -(void)executeByJs{
        [self initJS];    
        NSString *js = [self getJS];
        [self.context evaluateScript:js];
    }
    

    五、JSCore在Web容器中的使用

    在UIWebView中,
    我们可以在- (void)webViewDidFinishLoad:(UIWebView *)webView方法中,
    通过KVC的方式获取到当前容器的JSContent对象,通过该对象,我们就可以方便的进行hybrid操作。

    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    

    案例演示:在html中调研OC代码中的分享功能和调用相机功能。

    第一步:HelloWord.html代码如下:
    
    function jsCallNative(){
        WBBridge.callCamera();
    }
    function jsCallNative2(){
        var shareInfo = "分享内容";
        var str = WBBridge.share(shareInfo);
        alert(str);
    }
     
    <input type="button" onclick="jscallnative()" value="jscallnative" >
    <br>
    <input type="button" onclick="jscallnative2()" value="jscallnative2" >
    <br></input type="button" onclick="jscallnative2()" value="jscallnative2" ></input type="button" onclick="jscallnative()" value="jscallnative" >
    
    
    第二步:实现一个遵守JSExport的协议WebViewJSExport
    @protocol WebViewJSExport <jsexport>
    - (void)callCamera;
    - (NSString*)share:(NSString*)shareString;
    @end</jsexport>
    

    五、JSCore在Web容器中的使用

    在UIWebView中,我们可以在- (void)webViewDidFinishLoad:(UIWebView *)webView方法中,通过KVC的方式获取到当前容器的JSContent对象,通过该对象,我们就可以方便的进行hybrid操作。

    1.首先在控制器中创建一个UIWebview,遵循代理。加载对应的链接

    - (UIWebView *)webView{
        if (!_webView) {
            _webView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
            _webView.delegate = self;
        }
        return _webView;
    }
    
    - (void)viewDidLoad {
         self.view =self.webView;
        NSString *requestURl = [NSString stringWithFormat:@"%@%@",BASE_URL,BillList];
        NSURL *url = [NSURL URLWithString:requestURl];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [self.webView loadRequest:request];
    }
    
    
    2.定义一个AppJSObject模型 实现一个遵守JSExport的协议AppJSObjectDelegate
    
    
    #import <Foundation/Foundation.h>
    #import <Foundation/Foundation.h>
    #import <JavaScriptCore/JavaScriptCore.h>
    @protocol AppJSObjectDelegate <JSExport>
    //-(NSDictionary *)getLoginUser:(NSString *)str;
    //- (NSDictionary *)getName:(NSString *)str;
    //获取语言环境
    - (NSString *)getSysLanguage;
    //获取登录id
    - (NSString *)getLoginId;
    //获取IP
    - (NSString *)getBaseIp;
    //获取ST
    - (NSString *)getST;
    //获取AT
    - (NSString *)getAT;
    //获取默认账户
    - (NSString *)getCurConsNo;
    
    /*
     * 登录方法,JSExportAs的作用就是给OC方法导出一个js方法名,例如下面的方法js调用就是 login("your account", "your password")。在多参数的方法声明时必须使用这种方式
     */
    //JSExportAs(login, - (void)loginWithAccount:(NSString *)account password:(NSString *)password);
    
    //获取签名
    JSExportAs (signParam,- (NSString *)signParam:(id)paramJson);
    //设置标题
    JSExportAs (setTitle,- (void)setTitle:(NSString *)title);
    
    @end
    @interface AppJSObject : NSObject<AppJSObjectDelegate>
    
    @end
    
    实现对应的方法,那么在js中调用方法就会调用到原生AppJSObject实例对象中对应的方法了。
    //获取登录id
    - (NSString *)getLoginId{
        NSString *loginAccount = [[UserPreferenceManager defaultManager] getUserLoginAccount];
        return loginAccount;
    }   
    //获取IP
    - (NSString *)getBaseIp{
        return BASE_URL;
    }
    //获取ST
    - (NSString *)getST{
        NSString *ST = [[UserPreferenceManager defaultManager] getUserStstring];
        return ST;
    }
    //获取AT
    - (NSString *)getAT{
        NSString *AT = [[UserPreferenceManager defaultManager] getAT];
    
        return AT;
    }
    //获取默认账户
    - (NSString *)getCurConsNo{
        NSString *cons_No = [[UserPreferenceManager defaultManager] getDefaultAccount];
        return cons_No;
    }
    

    3.在UIWebview的代理方法中

    // 每次嵌入页面加载完毕都要给jsContext赋值,否则在js端调用可能会失效。
    (在UIWebView加载完成的代理中把AppJSObject实例对象类注入到JS中,那么在js中调用方法就会调用到原生AppJSObject实例对象中对应的方法了。
    
    )
    
    -(void)webViewDidFinishLoad:(UIWebView *)webView
    {
        self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
            context.exception = exception;
            NSLog(@"异常信息:%@", exception);
        };
      // ami是随便取的名字,可以改,改了之后JS要同步修改。如果Android端使用@JavaScriptInterface的形式,那么还要保证Android、iOS两端同步,建议都用app
    
        self.jsContext[@"ami"] = [[AppJSObject alloc] init];
        //关闭缓存
        [[NSUserDefaults standardUserDefaults] setInteger:0 forKey:@"WebKitCacheModelPreferenceKey"];
        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"WebKitDiskImageCacheEnabled"];
        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"WebKitOfflineWebApplicationCacheEnabled"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
    

    另外在项目一中还出现了一个问题
    webview加载http是可以的,但加载https类的网址就出现了白屏现象,怎么解决呢,现在就来说一下。

    1.首先找到appdelegate.m文件,找到
    @end

    2.在@end下面敲入如下代码。

    @implementation NSURLRequest(DataController)
    + (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
    {
        return YES;
    }
    @end
    

    参考文章
    https://www.cnblogs.com/oc-bowen/p/6092730.html
    https://www.jianshu.com/p/13b65557c27f

    相关文章

      网友评论

          本文标题:iOS Android与JS交互通用解决方案

          本文链接:https://www.haomeiwen.com/subject/mqheaftx.html