Skip to content

dannysunyu/androidstockbrowser

Repository files navigation

Code Analysis of Android Stock Browser

This analysis aims to provide a thorough guide to developing browser apps based on stock browser.

Stock Browser for Android

代码分析

ControllerWebViewUIActivity的桥梁 启动界面BrowserActivity中最重要的方法是createController(),Controller被创建时会调用 mController.setUi(new PhoneUi(this, controller))

PhoneUi包含NavigationBarPhone
TitleBar包含NavigationBarPhone

BaseUi里关联一个custom_screen.xml布局,并会创建一个TitleBar custom_screen.xml里有一个idmain_contentFrameLayout,叫做mContentViewTab切换都会从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导航界面

加载URL流程

Controller.loadUrl -> Tab.loadUrl

Tab.loadUrl里会1. 设置正在加载URL状态标志;2. 调用WebViewController.onPageStarted;最后调用WebView.loadUrl

WebViewController.onPageStarted十分关键,会调用BaseUi.onTabDataChanged,然后会调用TitleBar.onTabDataChangedNavigationBarBase.onTabDataChanged

对菜单项的更新不在TitleBar.onTabDataChangedNavigationBarBase.onTabDataChanged中;

有关ControlleronPageStartedonPageFinished方法

有关Controller.onPageFinished是在Tab.mWebViewClient.onPageFinished中被回调;

分析“前进”、“后退”菜单项更新状态逻辑

这些菜单项是埋藏在“更多”菜单项里,每次点击该按钮,会创建、更新一个PopupMenu,所以每次加载网页都不会主动更新菜单状态,而是在点击“更多”菜单项时,才更新状态(Lazy Initialization)。

分析“Stop”按钮状态更新逻辑

在Tab加载网页时,会在TitleBar上显示一个Stop按钮,并在加载完后隐藏。Stop按钮作为控件状态更新的典型,厘清该控制流程非常重要。

  1. 首先发现该按钮位于NavigationBarPhone内;
  2. 发现在更新Stop按钮的状态位于onProgressStartedonProgressStoppedonStateChanged中;

UrlInputView使用了Observer设计模式,其为一个主题(Subject)对象,而NavigationBarPhone为观察者,彼此通过UrlInputView.StateListener.StateListener接口来进行通信,其中NavigationBarPhone实现UrlInputView.StateListener接口。

NavigationBarPhoneonProgressStartedonProgressStopped被回调的地方是TitleBar.setProgress,而TitleBar.setProgress是被BaseUi.onProgressChanged调用的;

分析Back逻辑

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代码分析

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分析

对一个标签页,获取Favicon时机有:

  1. Tab$mWebViewClient.onPageStarted
  2. 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会在onPageStartedonPageFinished之间被回调,但是调用goBack回到上一个网页,onReceivedTitle却不会被回调。

TitleBar分析

TitleBar里带有刷/暂停按钮,这个按钮的状态变化将是接下来分析的重点

About

Stock Browser for Android

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages