浏览器探究——UserAgent
首先看APP层对UA的使用情况
BrowserSettings对UA的维护
在BrowserSettings.java中定义了几个固定的UA值。
还包含了一个成员private WeakHashMap<WebSettings, String> mCustomUserAgents;该成员记录的是某个WebSettings被用户额外的设置的UA情况。浏览器中有唯一的一个BrowserSettings。但是每个WebView有一个对应的WebSettings。BrowserSettings的mCustomUserAgents用来记录所有的WebSettings的用户设置情况,这个所谓的用户设置情况在android 4.0上只是设置的桌面版UA的情况,即某个WebSettings被设置了桌面版UA,则该WebSettings的桌面版UA值被添加入该映射中。
同步WebSettings的UA
在BrowserSettings:: syncSetting中有对WebSettings的UA的同步情况。当mCustomUserAgents中存储的与该WebSettings对应的UA不为空时,则直接用mCustomUserAgents存储的值,即用户设置的桌面版的请求。否则通过SharedPreferences的配置情况来获取当前UA用的哪个值,也就是前面说的定义的几个固定值。当前android中并没有开放SharedPreferences中对UA的选择,提供的是debug情况下才可以选择。这部分如果有需求,其实可以自己修改来加入该功能的。即在SharedPreferences中可以提供对外的接口,让用户来选择默认使用哪种UA。然后在BrowserSettings:: syncSetting中可以把它作为一个默认UA值。
设置WebSettings的UA用的是WebSettings::setUserAgentString函数。
使用桌面UA的功能
Android 4.0提供了一个叫请求桌面版网站的功能,其实所谓的请求桌面版就是使用桌面版的UA。在BrowserSettings中有函数toggleDesktopUseragent(WebView view)就是用于在桌面版UA和非桌面版UA之前进行切换用的。
当使用桌面版UA时,会向mCustomUserAgents中设置于该WebSettings对应的桌面版UA值,并再通过WebSettings::setUserAgentString把做桌面版UA设置进去。可见mCustomUserAgents中存的只是个记录而已。
当再次执行toggleDesktopUseragent时,会移除mCustomUserAgents中与该WebSettings对应的桌面版UA值,并通过WebSettings::setUserAgentString把默认UA设置进去。
可见,判断一个WebSettings使用的是不是桌面版UA,就是通过查询mCustomUserAgents中与该WebSettings对应的值是否为NULL来判断的。
果然,在boolean BrowserSettings::hasDesktopUseragent(WebView view)中就是这么判断的。
获取UA值
由于每个WebView都有自己的UA,所以在app层获取UA时,不能通过BrowserSettings。而是要通过WebSettings来获取。通过WebView::getSettings()获取到与该WebView对应的WebSettings,然后再通过WebSettings::getUserAgentString()获取到对应的UA。UA主要用在http通讯上,由于app层也有一些用到http通讯的地方(比如获取某个URL的MimeType值,就是发http请求,然后查询http响应头信息的),所以需要通过这个过程来获取UA值。
总结APP层对UA的使用
APP层的WebSettings中有个mCustomUserAgents用来记录某个WebSettings使用桌面版UA的情况。
WebSettings中定义了几个固定的UA值,并提供了BrowserSettings::syncSetting的函数,可以在设置中扩展设置项来选择默认的UA值。
当前的默认UA值为null。
UA的真正设置是通过WebSettings::setUserAgentString接口设置的。
UA的获取是通过WebSettings::getUserAgentString接口获取的。
其次看FrameWork层对UA的维护情况
WebSettings中对UA的维护
由于每个UA关联一个WebView,所以每个WebSettings都维护与之对应的WebView使用的UA值。
WebSettings有两个与UA相关的成员
private String mUserAgent; //使用的UA值
private boolean mUseDefaultUserAgent; //使用的是否是默认的值,这里的默认值是指系统自己生成的UA值,即不是通过应用调用WebSettings:: setUserAgentString(参数为null值)设置的UA值。
UA的初始值
在WebSettings的构造函数中会设置mUseDefaultUserAgent为true,即使用的是默认UA值。mUserAgent的值是通过WebSettings:: getCurrentUserAgent接口获取到的。
WebSettings::getCurrentUserAgent
会通过android.os.Build获取到一些信息,通过framework/base/core/res/res/values/strings.xml获取一些固定值,再通过java.util.Local获取语言信息,等组合成一个UA值,作为返回值。
那么初始的UA值,就是这个组合出来的值。
WebSettings:: setUserAgentString
/**
* Set the WebView's user-agent string. If the string "ua" isnull or empty,
* it will use the system default user-agent string.
*/
该函数用于提供接口供外部设置UA值。
如果参数为null或者字符串长度为0,则使用getCurrentUserAgent接口来获取默认的UA,赋值给mUserAgent,并设置mUseDefaultUserAgent为true。
如果参数为一个有效的字符串,则用参数来设置mUserAgent。并设置mUseDefaultUserAgent为false。
当UA值有改变时,会执行WebSettings:: postSync通知同步。
WebSettings::getUserAgentString
/**
* Return the WebView's user-agent string.
*/
该函数用于提供接口供外部获取UA值。
这里先判断如果使用的是桌面版UA值,则直接返回mUserAgent的内容,如果mUseDefaultUserAgent为false,即曾经有人通过setUserAgentString主动设置过非空的UA值,则也直接返回mUserAgent的内容。
否则会先查询下java.util.Local是否有改变(与存在WebSettings的成员static Local sLocale做对比),如果有改变还是通过getCurrentUserAgent返回UA值。
这里就有个疑问,为什么不直接返回mUserAgent呢?默认情况下mUserAgent的值应该和getCurrentUserAgent返回的相等啊,其实不然,因为计算组合UA时有一条是要通过java.util.Local获取语言信息的,当前用户可能切换了语言,那么UA的值也就应该随之改变了。
最后在该函数中,如果通过java.util.Local改变了,那么还要执行WebSettings:: postSync通知同步。
WebSettings::postSync
该接口对自身的Handler发送了一个消息,在消息处理中,会调用nativeSync这个jni函数。用途是通知c层更新设置。
通过以上就完成了对UA的初始化,设置,查询了。
总结Framework层对UA的维护
总结下FrameWork层对UA的维护情况,其实就是WebSettings在构造时做了初始化的操作,并提供了一个设置一个获取的接口。
UA的默认值是通过WebSettings::getCurrentUserAgent获取的,这个接口是利用一些信息组织成一个UA值的。
C层对UA的使用
WebKit层有一个与java层WebSettings对应的在Source/WebKit/android/jni/WebSettings.cpp的c层的WebSettings
在这里c层直接可以访问java层的WebSettings的成员mUserAgent。
这里只有一个jni方法,就是与java层的nativeSync对应的函数WebSettings::Sync
WebSettings::Sync
该接口会把java层的WebSettings中各个成员的信息设置给WebCore::Settings这个类。
这样WebCore中的各个类就可以通过WebCore::Settings来获取到响应的设置信息了。
但是UA的信息却特例,它是设置给了android::WebFrame这个类了。
之后网络请求时使用UA就会通过android::WebFrame:: userAgentForURL来获取UA值了。
总结C层对UA的使用
通过与java层WebSettings对应的c层的jni下的WebSettings的sync这个jni接口,即时更新UA的值,并把UA值设置给android::WebFrame。
其他的c层类通过android::WebFrame:: userAgentForURL来获取UA值。