瀏覽器探究——多窗口

點擊網址導航欄後面的多窗口的圖標。

會調用NavigationBarPhone.onClick。

NavigationBarPhone

該類創建了導航欄的各個控件,其中的onClick是各個控件點擊的總入口。

通過名字可以看出這個導航欄是指針對手機的,因爲4.0即包含phone的需求又包含pad的需求。NavigationBarPhone是繼承自NavigationBarBase,另外NavigationBarTablet也繼承自NavigationBarBase。

BrowserActivity在onCreate時創建了PhoneUi對象(如果是pad會創建XLargeUI),PhoneUi的構造函數中會創建NavigationBarPhone。

NavigationBarPhone的控件是在onFinishInflate中被設置的Linstener。該函數是繼承view的接口來的,描述如下。

Finalize inflating a view from XML.  This is called as the last phase ofinflation, after all child views have been added.

回到NavigationBarPhone.onClick處,調用PhoneUi.toggleNavScreen進而調用PhoneUi.showNavScreen。這個PhoneUi.showNavScreen是個比較麻煩的函數,與之對應的有一個PhoneUi.hideNavScreen。這兩個函數一個是從正常界面進入到多窗口界面,另一個是從多窗口界面恢復到正常界面,這個多窗口界面這裏被稱爲導航界面。這兩個函數看下來要花費點心思了。

PhoneUi.showNavScreen

顯示導引界面,就是多窗口的界面了。

PhoneUi中有個成員Tab mActiveTab;即當前激活的Tab,這裏調用一個重要的函數Tab.Capture。

Tab. Capture

Tab中有個成員Bitmap mCapture;而這個Tab.Capture函數的主要作用就是講當前Tab的WebView中的內容畫到這個mCapture上。看下這個函數的實現,先利用mCapture作爲參數,創建一個Canvas。

這裏要說一下mCapture的寬高了,在Tab構造函數中,會通過資源配置文件獲取圖片的寬和高(mCaptureWidth,mCaptureHeight),然後利用這個寬高創建的mCapture。

Tab. Capture中會先計算下mMainView(是個WebView)當前圖像的ScrollX和ScrollY的偏移,就是這個View的圖像很大,所以用了Scroll,但是當前只保存可視的圖像,所以要計算可視圖像在整體View的偏移。然後又計算了Capture與可視圖像的一個scale比例。然後調用了Canvas的兩個重要函數Canvas.translate和Canvas.scale。這兩個函數分別記錄了座標變換矩陣和scale情況,這兩個函數的效果會應用到後續所有的繪製操作上。比如設置了座標變換爲(100, 100),設置了scale爲0.5(這個數值跟scale參數不一樣,只是做個舉例)。後續如果繪製一個座標位置爲(0,0)大小爲(100,100)的矩形。則實際上canvas在對應的bitmap上繪製的情況是,在(100,100)的位置上繪製了大小爲(50,50)的矩形。即把前面設置的translate和scale都應用上了,這兩個設置就相當於給狀態機上設置了新的狀態。

然後調用一個BrowserWebView的重要函數BrowserWebView.drawContent。BrowserWebView就是mMainView,它創建時的實際類型是BrowserWebView,BrowserWebView是繼承自WebView,對WebView做了一點擴展而已。這個BrowserWebView.drawContent主要的處理還是調用了WebView.onDraw。這個函數的實現這裏先不看了,實現很複雜,但是大致情況應該就是把WebView的內容繪製在參數傳入的Canvas中。

之後再繪製一些邊框。那麼已經完成了對WebView可視區繪製到mCapture這個Bitmap的工作了。

額外提兩個Canvas的函數save和restoneToCount。

/**

     * Saves the currentmatrix and clip onto a private stack. Subsequent

     * calls totranslate,scale,rotate,skew,concat or clipRect,clipPath

     * will all operate asusual, but when the balancing call to restore()

     * is made, those callswill be forgotten, and the settings that existed

     * before the save() willbe reinstated.

     *

     * @return The value topass to restoreToCount() to balance this save()

     */

/**

     * Efficient way to popany calls to save() that happened after the save

     * count reachedsaveCount. It is an error for saveCount to be less than 1.

     *

     * Example:

     *    int count = canvas.save();

     *    ... // more calls potentially to save()

     *    canvas.restoreToCount(count);

     *    // now the canvas is back in the same stateit was before the initial

     *    // call to save().

     *

     * @param saveCount Thesave level to restore to.

     */

看起來就是狀態的入棧保存和出棧恢復。Canvas是個狀態機嘛。

