JavaScriptCore 框架详细解析(四) —— 工程实践之H5调用原生的jsbridge实践(一)

版本记录

版本号 时间
V1.0 2018.07.10

前言

JavaScriptCore是用来评估应用程序中的JavaScript程序,并支持应用程序的JavaScript脚本编写。接下来这几篇我们就详细的解析一下JavaScriptCore框架的使用情况。感兴趣的可以看上面写的那篇。
1. JavaScriptCore 框架详细解析(一) —— 基本概要
2. JavaScriptCore 框架详细解析(二) —— JS与OC通信
3. JavaScriptCore 框架详细解析(三) —— 内存管理与线程安全

应用场景

在做项目的时候,有时候全部用H5做不是很好,如果用H5做有很多交互不友好的地方,这个时候我们就需要用原生的去完成。所以,我们就需要监听H5中的事件(比如说点击了H5中的按钮),去完成下面的操作,其实就相当于H5调用了你的原生。

H5调用原生的方法可以在原生中的进行其他操作,这就自然而然的相当于将事件处理权由H5过渡到了原生。其中我们做直播的一个应用场景,就是在直播间中半屏H5活动页面,单击H5中的按钮,然后Push到一个全屏的页面,这个Push操作就需要在点击H5页面后去做,其实就是相当于H5调用了我们的原生完成了后面的操作。


实现流程

1. 定义函数

移动端和前端需要一起定义一个函数,函数名称相同,iOS和Android还有前端都是用这个函数,其实就是以后H5调用原生移动端的一个入口。比如我们定义的就是下面这个函数(或称方法)。

- (void)onJsCallback:(NSString *)json

传递的参数是json格式的,H5调用原生的时候就会将参数以json格式传递给移动端,移动端解析出来,获取到数据,对数据进行处理,把处理权掌握在自己手里,就达到了前端H5和移动端的通信。

2. 封装JS引擎类

其实这一步骤可以不做,可以直接在Webview类里面做处理,但是我这里单独提出来,其实是有原因的,如果都放在webview里面,一是会显的很乱;二是耦合太严重逻辑不清晰,所以这里封装了一个JS引擎单例类。

1. JJJSEngine.h
#import 
#import 

@protocol JJJSExport 

//H5调用native
- (void)onJsCallback:(NSString *)json;

@end

@interface JJJSEngine : NSObject 

@property (nonatomic, weak) UIWebView *webview;
@property (nonatomic, weak) JSContext *jsContext;
@property (nonatomic, strong) NSDictionary *moreDict;

+ (instancetype)shared;

//清理
- (void)cleanUp;

//native向js发送消息
- (void)sendJSMessage:(NSString *)content;

@end
2. JJJSEngine.m
#import "JJJSEngine.h"
#import "JSONHelp.h"
#import 


@interface JJJSEngine()

@property (nonatomic, strong) dispatch_queue_t jsQueue;
@property (nonatomic, strong) NSMutableDictionary *jsCallbacksDictM;

@end

@implementation JJJSEngine

#pragma mark -  Override Base Function

- (instancetype)init
{
    self = [super init];
    if (self) {
        _jsQueue = dispatch_queue_create("com.maobotv.jsbridge", DISPATCH_QUEUE_SERIAL);
        _jsCallbacksDictM = [NSMutableDictionary dictionaryWithCapacity:5];
    }
    return self;
}

#pragma mark -  Object Private Function

//发送JS消息
- (void)sendJSMessageName:(NSString *)callback response:(NSDictionary *)response
{
    if ([callback isNotEmpty] == NO || self.webview == nil) {
        return;
    }
    
    NSString *responseStr = [JSONHelp object2NSString:response];
    responseStr = responseStr ? : @"";
    responseStr = [responseStr stringByReplacingOccurrencesOfString:@"'" withString:@"\'"];
    NSString *jsCallStr = [NSString stringWithFormat: @"%@(\'%@\');", callback, responseStr];
    dispatch_async(dispatch_get_main_queue(), ^{
        @try {
            NSString *result = [self.webview stringByEvaluatingJavaScriptFromString:jsCallStr];
            if (result == nil) {
                DDLogError(@"evaluate script fail :%@",jsCallStr);
            }
        } @catch (NSException *exception) {
            DDLogError(@"webview evaluate error :%@",[exception reason]);
        }
    });
}

#pragma mark -  Object Public Function

- (void)cleanUp
{
    [self.jsCallbacksDictM removeAllObjects];
}

//native向js发送消息
- (void)sendJSMessage:(NSString *)content
{
    //字符串转对象
    NSDictionary *param = [JSONHelp string2NSObject:content];
    //这个JJJSUtil是工具类,用于一些常用工具处理
    NSDictionary *response = [JJJSUtil jsResponseWithRet:0 errorMsg:nil param:param];
    [self sendJSMessageName:self.jsCallbacksDictM[@"自定义的字符串key"] response:response];
}

#pragma mark -  Class Public Function

+ (instancetype)shared
{
    static JJJSEngine *object;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        object = [[self alloc] init];
    });
    return object;
}

#pragma mark -  JJJSExport

