整理了一下Browser系列, 以前写的太乱了.
Tab是浏览器和用户打交道的主要UI,浏览器的最主要的功能--上网就是有他来完成了.上一篇文章我们已经看到了
BrowserActivity是如何展现的第一个Tab, 这里我们看一下Tab的初始化:
TabControl是管理整个窗口切换的逻辑, 我们打开浏览器标签窗口, 切换窗口, 关闭窗口, 都是通过TabControl实现的.可见TabControl是一个非常重要的部件
在Controller中, 首先初始化的也是TabControl, 其构造函数如下:
1 /**
2 * Construct a new TabControl object
3 */
4 TabControl(Controller controller) {
5 mController = controller;//拿到Controller的引用, 将来会做一些 浏览器相关的事情
6 mMaxTabs = mController.getMaxTabs(); //最多支持多少tab
7 mTabs = new ArrayList<Tab>(mMaxTabs);//这个队列用来维护打开的tab tabs的删除添加等都需要添加到他上面,但是他不能知道哪个tab是最后打开的
8 mTabQueue = new ArrayList<Tab>(mMaxTabs);//这个队列用来按一定的顺序存放tab 最近访问的view在最后面
9 }
|
|
初始化ok 了TabControl之后, 从Acitivity的 onCreate中就会开始启动业务逻辑
1 | mController.start(icicle, getIntent()); |
这个做一下操作之后会开始启动Tabcontrol
1 | void start(final Bundle icicle, final Intent intent) { |
2 | boolean noCrashRecovery = intent.getBooleanExtra(NO_CRASH_RECOVERY, false);//是否设置了崩溃恢复 |
3 | if (icicle != null || noCrashRecovery) { |
4 | doStart(icicle, intent, false); |
6 | mCrashRecoveryHandler.startRecovery(intent); |
02 | void doStart(final Bundle icicle, final Intent intent, final boolean fromCrash) { |
03 | // Unless the last browser usage was within 24 hours, destroy any |
04 | // remaining incognito tabs. |
06 | Calendar lastActiveDate = icicle != null ? |
07 | (Calendar) icicle.getSerializable("lastActiveDate") : null; |
08 | Calendar today = Calendar.getInstance(); |
09 | Calendar yesterday = Calendar.getInstance(); |
10 | yesterday.add(Calendar.DATE, -1); |
12 | final boolean restoreIncognitoTabs = !(lastActiveDate == null |
13 | || lastActiveDate.before(yesterday) //当天启动的才会恢复以前的页面 |
14 | || lastActiveDate.after(today)); |
16 | // Find out if we will restore any state and remember the tab. |
17 | //是否可以恢复这个tab 如果可以恢复就返回其id否则 -1 |
18 | final long currentTabId = |
19 | mTabControl.canRestoreState(icicle, restoreIncognitoTabs); |
21 | if (currentTabId == -1) { |
22 | // Not able to restore so we go ahead and clear session cookies. We |
23 | // must do this before trying to login the user as we don't want to |
24 | // clear any session cookies set during login. 如果没有恢复的tab就清楚session |
25 | CookieManager.getInstance().removeSessionCookie(); |
28 | GoogleAccountLogin.startLoginIfNeeded(mActivity,//登陆谷歌账户 |
30 | @Override public void run() { |
32 | onPreloginFinished(icicle, intent, currentTabId, restoreIncognitoTabs, |
在这里有个获取 需要恢复窗口id的操作 final long currentTabId = mTabControl.canRestoreState(icicle, restoreIncognitoTabs);
01 | /** final long currentTabId = |
02 | mTabControl.canRestoreState(icicle, restoreIncognitoTabs); <span></span> * Check if the state can be restored. If the state can be restored, the |
03 | * current tab id is returned. This can be passed to restoreState below |
04 | * in order to restore the correct tab. Otherwise, -1 is returned and the |
05 | * state cannot be restored. |
07 | long canRestoreState(Bundle inState, boolean restoreIncognitoTabs) { |
08 | final long[] ids = (inState == null) ? null : inState.getLongArray(POSITIONS); |
12 | final long oldcurrent = inState.getLong(CURRENT); |
14 | if (restoreIncognitoTabs || (hasState(oldcurrent, inState) && !isIncognito(oldcurrent, inState))) { |
17 | // pick first non incognito tab |
19 | if (hasState(id, inState) && !isIncognito(id, inState)) { |
28 | private boolean hasState(long id, Bundle state) { |
29 | if (id == -1) return false; |
30 | Bundle tab = state.getBundle(Long.toString(id)); |
31 | return ((tab != null) && !tab.isEmpty()); |
33 | //是否是隐身窗口, 隐身窗口是不应该恢复的 |
34 | private boolean isIncognito(long id, Bundle state) { |
35 | Bundle tabstate = state.getBundle(Long.toString(id)); |
36 | if ((tabstate != null) && !tabstate.isEmpty()) { |
37 | return tabstate.getBoolean(Tab.INCOGNITO); |
后面的代码其实还是在onPreloginFinished函数中:
01 | /*!!这是浏览器 第一次启动时候的入口*/ |
02 | private void onPreloginFinished(Bundle icicle, Intent intent, long currentTabId, |
03 | boolean restoreIncognitoTabs, boolean fromCrash) { |
04 | if (currentTabId == -1) { |
05 | BackgroundHandler.execute(new PruneThumbnails(mActivity, null)); //清空缩略图缓存 |
06 | final Bundle extra = intent.getExtras(); |
07 | // Create an initial tab. |
08 | // If the intent is ACTION_VIEW and data is not null, the Browser is |
09 | // invoked to view the content by another application. In this case, |
10 | // the tab will be close when exit. |
11 | UrlData urlData = IntentHandler.getUrlDataFromIntent(intent); |
13 | if (urlData.isEmpty()) {//这里开始打开tab了 |
14 | t = openTabToHomePage();//intent没有数据 打开home |
16 | t = openTab(urlData); //打开对于url的 tab |
18 | if (t != null) {//设置调用应用的id |
19 | t.setAppId(intent.getStringExtra(Browser.EXTRA_APPLICATION_ID)); |
21 | WebView webView = t.getWebView(); |
23 | int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0); |
24 | if (scale > 0 && scale <= 1000) { |
25 | webView.setInitialScale(scale); |
28 | mUi.updateTabs(mTabControl.getTabs()); //更新多窗口列表 |
29 | } else {//这部分代码处理的是获取一下意外退出销毁的Tab的过程: |
30 | mTabControl.restoreState(icicle, currentTabId, restoreIncognitoTabs, |
31 | mUi.needsRestoreAllTabs()); |
32 | List<Tab> tabs = mTabControl.getTabs(); |
33 | ArrayList<Long> restoredTabs = new ArrayList<Long>(tabs.size()); |
35 | restoredTabs.add(t.getId()); |
37 | BackgroundHandler.execute(new PruneThumbnails(mActivity, restoredTabs)); |
38 | if (tabs.size() == 0) { |
42 | // TabControl.restoreState() will create a new tab even if |
43 | // restoring the state fails. |
44 | setActiveTab(mTabControl.getCurrentTab()); |
45 | // Handle the intent if needed. If icicle != null, we are restoring |
46 | // and the intent will be stale - ignore it. |
47 | if (icicle == null || fromCrash) { |
48 | mIntentHandler.onNewIntent(intent); |
51 | // Read JavaScript flags if it exists. |
52 | String jsFlags = getSettings().getJsEngineFlags(); |
53 | if (jsFlags.trim().length() != 0) { |
54 | getCurrentWebView().setJsFlags(jsFlags); |
57 | if (BrowserActivity.ACTION_SHOW_BOOKMARKS.equals(intent.getAction())) { |
58 | bookmarksOrHistoryPicker(ComboViews.Bookmarks); |
这里如果执行了else操作 会有个 恢复以前标签窗口的操作 其实就是从Bundle中拿到恢复的数据把窗口内容恢复:
发现最后还是会请求网络来载入以前的网页, 这里也可以我们自行改造, 在程序崩溃的时候把网页序列化到本地,在这里再读取到内存中.
02 | * Restore the state of all the tabs. 恢复崩溃前的状态 |
03 | * @param currentId The tab id to restore. |
04 | * @param inState The saved state of all the tabs. |
05 | * @param restoreIncognitoTabs Restoring private browsing tabs |
06 | * @param restoreAll All webviews get restored, not just the current tab |
07 | * (this does not override handling of incognito tabs) |
09 | void restoreState(Bundle inState, long currentId, |
10 | boolean restoreIncognitoTabs, boolean restoreAll) { |
11 | if (currentId == -1) { |
14 | long[] ids = inState.getLongArray(POSITIONS);//和saveState函数的put操作对应 |
15 | long maxId = -Long.MAX_VALUE; |
16 | HashMap<Long, Tab> tabMap = new HashMap<Long, Tab>(); |
17 | for (long id : ids) {//这些崩溃之前保存的tab 对应的 id 这些tab都会恢复到多窗口栈中 |
21 | final String idkey = Long.toString(id); |
22 | Bundle state = inState.getBundle(idkey); |
23 | if (state == null || state.isEmpty()) { |
26 | } else if (!restoreIncognitoTabs |
27 | && state.getBoolean(Tab.INCOGNITO)) { |
29 | } else if (id == currentId || restoreAll) { |
30 | Tab t = createNewTab(state, false); |
32 | // We could "break" at this point, but we want |
33 | // sNextId to be set correctly. |
37 | // Me must set the current tab before restoring the state |
38 | // so that all the client classes are set. |
39 | if (id == currentId) { |
40 | setCurrentTab(t);//这是当前的tab |
43 | // Create a new tab and don't restore the state yet, add it |
45 | Tab t = new Tab(mController, state); |
48 | // added the tab to the front as they are not current |
53 | // make sure that there is no id overlap between the restored |
57 | if (mCurrentTab == -1) { |
58 | if (getTabCount() > 0) { |
59 | setCurrentTab(getTab(0)); |
62 | // restore parent/child relationships<span></span> for (long id : ids) { |
63 | final Tab tab = tabMap.get(id); |
64 | final Bundle b = inState.getBundle(Long.toString(id)); |
65 | if ((b != null) && (tab != null)) { |
66 | final long parentId = b.getLong(Tab.PARENTTAB, -1); |
68 | final Tab parent = tabMap.get(parentId); |
70 | parent.addChildTab(tab); |
说到这里了, 那么崩溃前的保存现场是在那里进行的呢?在 saveState
具体调用是:Activity的onSaveInstanceState转发到Controller Controller再转发到TabControl的saveState函数
02 | * onSaveInstanceState(Bundle map) |
03 | * onSaveInstanceState is called right before onStop(). The map contains |
07 | protected void onSaveInstanceState(Bundle outState) { |
09 | Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this); |
11 | mController.onSaveInstanceState(outState); |
02 | * save the tab state: 保存崩溃前的状态 |
04 | * position sorted array of tab ids |
05 | * for each tab id, save the tab state |
09 | void saveState(Bundle outState) { |
10 | final int numTabs = getTabCount(); |
14 | long[] ids = new long[numTabs]; |
16 | for (Tab tab : mTabs) { |
17 | Bundle tabState = tab.saveState(); |
18 | if (tabState != null) { |
19 | ids[i++] = tab.getId(); |
20 | String key = Long.toString(tab.getId()); |
21 | if (outState.containsKey(key)) { |
22 | // Dump the tab state for debugging purposes |
23 | for (Tab dt : mTabs) { |
24 | Log.e(LOGTAG, dt.toString()); |
26 | throw new IllegalStateException( |
27 | "Error saving state, duplicate tab ids!"); |
29 | outState.putBundle(key, tabState); |
32 | // Since we won't be restoring the thumbnail, delete it |
33 | tab.deleteThumbnail(); |
36 | if (!outState.isEmpty()) { |
37 | outState.putLongArray(POSITIONS, ids); |
38 | Tab current = getCurrentTab(); |
40 | if (current != null) { |
41 | cid = current.getId(); |
43 | outState.putLong(CURRENT, cid); |
由于并不是所有tab都是可以恢复 (现场保存并不能保证全部都保存下来?)和需要恢复 (隐身窗口是不应该恢复) 的, 所以在onPreloginFinished 前 使用了canRestoreState函数进行判断:
02 | * Check if the state can be restored. If the state can be restored, the |
03 | * current tab id is returned. This can be passed to restoreState below |
04 | * in order to restore the correct tab. Otherwise, -1 is returned and the |
05 | * state cannot be restored. |
07 | long canRestoreState(Bundle inState, boolean restoreIncognitoTabs) { |
08 | final long[] ids = (inState == null) ? null : inState.getLongArray(POSITIONS); |
12 | final long oldcurrent = inState.getLong(CURRENT); |
14 | if (restoreIncognitoTabs || (hasState(oldcurrent, inState) && !isIncognito(oldcurrent, inState))) { |
17 | // pick first non incognito tab |
19 | if (hasState(id, inState) && !isIncognito(id, inState)) { |
28 | private boolean hasState(long id, Bundle state) { |
29 | if (id == -1) return false; |
30 | Bundle tab = state.getBundle(Long.toString(id)); |
31 | return ((tab != null) && !tab.isEmpty()); |
34 | private boolean isIncognito(long id, Bundle state) { |
35 | Bundle tabstate = state.getBundle(Long.toString(id)); |
36 | if ((tabstate != null) && !tabstate.isEmpty()) { |
37 | return tabstate.getBoolean(Tab.INCOGNITO); |