回到Tab.capture。在上述做好了縮略圖後,調用Tab.persisThumbnail。該函數會發送一個TAB_SAVE_THUMBNAIL給DataControllerHandler線程,該線程有個Handler來處理消息,處理方式是通過調用DataController.doSaveThumbnail把該Tab中剛剛生成的縮略圖保存到ContentResolver中。這樣Tab.capture大致就執行完了,該函數就是生成個當前WebView的可視區的縮略圖,縮略圖就是Tab的mCapture,然後還把這個縮略圖給保存到ContentResolver中了。

回到PhoneUi.showNavScreen,該函數在執行完Tab.capture後,會PhoneUi.detachTab。

PhoneUi.detachTab

該函數的作用是把當前的Tab從界面上移除。會調用PhoneUi.removeTabFromContentView。該函數先執行基類的removeTabFromContentView,基類的removeTabFromContentView會移除Tab的FrameLayout,這樣Tab就不會被顯示出來的,另外它還移除了WebView在Tab的FrameLayout的關係。之後PhoneUi.removeTabFromContentView又做了一點處理,如做了隱藏標題欄的處理(PhoneUi.hideTitleBar)

之後又做了一個處理是,把TitleBar的圖像和mActiveTab的WebView的縮略圖設置給一個叫AnimScreen的類對象,這個類主要就是輔助做動畫用的。之後的操作就是做了一個從當前瀏覽頁面的窗口,切換到多窗口頁面的一個動畫。這裏暫時不去看具體做動畫的操作,留到單獨的部分再看。

當執行完PhoneUi.showNavScreen後,界面就會進入到多窗口頁面了。

當點擊窗口的縮略圖時,會執行從多窗口頁面返回到正常瀏覽頁面的操作。

NavScreen.TabAdapter.getView中設置的OnClickListener會接收到響應。onClick會被調用。這個過程就是上述進入過程的反過程了。調用了NavScreen.close,進而調用PhoneUi.hideNavScreen。

PhoneUi.hideNavScreen

看名字就知道跟PhoneUi.showNavScreen是對應的了。通過點擊的位置可以從TabControl中找到被點擊的Tab. 然後把這個Tab通過Controller.setActiveTab設置爲ActivieTab。

Controller.setActiveTab

這個函數調用了2步:1執行TabControl.setCurrentTab,把tab在TabControl中更新。2執行PhoneUi.setActiveTab,這個是在UI層面上進行Tab的設置。即兩步一個是在控制數據結構中的更新,一個是在UI層面上的更新。依次看這兩步的實現情況。

TabControl

該類是控制多個Tab的,主要包含數據結構如下:

// Maximum number of tabs.

   private int mMaxTabs;

   // Private array of WebViews that are used as tabs.

   private ArrayList<Tab> mTabs;

   // Queue of most recently viewed tabs.

   private ArrayList<Tab> mTabQueue;

   // Current position in mTabs.

   private int mCurrentTab = -1;

這裏的mTabQueue需要注意下,TabControl用兩個list來維護tabs,這個mTabQueue記錄了Tabs被使用的順序,可以通過它獲取最近所打開的tab。

TabControl.setCurrentTab

  /**

    * Put the current tab in the background and set newTab as the currenttab.

    * @param newTab The new tab. If newTab is null, the current tab is not

    *               set.

    */

回到TabControl.setCurrentTab中來。該函數把當親的tab放到後臺。然後更新了mTabQueue,使得newTab放在隊列尾部。如果這個newTab沒有WebView,則爲它創建一個WebView並設置給它。最後把newTab調入前臺。完成了兩個Tab的切換。

PhoneUi. setActiveTab

調用父類的setActiveTab,即BaseUi.setActiveTab。BaseUi.setActiveTab會調用attachTabToContentView。

/*

     * Instead of attachingtabs directly to the content view, we attach them to the RealViewSwitcher.

     */

之前在執行進入多窗口時曾經調用過removeTabFromContentView,此處與之對應。這裏把Tab中的WebView加入到Tab的FrameLayout中,然後又把Tab這個View設置進BaseUi的FrameLayoutmContentView中。

之後會回調給Tab通知一些數據的更新,比如URL,進度。BaseUi.setActiveTab主要就執行這些。

PhoneUi.setActiveTab會繼續做一些處理,會把Focus設置給這個tab的TopWindow。這裏細節不看。

以上完成了Control.setCurrentTab。回到PhoneUi.hideNavScreen。接下來又是像進入多窗口頁面時那樣,設置一個動畫,執行完動畫後,多窗口頁面就回到了瀏覽主頁面上。

在多窗口頁面,點擊新建窗口的按鈕。會新創建一個Tab,然後返回到瀏覽頁面顯示新Tab,並在新Tab中打開homePage,

首先被調用的是NavScreen.onClick。NavScreen就是多窗口頁面,這裏被稱爲導航頁面,該文件中聲明瞭不少控件,但是沒有使用,可見android將來回在這個頁面上添加不少新功能的。

