浏览器探究——APP层基本架构
App层的功能主要分几块:
使用WebView的浏览器页面主体
除WebView之外的UI
页面的相关功能,如页内查找,前进,后退
设置
事件
多窗口管理
书签/历史记录
首先看构成主体框架的几个类
BrowserActivity
BrowserActivity继承自Activity,提供了对Browser应用生命周期的控制,事件的入口。这里的事件包括Key的事件,菜单的事件等。
BrowserActivity创建了Controller(用于控制)和UI(用于显示)这两个核心类的对象。
BrowserActivity中接收到的事件等信息都传递给Controller,由Controller来做具体的处理。这样BrowserActivity其实负责很轻松的工作,就是创建和传递。这里可以把BrowserActivity当做一个外界的信息源来看待,而把UI和Controller的组合当做真正的主体看待。
主界面
浏览页面的主界面由接口类UI来定义对外的接口,由UI的实现类BaseUI来实现基本的功能,由BaseUI的继承类PhoneUI和XLargeUI来分别实现Phone和Pad的特殊UI界面。
Controller中有UI的引用,UI定义的接口主要就是提供给Controller,供Controller控制UI。即Controller只能通过UI提供的这些有限的接口来控制主界面。
这样看主界面的控制并不繁琐,通过UI这个接口把界面的实现和界面的控制给分离开了。
BaseUI是实现UI接口的类。BaseUI提供了主界面的实现。这里的主界面是指浏览网页的界面,不包含书签,设置等页面。
PhoneUI和XLargeUI继承自BaseUI。他们对BaseUI的一些功能做了扩展,实际上BaseUI对一些函数做了空实现,由其继承类再做具体的实现。
BaseUI主要由两部分界面组成,一个是上面的TitleBar,另一个是下面的WebView。这里分别对应了TitleBar类与Tab类。
主界面的控制
有了UI界面后,需要有相应的控制器来操作UI界面。于是抽象出了主界面的控制器UiController接口。
其他的地方就可以通过获取UiController的引用,来操作UI了。
Controller实现了UiController接口,即实现UiController具体功能的就是Controller,其他地方获取的就是Controller的引用。
BaseUi本身也有UiController的引用,用于自身做一些控制跟UI相关的操作。其实UiController定义的接口并不都是控制UI界面的,而是指跟UI相关的一批功能。
Controller继承UiController接口,本身有UI接口的引用。
BaseUI继承UI接口,本身有UiController接口的引用。
WebView的控制
浏览器的主体是WebView,对网页的操作都是通过WebView这个控件完成的。
WebView被Tab类给包装起来,并且把BrowserSetting说也包含进来了。BrowserSetting是一个单例类,也即只有一个BrowserSetting对象。但是每一个窗口对应一个Tab。每个Tab里会创建一个WebView,同时包含BrowserSetting的引用。用公共的BrowserSetting的设置内容来配置它自己的WebView。
对于Tab,有一个TabControl的类来管理它,这里我觉得不称为控制,称为管理更好,因为TabControl与Tab是一对多的关系,TabControl用于创建,销毁Tab,并且管理所有的Tab,排序所有的Tab,让其他模块可以获取某个Tab,至于获取到Tab后,就由相应的模块直接控制该Tab了。
而TabControl又是在Controller中创建的,由Controller 来维护,但是Controller 也提供了getTabControl()的接口,可以让其他模块获取到TabControl的引用。
对于WebView 的控制操作,又抽象出了WebViewController接口。WebViewController中很多接口函数都是需要传入Tab作为参数的。而实现WebViewController接口的是Controller。
这就比较容易理解为什么需要传入Tab作为参数了,因为Controller中管理的是TabControl
的对象,而TabControl是管理一组Tab的。所以如果要明确的对某一个Tab进行相关的操作,则需要通过参数传入该Tab,而Tab中又包含着WebView,所以Controller能够进一步的操作到WebView。
在Tab中又有WebViewController的引用,这样Tab也可以进一步调用到TabControl,进而与其他的Tab相互控制。
界面,WebView,控制器
通过以上可见,Controller即实现了UiController接口,又实现了WebViewController接口。而主页面BaseUI中又有一个当前被激活使用的Tab。BaseUI与Tab都通过Controller来控制。
这样其他的模块只要获取了Controller的引用,即可控制界面的WebView和其他的界面部分。
WebView的接口的使用
WebView是伴随着Tab一起创建的,Tab中管理着WebView,但是WebView的create是在Controller中,再设置给Tab,这样Tab可以剥除它的WebView,也可以重新设置WebView。
但是WebView的接口主要通过Tab来调用。
Tab中有两个WebView,一个被称为mMainView,另一个为mSubView(暂时不知道何时使用)。另外有一个BrowserSettings用于获额取当前浏览器的设置,并可以通过BrowserSettings来对全局的设置进行操作。
Tab之间又可以组建父子关系,在一个页面中通过点击一个链接,并且在新的Tab页中打开这个链接时,那么新Tab就是当前这个Tab的孩子。
WebView的回调是通过WebChromeClient和WebViewClient这两个类作为回调类来实现的。
Tab中创建这两个类的继承类对象,然后设置在其WebView上。
这样外部模块通过Tab封装的WebView接口可以调用WebView的功能,或者通过Tab获取WebView,直接操作WebView来调用WebView的功能。
WebView通过回调类来回调一些操作,这些操作通过Tab实现的回调类来把回调信息传递到外部。注意Tab是可以获取到WebViewController的,而WebViewController与UiController是同一个Controller。即UI跟WebView是通过Controller连接起来的。所以在Tab中获取到回调信息后,是可以进一步通过Controller传递出去的。
UI请求WebView功能与WebView功能回调UI
基于上诉的框架可以看以下的一个例子,看下loadUrl这个操作。
当在地址栏输入一个url时并点击回车时,会触发loadUrl这个流程。
首先看界面上,地址栏是属于TitleBar的。在layout中可以看到title_bar_nav.xml文件定义了布局。而地址栏就对应了类UrlInputView。所以事件会从这里开始触发。
这个地址栏UI布局的从外到内整体情况如下:
BaseUi->TitleBar->NavigationBarBase->UrlInputView
在对事件从内向往的处理过程中,就可以通过BaseUi找到UiController了(实际代码中在创建一些UI相关的类对象时就会把UiController传给该UI类对象,以便于以后使用。但是UiController最初是在BaseUi中获取到的)。
再根据前面所述的框架,UI找到了UiController(即Controller),然后调用UiController的处理方法,UiController的处理时,此时就不要把它再看做UiController,而是看做Controller,Controller的处理会通过TabControl找到某个具体的Tab,然后再通过Tab找到WebView,进而调用WebView的loadUrl。
经过上述流程就完成了,然后Controller再调用UI接口提供的执行进度更新的处理,即又完成了Controller对UI的控制。
通过以上可见,UI部分可以通过找到UiController,进而调用Controller的处理函数。
Controller本身既包含对UI的处理,又包含对WebView的处理,以及对Tab的管理。
而Tab也可以通过WebViewController来找到Controller,进而对UI或者其他的Tab进行处理。
这样很清晰的看到Controller是个总控制,也是个中转。
上面对WebView执行loadUrl之后,会收到WebView的一些回调信息,这里以onPageStarted为例。
首先回调传达到WebViewClient的onPageStarted函数中。之前提过,WebViewClient是在Tab中创建的,并且关联到Tab中的WebView中。所以回调信息首先到达Tab中。
Tab如果想把信息传达到UI中那么就必须先找到Controller。Tab中有WebViewController,WebViewController就是Controller。这样Tab找到了Controller,Controller中有UI接口的对象,那么通过UI接口提供的操作就可以传达到BaseUI中,这样进一步就可以到达具体的UI控件中了比如TitleBar和NavigationBar。
WebView以事件方式的回调
WebView还有一种方式是以事件的方式回调。WebView本身是继承自View的一个控件,本身又有Activity的上下文信息,这样WebView可以发起一些事件,由BrowserActivity来响应,典型的一个例子是长按网页上的一个链接或者一个图片时,会弹出一个ContentMenu。
这个过程时WebView发起了performLongClick()请求,该请求会执行ContentMenu的事件调用。
BrowserActivity中的onCreateContentMenu被调用,BrowserActivity会把事件传递给Controller,由Controller来处理,Controller会根据WebView请求的一些信息来填充ContentMenu的菜单项,同时会设置菜单项的点击事件。然后又通过UI来设置TitleBar的隐藏。
这个过程其实就是组建了一个ContentMenu,然后又对当前UI做一些处理。也就是事件从WebView发出,BrowserActivity接收响应,响应传递给Controller。Controller做为主控制,则既可以操作UI也可以操作功能,可以填充ContentMenu也可以为每个菜单项设置具体的功能函数。可见在这个过程中Controller做了很多事,负担了组织和管理和分配的工作。
设置的管理
每个WebView有一个WebSettings与之对应,但是浏览器作为一个整体,其所有的WebView的设置内容都相同,于是在app层统一出一个BrowserSettings的单例类。
在BrowserSettings中由一个List管理了所有的WebSettings。这个是WebSettings的管理中心。
那么WebSettings的来源呢?在创建WebView的时候,会从该WebView中取出它的WebSettings,然后把这个WebSettings加入到BrowserSettings中进行统一管理,并且还会以当前BrowserSettings中的配置情况对这个WebSettings进行同步设置,这样新的WebSettings的设置情况就跟其他的WebSettings一样了,也即新创建的WebView的设置情况与其他的WebView的设置情况统一了。这么看就跟一个新员工入职一样,新员工入职(创建WebView),进行登记(把它的WebSettings加入到BrowserSettings),并且对他进行员工配置和培训(同步它的WebSettings),这样新员工就跟其他同事有了相同的职位属性了(WebSettings配置相同)。
设置的本地存储
有了上述的设置的管理和同步后,这些设置还需要保存到本地,以便下次启动时同步。根据android的数据保存的方式,该配置采用SharedPreferences的形式来保存,保存的位置就是手机的/data/data/com.android.browser/shared_prefs/com.android.browser_preferences.xml中。
BrowserSettings可以获取到SharedPreferences,通过SharedPreferences来读取和写入某项设置的配置情况。并且BrowserSettings把自身作为OnSharedPreferenceChangeListener注册给SharedPreferences,这样当SharedPreferences有变化时,BrowserSettings会请求同步所有的WebSettings,这样各个WebSettings始终跟SharedPreferences的配置情况保持一致。
设置的UI
设置的UI采用PreferenceActivity的方式,先把设置分组,每组作为一个header,见preference_headers.xml
每组的页面又以PreferenceFragment的形式展现,各个PreferenceFragment的初始配置从SharedPreferences中对应项取出。
当用户选择一些配置项的设置时,就会触发前面所说的SharedPreferences的更新事件,对于一些复杂的配置项,则需要在PreferenceFragment中主动的执行SharedPreferences的设置了,这样同样也会触发SharedPreferences的更新事件。
设置的整体结构
这样,把上述的内容组合起来得到下面的设置的整体结构