整理了一下Browser系列, 之前寫的太亂了. java
Tab是瀏覽器和用戶打交道的主要UI,瀏覽器的最主要的功能--上網就是有他來完成了.上一篇文章咱們已經看到了 web
BrowserActivity是如何展示的第一個Tab, 這裏咱們看一下Tab的初始化: 瀏覽器
TabControl是管理整個窗口切換的邏輯, 咱們打開瀏覽器標籤窗口, 切換窗口, 關閉窗口, 都是經過TabControl實現的.可見TabControl是一個很是重要的部件 緩存
在Controller中, 首先初始化的也是TabControl, 其構造函數以下: cookie
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 session
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); app
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函數中: less
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中拿到恢復的數據把窗口內容恢復: ide
發現最後仍是會請求網絡來載入之前的網頁, 這裏也能夠咱們自行改造, 在程序崩潰的時候把網頁序列化到本地,在這裏再讀取到內存中.
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); |