BrowserActivity 是浏览器的核心Activity了, 是浏览器的入口, 但是他里面并没有出来很多复杂的逻辑, 只是实现一些android
系统对activity的回调. 这些逻辑交给了Controller来处理, 就让我们一步一步的来看看浏览器是怎么从启动到打开Tab的 吧
首先是初始化Controller, 其时序图如下: Controller 初始化了一堆浏览器运行至关重要的类:
先看一下 onCreate 函数如下:
02 |
public void onCreate(Bundle icicle) { |
04 |
Log.v(LOGTAG, this + " onStart, has state: " |
05 |
+ (icicle == null ? "false" : "true")); |
07 |
super.onCreate(icicle); |
09 |
// If this was a web search request, pass it on to the default web |
10 |
// search provider and finish this activity. |
12 |
if (IntentHandler.handleWebSearchIntent(this, null, getIntent())) { |
17 |
mController = new Controller(this, icicle == null); |
18 |
boolean xlarge = isTablet(this); |
21 |
mUi = new XLargeUi(this, mController); |
23 |
mUi = new PhoneUi(this, mController); |
26 |
mController.setUi(mUi); |
28 |
Bundle state = getIntent().getBundleExtra(EXTRA_STATE); |
29 |
if (state != null && icicle == null) { |
33 |
mController.start(icicle, getIntent()); |
是Controller这个类,这是浏览器的核心,我看看一次Controller都初始化什么业务:
01 |
public Controller(Activity browser, boolean preloadCrashState) { |
03 |
mSettings = BrowserSettings.getInstance(); //拿到BrowserSetting 的设置实例 |
04 |
mTabControl = new TabControl(this); //初始化tab的控制器 |
05 |
mSettings.setController(this);//Setting的实例也需要从controller 中设置一些 东西 |
06 |
mCrashRecoveryHandler = CrashRecoveryHandler.initialize(this); //崩溃处理注册 |
07 |
if (preloadCrashState) { |
08 |
mCrashRecoveryHandler.preloadCrashState(); //载入崩溃恢复的网页 |
10 |
mFactory = new BrowserWebViewFactory(browser);//初始化 webview的工厂 |
12 |
mUrlHandler = new UrlHandler(this);//url处理类 |
13 |
mIntentHandler = new IntentHandler(mActivity, this);//处理各种intent在BrowserActivity也曾用到 |
14 |
mPageDialogsHandler = new PageDialogsHandler(mActivity, this); //页面信息页面 |
16 |
startHandler();//初始化全局的handler 这个handler 用来处理 形如 前进后退,打开书签窗口等操作 |
17 |
mBookmarksObserver = new ContentObserver(mHandler) { //数据库数据变化通知tabcontroller更新书签数据 |
19 |
public void onChange(boolean selfChange) { |
20 |
int size = mTabControl.getTabCount(); |
21 |
for (int i = 0; i < size; i++) { |
22 |
mTabControl.getTab(i).updateBookmarkedStatus(); |
27 |
browser.getContentResolver().registerContentObserver( |
28 |
BrowserContract.Bookmarks.CONTENT_URI, true, mBookmarksObserver);//注册这个观察者 |
30 |
mNetworkHandler = new NetworkStateHandler(mActivity, this); //网络变化监听 |
31 |
// Start watching the default geolocation permissions |
32 |
mSystemAllowGeolocationOrigins = |
33 |
new SystemAllowGeolocationOrigins(mActivity.getApplicationContext()); //地理位置信息服务 |
34 |
mSystemAllowGeolocationOrigins.start(); |
36 |
openIconDatabase();//网址图标 |
初始化ok了Controller之后就是设置View了, 这个在上一篇文章已经提到, 不再赘述. 我们看Activity的onResume函数中:
02 |
protected void onResume() { |
05 |
Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this); |
07 |
if (mController != null) { |
08 |
mController.onResume(); |
回调到了Controller的onResume 我们也说过 Activity的功能基本上都是转发
02 |
if (!mActivityPaused) { //android 经常有这样的标志位判断是否 是发生了Pause因为 从pause 和start都会执行onResume |
03 |
Log.e(LOGTAG, "BrowserActivity is already resumed."); |
06 |
mActivityPaused = false; |
07 |
Tab current = mTabControl.getCurrentTab(); |
08 |
if (current != null) { |
09 |
current.resume();//恢复当前的tab |
10 |
resumeWebViewTimers(current); //是否恢复webview的解析js执行等功能 |
12 |
releaseWakeLock(); //更换cpu模式 |
14 |
mUi.onResume(); //初始化UI 设置为当前tab |
15 |
mNetworkHandler.onResume(); //注册网络变化通知 |
16 |
WebView.enablePlatformNotifications(); |
17 |
NfcHandler.register(mActivity, this); //注册nfc |
mUI.onResume()
回调到了BaseUI的onResume
1 |
public void onResume() { |
2 |
mActivityPaused = false; |
3 |
// check if we exited without setting active tab |
5 |
final Tab ct = mTabControl.getCurrentTab(); |
7 |
setActiveTab(ct);//设置当前的Tab |
这里就是设置当前的Tab了.
浏览器浏览器的启动其实有两种方式: 1.通过Launcher 启动 2.其他app调用浏览器启动 , 对于第二种启动, 如果浏览器在后台, 就直接执行onNewIntent函数如果不在后台, 会先onCreate 然后再 onNewIntent:
03 |
* browseractivity的 launchMode为singleTask的时候,通过Intent启到一个Activity,如果系统已经存在一个实例, |
04 |
* 系统就会将请求发送到这个实例上,但这个时候,系统就不会再调用通常情况下我们处理请求数据的onCreate方法, |
05 |
* 而是调用onNewIntent方法,如下所示: |
08 |
protected void onNewIntent(Intent intent) { |
09 |
if (ACTION_RESTART.equals(intent.getAction())) { |
10 |
Bundle outState = new Bundle(); |
11 |
mController.onSaveInstanceState(outState); |
13 |
//是否彻底重启浏览器? 这样 会调用onCreate 而不是这里 |
14 |
getApplicationContext().startActivity( |
15 |
new Intent(getApplicationContext(), BrowserActivity.class) |
16 |
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
17 |
.putExtra(EXTRA_STATE, outState)); |
20 |
mController.handleNewIntent(intent); |
其实还是转发Controller:
2 |
public void handleNewIntent(Intent intent) { |
3 |
if (!mUi.isWebShowing()) { |
6 |
mIntentHandler.onNewIntent(intent); |
其中主要的任务是三个:
1.打开书签 历史窗口选择书签等
2.打开传入的Url
3.可能传入的是关键词, 用户调用浏览器进行搜索
其中还包括了形如 复用Tab等代码处理逻辑
001 |
void onNewIntent(Intent intent) { |
002 |
Tab current = mTabControl.getCurrentTab(); |
003 |
// When a tab is closed on exit, the current tab index is set to -1. |
004 |
// Reset before proceed as Browser requires the current tab to be set. |
005 |
if (current == null) { |
006 |
// Try to reset the tab in case the index was incorrect. |
007 |
current = mTabControl.getTab(0); |
008 |
if (current == null) { |
009 |
// No tabs at all so just ignore this intent. |
012 |
mController.setActiveTab(current);//在当前页面打开传入的url |
014 |
final String action = intent.getAction(); |
015 |
final int flags = intent.getFlags(); |
016 |
if (Intent.ACTION_MAIN.equals(action) || |
017 |
(flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) { |
018 |
// just resume the browser |
022 |
if (BrowserActivity.ACTION_SHOW_BOOKMARKS.equals(action)) { |
024 |
mController.bookmarksOrHistoryPicker(ComboViews.Bookmarks); |
028 |
// In case the SearchDialog is open. |
029 |
((SearchManager) mActivity.getSystemService(Context.SEARCH_SERVICE)) |
031 |
boolean activateVoiceSearch = RecognizerResultsIntent |
032 |
.ACTION_VOICE_SEARCH_RESULTS.equals(action); |
033 |
if (Intent.ACTION_VIEW.equals(action) |
034 |
|| NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action) |
035 |
|| Intent.ACTION_SEARCH.equals(action) |
036 |
|| MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action) |
037 |
|| Intent.ACTION_WEB_SEARCH.equals(action) |
038 |
|| activateVoiceSearch) { |
039 |
if (current.isInVoiceSearchMode()) {//是否语音输入 |
040 |
String title = current.getVoiceDisplayTitle(); |
041 |
if (title != null && title.equals(intent.getStringExtra( |
042 |
SearchManager.QUERY))) { |
043 |
// The user submitted the same search as the last voice |
044 |
// search, so do nothing. |
049 |
if (Intent.ACTION_SEARCH.equals(action) |
050 |
&& current.voiceSearchSourceIsGoogle()) { |
051 |
Intent logIntent = new Intent( |
052 |
LoggingEvents.ACTION_LOG_EVENT); |
053 |
logIntent.putExtra(LoggingEvents.EXTRA_EVENT, |
054 |
LoggingEvents.VoiceSearch.QUERY_UPDATED); |
056 |
LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE, |
057 |
intent.getDataString()); |
058 |
mActivity.sendBroadcast(logIntent); |
059 |
// Note, onPageStarted will revert the voice title bar |
060 |
// When http://b/issue?id=2379215 is fixed, we should update |
061 |
// the title bar here. |
064 |
// If this was a search request (e.g. search query directly typed into the address bar), |
065 |
// pass it on to the default web search provider. |
066 |
//如果是搜索词 就直接开始搜索, 其实请求的是 打开Intent.ACTION_WEB_SEARCH这个action |
067 |
if (handleWebSearchIntent(mActivity, mController, intent)) { |
072 |
UrlData urlData = getUrlDataFromIntent(intent); |
073 |
if (urlData.isEmpty()) { |
074 |
urlData = new UrlData(mSettings.getHomePage()); |
077 |
if (intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false) |
078 |
|| urlData.isPreloaded()) { |
079 |
Tab t = mController.openTab(urlData);//窗口新的tab |
083 |
* TODO: Don't allow javascript URIs |
084 |
* 0) If this is a javascript: URI, *always* open a new tab |
085 |
* 1) If this is a voice search, re-use tab for appId |
086 |
* If there is no appId, use current tab |
087 |
* 2) If the URL is already opened, switch to that tab //如果url已经打开了 使用当前tab |
088 |
* 3-phone) Reuse tab with same appId //对于同一个app 重用其tab |
089 |
* 3-tablet) Open new tab |
091 |
final String appId = intent |
092 |
.getStringExtra(Browser.EXTRA_APPLICATION_ID); |
093 |
if (!TextUtils.isEmpty(urlData.mUrl) && |
094 |
urlData.mUrl.startsWith("javascript:")) { |
095 |
// Always open javascript: URIs in new tabs |
096 |
mController.openTab(urlData); |
099 |
if ((Intent.ACTION_VIEW.equals(action) |
100 |
// If a voice search has no appId, it means that it came |
101 |
// from the browser. In that case, reuse the current tab. |
102 |
|| (activateVoiceSearch && appId != null)) |
103 |
&& !mActivity.getPackageName().equals(appId)) { |
104 |
if (activateVoiceSearch || !BrowserActivity.isTablet(mActivity)) { |
105 |
Tab appTab = mTabControl.getTabFromAppId(appId); |
106 |
if (appTab != null) { |
107 |
mController.reuseTab(appTab, urlData); |
111 |
// No matching application tab, try to find a regular tab |
112 |
// with a matching url. |
113 |
Tab appTab = mTabControl.findTabWithUrl(urlData.mUrl); |
114 |
if (appTab != null) { |
115 |
// Transfer ownership |
116 |
appTab.setAppId(appId); |
117 |
if (current != appTab) { |
118 |
mController.switchToTab(appTab); |
120 |
// Otherwise, we are already viewing the correct tab. |
122 |
// if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url |
123 |
// will be opened in a new tab unless we have reached |
124 |
// MAX_TABS. Then the url will be opened in the current |
125 |
// tab. If a new tab is created, it will have "true" for |
127 |
Tab tab = mController.openTab(urlData); |
130 |
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { |
131 |
tab.setCloseOnBack(true); |
137 |
if (!urlData.isEmpty() |
138 |
&& urlData.mUrl.startsWith("about:debug")) { |
139 |
if ("about:debug.dom".equals(urlData.mUrl)) { |
140 |
current.getWebView().dumpDomTree(false); |
141 |
} else if ("about:debug.dom.file".equals(urlData.mUrl)) { |
142 |
current.getWebView().dumpDomTree(true); |
143 |
} else if ("about:debug.render".equals(urlData.mUrl)) { |
144 |
current.getWebView().dumpRenderTree(false); |
145 |
} else if ("about:debug.render.file".equals(urlData.mUrl)) { |
146 |
current.getWebView().dumpRenderTree(true); |
147 |
} else if ("about:debug.display".equals(urlData.mUrl)) { |
148 |
current.getWebView().dumpDisplayTree(); |
149 |
} else if ("about:debug.nav".equals(urlData.mUrl)) { |
150 |
current.getWebView().debugDump(); |
152 |
mSettings.toggleDebugSettings(); |
156 |
// Get rid of the subwindow if it exists |
158 |
mController.dismissSubWindow(current); |
159 |
// If the current Tab is being used as an application tab, |
160 |
// remove the association, since the new Intent means that it is |
161 |
// no longer associated with that application. |
162 |
current.setAppId(null); |
164 |
mController.loadUrlDataIn(current, urlData); |
然后执行OnResume就可以展现用户需要的窗口了!
Browser还有一个重要的结构是BaseUI, Browser的UI操作基本都限制在了BaseUI中, 当需要展示某个UI了, BaseUI会通知Controller, 然后Controller 开始调用显示各种用户交换元素:
针对手机和平板的UI不同但是功能差不多, 所有又有PhoneUI和XLargeUi继承BaseUI实现其功能, 这和Android系统的设计大同小异
的确Controller是Browser的核心, 计划从几个方面来分析:
1.TabControl的逻辑也就是多窗口切换的逻辑
2.BookMarkControl的处理逻辑也就是书签历史和保存网页
3.PieControl的学习 也就是快捷控制菜单的学习
4.框计算的学习, 就是TitleBar
5.Url 的处理 包括判断 猜测url fix Url等
6.CrashRecoveryHandler ,的学习
7.NetworkStateHandler的学习
可能还有需要研究的点,待后续补充!