前言
客户端界面嵌了 H5,做了混合开发。点击原生的按钮跳到了一个 WebView,再点击 H5 里某个按钮又要可以跳回原生界面。由于 H5 的页面已经在公众号正常运营,需要判断当前打开页面的环境,如果是 App,JS 的点击事件改为调用原生。最后我们采用修改 UserAgent 来做标识。
定义
User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等
打开火狐页面开发调试command + shift + g 如图所示:
获取UserAgent
UIWebView 和 WKWebView 与 JS 交互的方法有点区别,UIWebView 是同步的,而 WKWebView 是异步的。
- UIWebView :
NSString *oldUserAgent = [[[UIWebView alloc]init] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
- WKWebView:
[self evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
NSString *oldUserAgent = result;
}];
默认UserAgent
以下是我的模拟器 iPhone 11s Pro Max,iOS 13.3.3 获取到的UserAgent。
Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148/requestByNative
无论使用 UIWebView 方式还是 WKWebView 方式,获取到的结果是一样的。也就是说,获取 UserAgent 不区分
webView 是哪个控件哪个内核。
修改全局UserAgent值(这里是在原有基础上拼接自定义的字符串)
- UIWebView :
1.如果想要统一自定义 UserAgent 让所有的 webView 访问网页时都生效,可以在 App 启动的时候,修改全局 UserAgent。
2.还可以用单利只设置一次
+ (void)registUserAgent {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIWebView *webView = [[UIWebView alloc]initWithFrame:CGRectZero];
//修改UserAgent
NSString *oldUserAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
//自定义需要拼接的字
NSString *newUserAgent = [oldUserAgent stringByAppendingString:@"xxx"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newUserAgent, nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
[[NSUserDefaults standardUserDefaults] synchronize];
});
}
userAgent 为默认 oldUserAgent。
newUserAgent 首先拼接了XXX ,标识比如,版本号,功能类别
- WKWebView:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self p_dealWithUserAgent];
return YES;
}
// 获取默认User-Agent
- (void)p_dealWithUserAgent{
[self.wkWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
NSString *oldAgent = result;
if ([CMBCBLStringUtil strNilOrEmpty:oldAgent]) {
return;
}
NSRange rangeUa = [oldAgent rangeOfString:_webViewConfig.webUA];
NSString *uaSuffix = [NSString stringWithFormat:@"%@%@",webUA,releaseVersion];
// 如果仅仅设置一次UA,设置UA后重新关闭浏览器才会生效
if(rangeUa.location == NSNotFound)
{
NSString *newAgent = [NSString stringWithFormat:@"%@/%@",oldAgent,uaSuffix];
NSDictionary *dictionnary = @{@"UserAgent":newAgent};
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];
[[NSUserDefaults standardUserDefaults] synchronize];
if (@available(iOS 9.0, *)) {
[self.wkWebView setCustomUserAgent:newAgent];
}
}
}];
}
oldAgent 为默认 oldUserAgent。
newAgent 首先拼接了XXX ,标识比如,版本号,功能类别
虽然一样可以实现,但我不推荐使用这种方式,因为它是异步的。也就是必须要先声明个 property,调用 self.wkWebView = wkWebView; 把 wkWebView 保存起来。否则 block 回调时,这个 wkWebView 对象已经销毁了,回调的参数也都是 nil。
- App启动时获取系统默认UserAgen进行存储。
- 使用WebView之前,对存储的默认UserAgent进行修改,通过
registerDefaults:方法注册到内存中。 - WebView实例的时候从内存中拿到我们修改的userAgent。
修改局部UserAgent
有时候只有部分页面访问的时候需要改 UserAgent,或者不同页面访问的时候需要修改不同的 UserAgent,这个时候就只能在加载页面前进行修改。
- UIWebView :
/** 修改UIWebView的UserAgent */
- (void)changeUIWebViewUserAgent
{
/** 修改UIWebView的UserAgent */
- (void)changeUIWebViewUserAgent
{
self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.webView];
//修改UserAgent
[self changeUIWebViewUserAgent];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.whoishostingthis.com/tools/user-agent/"]]];
}
}
切记要在 - loadRequest: 之前修改。
注意,有时候把方法的调用写在 - loadRequest: 下面也没问题。这只是偶然,因为加载页面也是异步的,有时候会有延迟,实际上改 UserAgent 的代码执行完了才加载完页面。如果网速极端好的情况,就会出现 UserAgent 设置无效的问题。
还要注意,获取并修改 userAgent 的 webView 对象,跟加载网页的 webView 不能是同一个对象。
我调用 - changeUIWebViewUserAgent 在方法内部重新初始化了一个 webView 对象去获取并修改 userAgent ,而 self.webView 则负责加载网页,两者不是同一个对象。否则,就会出现第一次设置 UserAgent 会无效的问题。
- WKWebView:
UIWebView修改+WKWebView加载
建议使用 UIWebView 的方式修改 UserAgent 后,再使用 WKWebView 加载网页,这样就很简单,使用起来跟 UIWebView 一样。
/** 修改WKWebView的UserAgent */
- (void)changeWKWebViewUserAgent
{
self.wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.wkWebView];
//
UIWebView *webView = [[UIWebView alloc]initWithFrame:CGRectZero];
//修改UserAgent
NSString *oldUserAgent = [webView stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];
//自定义需要拼接的字
NSString *newUserAgent = [oldUserAgent stringByAppendingString:@"xxx"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newUserAgent, nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
[[NSUserDefaults standardUserDefaults] synchronize];
[self.wkWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.whoishostingthis.com/tools/user-agent/"]]];
}
纯WKWebView修改+加载
/** 修改WKWebView的UserAgent */
- (void)changeWKWebViewUserAgent
{
WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:CGRectZero];
self.wkWebView = wkWebView;
__weak typeof(self) weakSelf = self;
[wkWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
NSString *userAgent = result;
NSString *newUserAgent = [userAgent stringByAppendingString:@" origin/sfddjapp"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:newUserAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_async(dispatch_get_main_queue(), ^{
// 重新初始化WKWebView
strongSelf.wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[strongSelf.view addSubview:self.wkWebView];
[strongSelf.wkWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.whoishostingthis.com/tools/user-agent/"]]];
});
}];
}
这里也一样有坑,调用获取并修改 UserAgent 的 wkWebView 对象和加载页面的 wkWebView 必须是不同的对象,也就是在回调里需要重新初始化 wKWebView。否则就会出现设置 UserAgent 无效的问题,大概也就是网上说的,“要第二次才会显示自定义的值”。
顺带一提,iOS9 出了新的 API
@property (nullable, nonatomic, copy) NSString *customUserAgent
API_AVAILABLE(macosx(10.11), ios(9.0));可以直接修改 WKWebView 的 UserAgent。即使在WebView实例之后
[self.wkWebView setCustomUserAgent:newUserAgent];
但是我们还是需要适配 iOS8
思考
每次启动App,都会是系统默认的UserAgent,而调用[[NSUserDefaults standardUserDefaults]
registerDefaults:dictionary];修改后再次获得的UserAgent都是修改后的UserAgent???
结论
我们修改的UserAgent是存在在内存当中,每次App重启就都会获取到硬盘中存在的UserAgent。
后记
总而言之,不管是 UIWebView 还是 WKWebView,获取到的 UserAgent 是一样的。如果要做到最简单最通用,就用 UIWebView 的方式获取并修改 UserAgent。还有注意,修改 UserAgent 之前获取 UserAgent 的 webView 对象,和修改之后调用加载网页的 webView 对象,不能是同一个对象,否则会出现第一次设置无效的问题。
注意:
- 所以最好不要完全自定义 UserAgent,而是在默认的 UserAgent 后,拼接上所需要的自定义标识即可。