30

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

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI0MTcwNDcyMw%3D%3D&%3Bmid=2247484266&%3Bidx=1&%3Bsn=00cc1e2e321a952abdf5b9dd3cea903e
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文转载自: https://www.jianshu.com/p/0af14da1fbc6。 作者: 莫云溪。

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:(NSInt

加锁

每次加锁成功会返回一个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了

Fn6j6re.gif如果感觉这篇文章不错可以点击在看:point_down:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK