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的学习
可能还有需要研究的点,待后续补充!