//H5调用native
- (void)onJsCallback:(NSString *)json
{
    IMP_WSELF()
    dispatch_async(_jsQueue, ^{
        IMP_SSELF()
        if (sself == nil) {
            return;
        }
        id jsonObj = [JSONHelp string2NSObject:json];
        if ([jsonObj isKindOfClass: [NSDictionary class]]) {
            NSDictionary *dict = (NSDictionary *)jsonObj;
            NSString *actionTypeStr = _To_Str(dict[@"action"]);
            NSDictionary *dataDict = _To_Dict(dict[@"data"]);
            NSString *contentURL = _To_Str(dataDict[@"url"]);
            
            dispatch_async(dispatch_get_main_queue(), ^{
                //全屏的H5
                if ([actionTypeStr isEqualToString:@"toFullPage"]) {
                    JJWebViewVC *webVC = [[JJWebViewVC alloc] init];
                    extern BOOL isTestCondition;
                    webVC.urlStr = contentURL;
                    self.moreDict = _To_Dict(dataDict[@"more"]);
                    webVC.isOpenGuardH5 = YES;
                    webVC.feed = self.feed;
                    webVC.view.backgroundColor = [UIColor colorWithRed:35/255.0 green:27/255.0 blue:54/255.0 alpha:1.0];
                    [JJCurrentNaviController pushViewController:webVC animated:YES];
                }
                //返回上一页
                else if([actionTypeStr isEqualToString:@"wvBack"]){
                    if ([self.webview canGoBack]) {
                        [self.webview goBack];
                    }
                    else {
                        [JJCurrentNaviController popViewControllerAnimated:YES];
                    }
                }
                //展示迷你卡
                else if([actionTypeStr isEqualToString:@"showCard"]){
                    NSString *uid = _To_Str(dataDict[@"uid"]);
                    [[NSNotificationCenter defaultCenter] postNotificationName:kNotification_show_user_info_card object:uid];
                }
            });
            
        }
    });
}

@end

上面的类JJJSUtil是一个工具类,用于存放一些基本的逻辑处理,比如

//判断url的前缀是否相等(?之前)
+ (BOOL)isH5URLPrefixEqual:(NSString *)url1 url2:(NSString *)url2;
//判断url的前缀是否相等(?之前)
+ (BOOL)isH5URLPrefixEqual:(NSString *)url1 url2:(NSString *)url2
{
    if (url1 == nil || url2 == nil) {
        return NO;
    }
    
    NSRange range1 = [url1 rangeOfString:@"?" options:NSBackwardsSearch];
    if (range1.location != NSNotFound) {
        url1 = [url1 substringToIndex:range1.location];
    }
    
    NSRange range2 = [url2 rangeOfString:@"?" options:NSBackwardsSearch];
    if (range2.location != NSNotFound) {
        url2 = [url2 substringToIndex:range2.location];
    }
    
    return [url1 isEqualToString:url2];
}

3. webview类中的处理

前面我们已经写好了jsbridge引擎,下面我们要在webview中做一些事情,这样才能将我们定义的引擎和webview中的H5连接起来,让路“通”起来。

#import "JJJSEngine.h"

@property (nonatomic, strong) JJJSEngine *jsEngine;

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self createWebView];

    [JJJSEngine shared].webview = self.webView;
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    self.jsContext[@"JSCallInterface"] = [JJJSEngine shared];
}

上面self.jsContext[@"JSCallInterface"] = [JJJSEngine shared];JSCallInterface是告诉jsbridge要找那个对象去处理,这个字符串也是和前段定义好的了。

下面接着在代理方法中继续处理

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    _isLoadingFinished = YES;
    [self.loadingView removeLoadingView];
    self.loadingView = nil;
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    self.jsContext[@"JSCallInterface"] = [JJJSEngine shared];
    [JJJSEngine shared].jsContext = self.jsContext;
    [JJJSEngine shared].webview = self.webView;

    // 增加jsBridge异常的处理
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        CPLog(@"jsbridge - 异常信息:%@", exceptionValue);
    };
}

最后别忘记在dealloc中进行销毁处理

- (void)dealloc
{
    [[JJJSEngine shared] cleanUp];
    [JJJSEngine shared].webview = nil;
    
    [self.webView stopLoading];
    self.webView.delegate = nil;
    [self.webView removeFromSuperview];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

4. 场景验证

现在有一个需求,比如说直播间的半屏的H5页面,要求我们点击某一个按钮,我们push到一个全屏的H5页面,按钮点击一定是H5的东西,要求的就是通过jsbridge让原生对点击事件进行响应,具体跳转到哪个界面,都是通过上面一起定义的函数的json数据进行传递的。

JavaScriptCore 框架详细解析(四) —— 工程实践之H5调用原生的jsbridge实践(一)_第1张图片
半屏H5页面
JavaScriptCore 框架详细解析(四) —— 工程实践之H5调用原生的jsbridge实践(一)_第2张图片
跳转到全屏H5的独立页面

后记

本篇主要讲述了jsbridge的集成,感兴趣的给个赞或者关注~~~~

JavaScriptCore 框架详细解析(四) —— 工程实践之H5调用原生的jsbridge实践(一)_第3张图片

你可能感兴趣的:(JavaScriptCore 框架详细解析(四) —— 工程实践之H5调用原生的jsbridge实践(一))