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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: UIWebView没有提供设置UserAgent的接口,但是有一个办法可以间接的设置。NSDictionary* dict = [[NSDictionary alloc] initWithObjectsAndKeys:value, @"UserAge...

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了

目录
相关文章
|
10天前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
26 0
|
10天前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
25 0
|
10天前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
21 0
|
8天前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
29 5
|
10天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
10天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
10天前
|
算法 Java 程序员
Map - TreeSet & TreeMap 源码解析
Map - TreeSet & TreeMap 源码解析
20 0
|
10天前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
24 0
|
1月前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
108 29
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
1月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
307 37

推荐镜像

更多