美文网首页程序员
Cordova源码解析(二)- 自定义UserAgent

Cordova源码解析(二)- 自定义UserAgent

作者: 莫云溪 | 来源:发表于2018-05-15 19:28 被阅读0次

    UIWebView没有提供设置UserAgent的接口,但是有一个办法可以间接的设置。

    NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:value, @"UserAgent", nil];
    [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
    

    通过设置NSUserDefaultsUserAgent的值来修改,但是这种设置方法有一个限制,需要在UIWebViewloadRequest之前调用才能生效(加载PDF比较特殊)。这是Cordova源码中关于这个问题的描述

    Setting the UserAgent must occur before a UIWebView is instantiated.
    It is read per instantiation, so it does not affect previously created views.
    Except! When a PDF is loaded, all currently active UIWebViews reload their
    User-Agent from the NSUserDefaults some time after the DidFinishLoad of the PDF bah!

    CDVUserAgentUtil

    在多WebView的情况下,如果每个WebView都有不同的UserAgent,就会产生数据竞争的问题,大家都要修改NSUserDefaultsUserAgent的值,于是需要对资源加锁来保证每个WebView都设置预期的UserAgent。在Cordova中,专门有一个类CDVUserAgentUtil来实现这个功能。

    CDVUserAgentUtil.h文件中定义了四个方法

    // 获取UIWebView默认的UserAgent
    + (NSString*)originalUserAgent;
    // 获取锁
    + (void)acquireLock:(void (^)(NSInteger lockToken))block;
    // 释放锁
    + (void)releaseLock:(NSInteger*)lockToken;
    // 设置UIWebView的UserAgent
    + (void)setUserAgent:(NSString*)value lockToken:(NSInteger)lockToken;
    

    加锁

    每次加锁成功会返回一个NSInteger类型的token,在释放锁的时候需要把token传入。token会不断递增,保证每次加锁返回的token都不回重复。加锁的实现代码如下:

    // CDVUserAgentUtil.m
    + (void)acquireLock:(void (^)(NSInteger lockToken))block
    {
        if (gCurrentLockToken == 0) {
            gCurrentLockToken = ++gNextLockToken;
            VerboseLog(@"Gave lock %d", gCurrentLockToken);
            block(gCurrentLockToken);
        } else {
            if (gPendingSetUserAgentBlocks == nil) {
                gPendingSetUserAgentBlocks = [[NSMutableArray alloc] initWithCapacity:4];
            }
            VerboseLog(@"Waiting for lock");
            [gPendingSetUserAgentBlocks addObject:block];
        }
    }
    

    调用acquireLock:,首先会判断gCurrentLockToken是否等于0

    • 如果是0说明没有模块正在修改UserAgent,能够成功获取到锁,gCurrentLockToken递增,标致当前有模块正在修改UserAgent,并回调block,返回gCurrentLockToken
    • 如果不为0说明当前有模块正在修改UserAgent,将block回调存在一个队列gPendingSetUserAgentBlocks

    释放锁

    释放锁需要传入token,释放锁代码如下:

    + (void)releaseLock:(NSInteger*)lockToken
    {
        if (*lockToken == 0) {
            return;
        }
        NSAssert(gCurrentLockToken == *lockToken, @"Got token %ld, expected %ld", (long)*lockToken, (long)gCurrentLockToken);
    
        VerboseLog(@"Released lock %d", *lockToken);
        if ([gPendingSetUserAgentBlocks count] > 0) {
            void (^block)() = [gPendingSetUserAgentBlocks objectAtIndex:0];
            [gPendingSetUserAgentBlocks removeObjectAtIndex:0];
            gCurrentLockToken = ++gNextLockToken;
            NSLog(@"Gave lock %ld", (long)gCurrentLockToken);
            block(gCurrentLockToken);
        } else {
            gCurrentLockToken = 0;
        }
        *lockToken = 0;
    }
    
    • 如果要释放的lockToken为0,说明还没加过锁,就调用释放了,直接返回
    • 从队列gPendingSetUserAgentBlocks中取出最早加入的block,从队列中移除
    • gCurrentLockToken递增生成新token,回调block
    • 如果队列gPendingSetUserAgentBlocks释放完成,说明释放锁的调用次数>加锁的次数,不做操作,然后把gCurrentLockToken置为0

    设置UserAgent

    在Cordova实际运用中,操作锁的时机:
    加锁时机:CDVViewController加载完毕,在viewDidLoad里调用
    释放锁时机:

    • UIWebViewwebViewDidFinishLoad:回调
    • UIWebViewwebView:didFailLoadWithError:回调
    • CDVViewControllerdealloc
    • CDVViewControllerviewDidUnload

    加锁代码,省略了不相关代码

    // CDVViewController.m
    - (void)viewDidLoad
    {
        [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
            _userAgentLockToken = lockToken;
            [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken];
            NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
            [self.webViewEngine loadRequest:appReq];
        }];
    }
    

    释放锁代码,这里只看正常逻辑,在网页加载完成回调webViewDidFinishLoad:中释放逻辑。不考虑异常情况,省略了不相关代码。

    // CDVUIWebViewNavigationDelegate.m
    - (void)webViewDidFinishLoad:(UIWebView*)theWebView
    {
        NSLog(@"Finished load of: %@", theWebView.request.URL);
        CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
    
        // It's safe to release the lock even if this is just a sub-frame that's finished loading.
        [CDVUserAgentUtil releaseLock:vc.userAgentLockToken];
    }
    

    webViewDidFinishLoad:回调时,UserAgent已经设置成功,所以可以释放锁,让其它WebView操作UserDefault了


    欢迎关注我的博客

    相关文章

      网友评论

        本文标题:Cordova源码解析(二)- 自定义UserAgent

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