當前能點擊的就三個按鈕,書籤,新Tab,菜單。點擊新Tab時,執行的是NavScreen.openNewTab。

NavScreen.openNewTab

該函數先通過單例類BrowserSetting獲取到homePage,然後作爲參數調用Controller.openTab。

Controller.openTab

Controller.openTab利用Control.createNewTab來創建一個新的Tab。然後對這個Tab執行loadUrl的操作,這裏的url即爲homePage。

Controller.createNewTab

// this method will attempt to create a new tab

    // incognito: privatebrowsing tab

    // setActive: ste tab ascurrent tab

    // useCurrent: if no newtab can be created, return current tab

注意下第二個參數,4.0的代碼中加入了隱身的Tab,如果該參數就會創建一個隱身模式的Tab,隱身模式在瀏覽時不會記錄到歷史記錄之類的。

該函數首先要通過TabControl判斷下Tab數量是否滿了,如果沒滿則開始創建。如果已經滿了,則看下參數是否使用當前的Tab,如果可以就用當前的。如果不行就顯示警告了。創建的時候調用TabControl.createNewTab。當前在Control類中,而對於Tab的操作基本上都是通過TabControl來實現的。當TabControl.createNewTab創建完Tab後,會把這個Tab設置爲ActiveTab即執行Controller.setActiveTab。該函數在上面已經說過了。

TabControl.createNewTab

/**

     * Create a new tab.

     * @return The newlycreateTab or null if we have reached the maximum

     *         number of open tabs.

     */

該函數通過TabControl.createNewWebView創建一個WebView,然後用這個WebView來New一個Tab,之後把這個Tab加入到TabControl的ArrayList<Tab>mTabs中去。最後把這個Tab放入後臺,即初始時Tab都是在後臺的。創建流程很清晰。繼續看下TabControl.createNewWebView的實現。

TabControl.createNewWebView

/**

     * Creates a new WebViewand registers it with the global settings.

     * @param privateBrowsingWhen true, enables private browsing in the new

     *        WebView.

     */

這個的實現部分可以發現,真正創建WebView的並不是通過TabControl的方法,而是通過Controller獲取了一個WebViewFactory,這個工廠類提供了創建WebView和創建SubWebView的接口。瀏覽器提供了一個繼承該接口的類BrowserWebViewFactory,它實現了接口。TabControl.createNewWebView獲取到BrowserWebViewFactory後調用它的createWebView方法。

BrowserWebViewFactory.createWebView

先new一個BrowserWebView,這個BrowserWebView extend WebView //Manage WebView scroll events。可見BrowserWebView主要就是多了些對WebView事件的處理,其實處理的內容並不多。但是有個子類也方便未來的擴展。

這裏需要注意下WebView的結構,每個WebView都有個WebViewCore對象,但是隻有一個WebCoreThread線程,WebView通過WebViewCore向WebCoreThread線程發消息,請求處理任務。每個WebViewCore都有個WebSettings。即每個WebView都有個WebSettings了。WebSettings裏記錄了很多的設置項,其中包含UserAgent的獲取函數和設置函數以及保存的UserAgent變量。這個WebSettings是framework/webkit下的,即跟WebView關聯的。

BrowserSettings

說到WebSettings就不得不說下BrowserSettings了,BrowserSettings是app層的代碼,並且是個單例的類,它有個重要的成員private LinkedList<WeakReference<WebSettings>>mManagedSettings;另外有個重要的函數BrowserSettings.startManagingSettings(WebSettings settings)。這個函數會把BrowserSettings中的一些設置項的內容同步到參數WebSettings中,然後把WebSettings加入到BrowserSettings.mManagedSettings。BrowserSettings.mManagedSettings就用於同步所有的WebView的WebSettings,這樣當設置有變化時,所有的WebView的WebSettings都會得到更新。

回到BrowserWebViewFactory. createWebView,在實例化WebView後執行BrowserWebViewFactory.initWebViewSettings。看名字就知道這裏爲WebView做了一些初始化的設置,當前主要是跟ScrollBar相關的和zoom相關的。然後就執行了剛剛講的BrowserSettings. startManagingSettings函數,來同步新建的WebView的WebSettings,並把這個WebSettings加入到BrowserSettings中來統一管理。

經過以上,一個WebView算是正經的創建完了,就可以回到TabControl.createNewTab中了。然後就一層層的返回了。

在多窗口頁面,點擊某個窗口的關閉按鈕,會關閉這個窗口。也可以通過向左或者向右拖動來關閉這個窗口。

當執行點關閉按鈕時,會執行NavScreen的TabAdapter.getView中設置的OnClickListener的onClick。

這裏會調用NavTabScoller.animateOut這個函數做了一個動畫的處理,細節不看,在動畫的完成時會執行之前設置的OnRemoveListener的onRemovePosition。這個Listener是在NavScreen.init中被設置的。

