浏览器探究——多窗口

点击网址导航栏后面的多窗口的图标。

会调用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。设个函数上述有说。

以上就是在多窗口页面关闭窗口的过程。

你可能感兴趣的:(数据结构,浏览器,null,tabs,scroll)