This analysis aims to provide a thorough guide to developing browser apps based on stock browser.
Stock Browser for Android
Controller是WebView、UI、Activity的桥梁
启动界面BrowserActivity中最重要的方法是createController(),Controller被创建时会调用
mController.setUi(new PhoneUi(this, controller))
PhoneUi包含NavigationBarPhone
TitleBar包含NavigationBarPhone
BaseUi里关联一个custom_screen.xml布局,并会创建一个TitleBar
custom_screen.xml里有一个id为main_content的FrameLayout,叫做mContentView,
Tab切换都会从mContentView去添加和移除视图
<FrameLayout
android:id="@+id/fixed_titlebar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<FrameLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
其中fixed_title_container是放置TitleBar的,参考BaseUi.addFixedTitleBar
NavScreen是点击Tab Switch按钮后显示的Tab导航界面
Controller.loadUrl -> Tab.loadUrl
Tab.loadUrl里会1. 设置正在加载URL状态标志;2. 调用WebViewController.onPageStarted;最后调用WebView.loadUrl
WebViewController.onPageStarted十分关键,会调用BaseUi.onTabDataChanged,然后会调用TitleBar.onTabDataChanged和NavigationBarBase.onTabDataChanged
对菜单项的更新不在TitleBar.onTabDataChanged和NavigationBarBase.onTabDataChanged中;
有关Controller.onPageFinished是在Tab.mWebViewClient.onPageFinished中被回调;
这些菜单项是埋藏在“更多”菜单项里,每次点击该按钮,会创建、更新一个PopupMenu,所以每次加载网页都不会主动更新菜单状态,而是在点击“更多”菜单项时,才更新状态(Lazy Initialization)。
在Tab加载网页时,会在TitleBar上显示一个Stop按钮,并在加载完后隐藏。Stop按钮作为控件状态更新的典型,厘清该控制流程非常重要。
- 首先发现该按钮位于
NavigationBarPhone内; - 发现在更新Stop按钮的状态位于
onProgressStarted、onProgressStopped、onStateChanged中;
UrlInputView使用了Observer设计模式,其为一个主题(Subject)对象,而NavigationBarPhone为观察者,彼此通过UrlInputView.StateListener.StateListener接口来进行通信,其中NavigationBarPhone实现UrlInputView.StateListener接口。
NavigationBarPhone的onProgressStarted和onProgressStopped被回调的地方是TitleBar.setProgress,而TitleBar.setProgress是被BaseUi.onProgressChanged调用的;
BrowserActivity将onKeyDown和onKeyUp方法的处理委托给Controller的onKeyDown和onKeyUp。当按下返回键,Controller.onBackKey会被回调:
protected void onBackKey() {
if (!mUi.onBackKey()) {
WebView subwindow = mTabControl.getCurrentSubWindow();
if (subwindow != null) {
if (subwindow.canGoBack()) {
subwindow.goBack();
} else {
dismissSubWindow(mTabControl.getCurrentTab());
}
} else {
goBackOnePageOrQuit();
}
}
}可见,Controller先将处理委托给UI.onBackKey,仅当返回false时,Controller会让当前的SubWindow来消化该事件,如果没有SubWindow,则由Controller.goBackOnePageOrQuit处理该事件。
当当前Tab不为null,并且无法goBack时,如果有parent则切换到parent后再关闭tab,如果没有则关闭当前Tab,即调用Controller.closeCurrentTab。
NavScreen是多标签切换界面,它的布局是nav_screen.xml。
布局中重要的类是NavTabScroller,它主要负责Tab页滚动。
ViewConfiguration.get(Context).hasPermanentMenuKey()判断是否有固态Menu键
NavTabScoller通过设置BaseAdapter来绑定数据
BrowserActivity.onLowMemory会委托Controller.onLowMemory释放内存。
@Override
public void onLowMemory() {
super.onLowMemory();
mController.onLowMemory();
}Controller.onLowMemory会调用mTabControl.freeMemory来释放内存:
/**
* Free the memory in this order, 1) free the background tabs; 2) free the
* WebView cache;
*/
void freeMemory() {
if (getTabCount() == 0) return;
// free the least frequently used background tabs
Vector<Tab> tabs = getHalfLeastUsedTabs(getCurrentTab());
if (tabs.size() > 0) {
Log.w(LOGTAG, "Free " + tabs.size() + " tabs in the browser");
for (Tab t : tabs) {
// store the WebView's state.
t.saveState();
// destroy the tab
t.destroy();
}
return;
}
// free the WebView's unused memory (this includes the cache)
Log.w(LOGTAG, "Free WebView's unused memory and cache");
WebView view = getCurrentWebView();
if (view != null) {
view.freeMemory();
}
}问题是,该方法并不能实质释放内存,至少在小米4c上调试时观察Android Monitor的Memory曲线,并无内存释放。
释放内存的步骤为,先释放后台Tabs,再释放当前WebView的内存缓存。
当发生网址重定向时,WebView会多次回调WebViewClient.onPageStarted方法。比如在地址栏输入qq.com后,回调方式如下:
V/Tab: ⇢ onPageStarted(view=BrowserWebView, url="http://qq.com/", favicon=Bitmap@23b4987f)
V/Tab: ⇢ onPageStarted(view=BrowserWebView, url="http://www.qq.com/", favicon=Bitmap@23b4987f)
V/Tab: ⇢ onPageStarted(view=BrowserWebView, url="http://xw.qq.com/index.htm", favicon=Bitmap@23b4987f)
V/Tab: ⇢ onReceivedIcon(view=BrowserWebView, icon=Bitmap@315bd90a)
V/Tab: ⇢ onPageFinished(view=BrowserWebView, url="http://xw.qq.com/index.htm")
可见每重定向一次,就会回调onPageStarted方法,倘若需要多次重定向,则每次都会回调onPageStarted方法。只有最终重定向的网址会被记录到历史栈中。
对一个标签页,获取Favicon时机有:
- Tab$mWebViewClient.onPageStarted
- Tab$mWebViewClient.onReceivedIcon
值得注意的是,这两个回调返回的Bitmap对象并不是同一个。那么这两个方法返回的favicon各是什么呢?
onPageStarted()返回上一个网页的favicon;而onReceivedIcon返回当前网页的favicon。
假设先访问A(qq.com),onPageStarted返回Favicon A1,onReceivedIcon返回Favicon A2,接着访问B,B是没有Favicon的站点(比如mail.126.com),onPageStarted返回Favicon A2。
WebChromeClient.onReceivedTitle(WebView view, String title)提供了从WebView获取标题的方法。
一般加载一个网页时,onReceivedTitle会在onPageStarted和onPageFinished之间被回调,但是调用goBack回到上一个网页,onReceivedTitle却不会被回调。
TitleBar里带有刷/暂停按钮,这个按钮的状态变化将是接下来分析的重点