UIWebView获取详细浏览记录

本文所用方法会使用到苹果私有API,上架APP Store请谨慎使用。

需求

获取UIWebView的前进后退的浏览记录,举个例子,比如从A->B->C->B,此时B上一页是A,下一页是C,需要获取A、C的URL信息。

WKWebView暴露了这个属性,WKBackForwardList *backForwardList。可以很容易的取到,无奈项目还是使用的UIWebView,于是有了后面的探索。

首先在UIWebView提供的API里找,相关的API只有canGoBackcanGoForward,命名很直观,是否可以后退和前进,这条路走不通。
想到是否可以通过JS的Window.history获取到,查了下API,唯一一个有点像的length属性,智能取到浏览记录中的所有URL的数量,不区分前后,比如在上面提到的例子中,在B页面取到的length是3,而且不能取出具体的URL。

15325235986699

无奈只有上源码了,我下载的是“WebKit-7604.1.38.0.7”和“WebCore-7604.1.38.0.7”

Window.History.length实现本质

在WebCore里面找到了JS方法Window.History.length实现的本质,上代码:

1
2
3
4
5
6
7
8
9
10
// WebCore History.cpp
unsigned History::length() const
{
if (!m_frame)
return 0;
auto* page = m_frame->page();
if (!page)
return 0;
return page->backForward().count();
}
1
2
3
4
5
6
// WebCore BackForwardController.cpp
int BackForwardController::count() const
{
// count = 当前页之前的页面总数 + 当前页之后的页面总数 + 1
return m_client->backListCount() + 1 + m_client->forwardListCount();
}

WebCore里面backListCount()forwardListCount()定义是虚函数,具体实现在WebKit可以找到。

1
2
3
4
5
6
7
8
9
10
// WebKit BackForwardList.mm
int BackForwardList::backListCount()
{
return m_current == NoCurrentItemIndex ? 0 : m_current;
}

int BackForwardList::forwardListCount()
{
return m_current == NoCurrentItemIndex ? 0 : (int)m_entries.size() - (m_current + 1);
}

还有一个方法我们需要关注一下,后面会用到。

1
2
3
4
5
6
7
8
9
10
11
// WebKit BackForwardList.mm
// 获取之前最多几条历史记录
void BackForwardList::backListWithLimit(int limit, Vector<Ref<HistoryItem>>& list)
{
list.clear();
if (m_current != NoCurrentItemIndex) {
unsigned first = std::max(static_cast<int>(m_current) - limit, 0);
for (; first < m_current; ++first)
list.append(m_entries[first].get());
}
}

获取History

WebView的中,我们可以找到WebBackForwardList的定义。

1
2
3
4
5
/*!
@property backForwardList
@abstract The backforward list for this WebView.
*/
@property (nonatomic, readonly, strong) WebBackForwardList *backForwardList;

WebBackForwardList是对BackForwardList的一层封装,阅读一下它的.h文件,不难找到获取浏览记录的方法。

1
2
3
4
5
6
7
/*!
@method backListWithLimit:
@abstract Returns a portion of the list before the current entry.
@param limit A cap on the size of the array returned.
@result An array of items before the current entry, or nil if there are none. The entries are in the order that they were originally visited.
*/
- (NSArray *)backListWithLimit:(int)limit;

上面的方法可以获取到一个WebHistoryItem数组,WebHistoryItem保存了浏览记录的详细信息。

获取WebBackForwardList

UIWebView没有暴露获取WebView或者WebBackForwardList的方法,但是我们可以用KVO曲线救国,于是我们需要找到WebView的私有变量名。用runtime可以做到,为了简化这个过程,我写了一个工具类来辅助搜索,具体可以看这篇文章-runtime实现私有变量搜索。简单来说就是用runtime获取类成员变量列表,然后用BFS来搜索我们要找的类。

1
2
3
4
5
6
7
8
9
10
// 以UIWebView为根节点,BFS搜索WebView
[BFSSearchClass searchClass:@"WebView" inClass:@"UIWebView"];

// 搜索结果如下
// Class Name:类名,Ivar Name:变量名,Super Class:父类
Class Name:【WebView】,Ivar Name:【_webView】
Class Name:【UIWebDocumentView】,Ivar Name:【Super Class】
Class Name:【UIWebBrowserView】,Ivar Name:【browserView】
Class Name:【UIWebViewInternal】,Ivar Name:【_internal】
Root Class:UIWebView

有了上面的结论,我们就可以用KVC来获取到浏览记录了。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)printWebViewHistory:(UIWebView *)aWebView {
id webviewInternal = [aWebView valueForKey:@"_internal"];
id browserView = [webviewInternal valueForKey:@"browserView"];
id webView = [browserView valueForKey:@"_webView"];
id backForwardList = [webView performSelector:@selector(backForwardList)];
// WebHistoryItem存储的具体某条浏览记录信息
NSArray *historyItems = [backForwardList performSelector:@selector(backListWithLimit:) withObject:@10];
for (id item in historyItems) {
// 获取浏览记录的url
NSString *url = [item performSelector:@selector(URLString)];
NSLog(@"%@", url);
}
}