WebView中clearCache的执行过程源码分析。


前情提要

上一篇文章中我们对Android WebView的loadUrl方法进行了分析,这次我们来看看clearCache究竟做了什么事,进而搞清楚WebView中的缓存机制。

WebView.clearCache

clearCache的源码很简单,唯一的参数includeDiskFiles也很容易理解。如前篇所述,WebView的大部分操作,都是代理给WebViewProvider来进行,clearCache也不例外。不过可以发现,这里有个checkThread()的操作,这是来进行什么的呢?让我们在源码中一探究竟。

1
2
3
4
5
6
7
8
9
10
/**
* Clears the resource cache. Note that the cache is per-application, so
* this will clear the cache for all WebViews used.
*
* @param includeDiskFiles if false, only the RAM cache is cleared
*/
public void clearCache(boolean includeDiskFiles) {
checkThread();
mProvider.clearCache(includeDiskFiles);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void checkThread() {
// Ignore mWebViewThread == null because this can be called during in the super class
// constructor, before this class's own constructor has even started.
if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
Throwable throwable = new Throwable(
"A WebView method was called on thread '" +
Thread.currentThread().getName() + "'. " +
"All WebView methods must be called on the same thread. " +
"(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
", FYI main Looper is " + Looper.getMainLooper() + ")");
Log.w(LOGTAG, Log.getStackTraceString(throwable));
StrictMode.onWebViewMethodCalledOnWrongThread(throwable);

if (sEnforceThreadChecking) {
throw new RuntimeException(throwable);
}
}
}

开头注释里就已经说明,忽略mWebViewThread为null的情况,因为可能会在超类的构造器里调用这个方法,此时WebView这个类的对象还没有构建出来,意味着mWebViewThread为null。if条件中的Looper.myLooper() != mWebViewThread这条判断,对Android系统有过一定了解的人的人,应该会对Looper.myLooper()这种写法非常熟悉,这是获取当前线程的典型手段。如果当前线程不是WebView线程的话,就要做好抛出异常的准备。为什么要做这种判断呢?我分析了10分钟,得出结论是:因为WebView中很多操作,都需要运用线程内部资源,如果在外部线程中调用,很容易出现并发问题,如果要避免,就只有增加同步的成本,所以索性只限制在线程内了。有一点值得注意的是,因为WebView是一个UI组件,大部分时间里都在主线程中初始化,因此,这里判断的就是当前线程是否为UI线程。

接下来我们看一下WebViewProvider中的clearCache方法,依旧是WebViewChromium.java

1
2
3
4
@Override
public void clearCache(boolean includeDiskFiles) {
mAwContents.clearCache(includeDiskFiles);
}

同样,这个操作被代理给了AwContents,源码见AwContents.java。需要注意,由于app内所有的WebView缓存都是保存在一个地方,这个方法会清空app全部WebView的缓存,参数includeDiskFiles控制清空硬盘上的缓存文件。

1
2
3
4
5
6
7
8
9
10
/**
* Clears the resource cache. Note that the cache is per-application, so this will clear the
* cache for all WebViews used.
*
* @param includeDiskFiles if false, only the RAM cache is cleared
*/
public void clearCache(boolean includeDiskFiles) {
if (mNativeAwContents == 0) return;
nativeClearCache(mNativeAwContents, includeDiskFiles);
}

对应的实现位于aw_contents.cc代码。先检测当前线程为UI线程,然后调用render_view_host_ext_的ClearCache方法,如果需要清空硬盘上的缓存的话,调用RemoveHttpDiskCache(web_contents_->GetRenderProcessHost())。逐个来分析。

1
2
3
4
5
6
7
8
void AwContents::ClearCache(JNIEnv* env,
const JavaParamRef<jobject>& obj,
jboolean include_disk_files) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
render_view_host_ext_->ClearCache();
if (include_disk_files)
RemoveHttpDiskCache(web_contents_->GetRenderProcessHost());
}

首先看render_view_host_ext_->ClearCache(),代码在aw_render_view_host_ext.cc。这里发出了一个信息,AwViewMsg_ClearCache,看出来了,是广播模式。那么消息监听者在哪儿呢?

1
2
3
4
void AwRenderViewHostExt::ClearCache() {
DCHECK(CalledOnValidThread());
Send(new AwViewMsg_ClearCache);
}

监听者位于aw_render_thread_observer.cc。通过blink::WebCache::clear()调用clear方法。blink究竟是个啥?I don’t know. 借助Google的力量,找到了WebCache::clear()的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
bool AwRenderThreadObserver::OnControlMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(AwRenderThreadObserver, message)
IPC_MESSAGE_HANDLER(AwViewMsg_ClearCache, OnClearCache)
IPC_MESSAGE_HANDLER(AwViewMsg_SetJsOnlineProperty, OnSetJsOnlineProperty)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void AwRenderThreadObserver::OnClearCache() {
blink::WebCache::clear();
}

WebCache.cpp

1
2
3
4
5
6
void WebCache::clear()
{
MemoryCache* cache = memoryCache();
if (cache)
cache->evictResources();
}

获取memoryCache()的方法位于WebCache.cpp,可以看出该cache是线程相关的,而且从命名上就可以看出,这是MemoryCache,位于RAM中。

1
2
3
4
5
6
7
MemoryCache* memoryCache()
{
ASSERT(WTF::isMainThread());
if (!gMemoryCache)
gMemoryCache = new Persistent<MemoryCache>(MemoryCache::create());
return gMemoryCache->get();
}

分析完清理内存缓存的路径,我们接下来看如何清理磁盘缓存。

1
2
3
if (include_disk_files)
RemoveHttpDiskCache(web_contents_->GetRenderProcessHost());
}

RemoveHttpDiskCache位于net_disk_cache_remover.cc。所做的事情是,在BrowserThread中发布一个Task,清理Render进程拥有的磁盘空间。

1
2
3
4
5
6
7
8
9
void RemoveHttpDiskCache(content::RenderProcessHost* render_process_host) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&ClearHttpDiskCacheOnIoThread,
base::Unretained(render_process_host->GetStoragePartition()->
GetURLRequestContext()),
base::Unretained(render_process_host->GetStoragePartition()->
GetMediaURLRequestContext())));
}

到这里,似乎告一段落了。