那麼看下這個listener的執行過程。先通過TabAdapter和參數pos找到被點擊的Tab。然後執行NavScreen.onCloseTab就完了。那麼久看下這個關鍵的函數吧。

NavScreen.onCloseTab

參數tab是大年tab時執行Controller.closeCurrentTab,否則執行Controller.closeTab。

Controller.closeTab

/**

    * Close the tab, remove its associated title bar, and adjustmTabControl's

    * current tab to a valid value.

    */

Tab爲CurrentTab時,執行Controller.closeCurrentTab,否則執行Controller.removeTab。Controller.removeTab

調用PhoneUi.removeTab,再調用TabControl.removeTab。可見對Tab的各種操作都是對UI層通過PhoneUi提供的接口,執行UI層的更新操作。對控制層通過TabControl提供的接口,執行控制層的更新。

TabControl.removeTab

/**

     * Remove the tab from thelist. If the tab is the current tab shown, the

     * last created tab willbe shown.

     * @param t The tab to beremoved.

     */

這個函數就如註釋說所,從TabControl的mTabs和mTabQueue這兩個list中都移除這個Tab.

然後執行Tab.desory進行銷燬。最後把Tab從父子關係中移除,即原來的Tab可能有其父Tab也可能有其子Tab。如果有子Tab則設所有的子Tab的父Tab爲null,如果有父Tab則把它從父Tab的子列表中移除。即解除所有跟它相關的父子關係。解除了關係後還會制修訂Tab.deleteThumbnail來刪除之前存儲的縮略圖數據。這裏看下Tab.destory的銷燬過程。

Tab.destory

//Destroy the tab's main WebView andsubWindow if any

通過Tab.dismissSubWindow //Dismiss the subWindow for the tab. 移除並銷燬子WebView。這個其實跟銷燬主WebView差不太多,不看。

對主WebView執行BrowserWebView.setEmbeddedTitleBar

// Make sure the embedded title bar isn't still attached

/**

     * Add or remove a title bar to be embeddedinto the WebView, and scroll

     * along with it vertically, whileremaining in view horizontally. Pass

     * null to remove the title bar from theWebView, and return to drawing

     * the WebView normally without translatingto account for the title bar.

     * @hide

     */

再之後就是先保存下MainView這個引用,然後執行Tab.setWebView(null)把主WebView與該Tab解除關係。之後調用WebView.destroy。

WebView.destroy

/**

     * Destroy the internalstate of the WebView. This method should be called

     * after the WebView hasbeen removed from the view system. No other

     * methods may be calledon a WebView after destroy.

     */

註釋已經說明,在執行該函數前要先移除WebView,剛纔的過程就是那樣。

這裏需要注意一點是,在destory中會執行WebViewCore.destory。而WebViewCore.destory會向WebViewCoreThread線程發一個DESTORY的消息,WebViewCoreThread線程收到消息後會執行BrowserFrame.destory。注意下BrowserFrame new出來時也是在這個線程裏做的,這裏銷燬時還是在這個線程裏。雖然一個BrowserFrame是對應一個WebView。這裏應該是由於BrowserFrame的各種處理都是在該線程裏的,所以銷燬也要在該線程裏,防止併發導致問題。

然後回執行WebSettings.onDestoryed。這個函數其實是個空實現。這裏只是通知下WebSetttings

Controller.removeTab的執行情況大體如此。回到Controller.closeTab。Controller.closeTab說當Tab爲CurrentTab是執行的是Controller.closeCurrentTab。

Controller.closeCurrentTab

Controller.closeCurrentTab的處理過程相比Controller.removeTab主要就是多了一個選擇一個新的Tab設置爲CurrentTab的過程,調用了Controller.switchToTab的操作。這個新Tab的選擇邏輯是:1:原CurrentTab有父親,就用其父Tab。2:該Tab位置的下面有Tab就用下面的Tab。3:用上面的Tab。

另外補充一點,如果當前一共就一個Tab了,會執行TabControl.removeTab後執行BrowserActivity.finish。

額外說一句BrowserActivity.finish。只是對Activity執行了finish,銷燬Activity,但是瀏覽器有很多其他的子線程,那些子線程通過這個finish是不會被銷燬的。所以可以看到瀏覽器的進程仍然存在,那些子線程們也都還存在。

Controller.switchToTab

/**

     * @param tab the tab toswitch to

     * @return boolean True ifwe successfully switched to a different tab. If

     *                 the indexth tab is null, or ifthat tab is the same as

     *                 the current one, return false.

     */

這個函數其實就是將傳入的Tab執行Controller.setActiveTab。設個函數上述有說。

以上就是在多窗口頁面關閉窗口的過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章