1. 前言
大家對WebView應該不陌生, 它是Android裡面用來顯示網頁的控制項, 用它顯示網頁只需要幾行代碼, 如下:
public class WebViewDemoActivity extendsActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mWebView = (WebView) findViewById(R.id.webview);
mWebView.loadUrl("http://www.bkjia.com");
}
}
這樣在WebViewDemoActivity啟動時就會顯示出google的首頁, 我們還可以在裡面輸入關鍵字進行搜尋。究竟WebView的loadUrl函數裡面究竟做了哪些事情呢?我們帶著好奇心走進android的Webkit世界。
2. 載入網頁過程的UML順序圖表
這裡先給出loadUrl整個過程的uml順序圖表, 然後再慢慢分析。 。
3. WebKit介紹
前面提到了Webkit,那究竟Webkit是什麼呢?它和我們所熟悉的WebView是什麼關係呢?這裡引用網上的解釋:“WebKit是一個開源的瀏覽器網頁排版引擎,包含WebCore排版引擎和JSCore引擎。WebCore和JSCore引擎來自於KDE項目的KHTML和KJS開源項目。Android平台的Web引擎架構採用了WebKit項目中的WebCore和JSCore部分,上層由Java語言封裝,並且作為API提供給Android應用開發人員,而底層使用WebKit核心庫(WebCore和JSCore)進行網頁排版。”可見,WebView就屬於Android的Webkit提供給應用開發人員用的上層API部分。
很多偉大的軟體都是可移植的,Webkit也不例外。目前Webkit支援Qt,Gtk,Android等等。大家都知道移植是有條件的,比如要將linux系統移植到某個硬體平台,那麼這個硬體平台至少要有個硬體定時器來進行進程調度,要有mmu來支援虛擬記憶體管理,另外對系統ram和flash的空間也是有一定底線要求的。移植Webkit所要求的條件是什麼呢?先分析Webkit載入網頁的這樣一個過程:
l 從網路上把url地址上的html檔案下載到本地
l 詞法分析和文法分析
l 繪製
很明顯,Webkit需要依賴平台相關的網路介面和圖形介面,如所示:
可移植的軟體都可以分為兩部分:平台相關代碼和平台無關代碼。這兩部分之間的互動是通過固定介面來完成的,當然Webkit也不例外。對於Android Webkit,我們把它簡單看為兩部分:Android和WebCore。 Android到Webcore方向當然是直接調用的關係, 而WebCore有時也需要回調Android部分的代碼, 這就需要通過WebCore定義的一套固定的介面來完成。如所示, ResourceHandle是WebCore擷取網路資源的介面, Android平台對這個介面的實現是調用了Android API中的android.net.*包裡面的類實現的。 GraphicContext是WebCore繪製頁面的介面, Android平台對這個介面的實現是通過調用Android平台的2D繪圖引擎Skia實現的。
4. 載入網頁過程分析
有一個很重要的概念需要解釋一下,Frame,翻譯成中文是“幀”,一個html頁面構成如,即一個html頁麵包含一個MainFrame,MainFrame中又可以包含0到n個子Frame。一個Frame對應一個Url,在MainFrame裡包含子Frame就意味著把子Frame的內容嵌入到MainFrame的一塊空間裡面。
在WebKit裡面有個結構體WebCore::Frame,這個資料結構就包含了,一個網頁解析後的全部資料。下面就是從一個Url到產生一個MainFrame的過程。
接下來就是要將Main frame裡面的資料顯示出來,這部分代碼是每個平台的實現都是不同的,我們以Android為例。首先需要說明的是,Android Webkit載入網頁是通過三個線程協作完成的:
l UI線程:Android圖形系統派發事件的線程,WebView的onDraw函數和onTouchEvent函數就是運行在這個線程。UI線程只做一些輕量級的工作,會將耗時的工作轉給Webcore線程進行處理。
l WebCore線程:在andriod.webkit.WebViewCore類中建立,負責html檔案解析等任務。
l 下載線程:在android.webkit.WebViewWorker中建立,負責下載從指定url下載html。
當調用WebView.loadUrl時線程共同作業圖表如下,UI線程向WebCore線程發送訊息,WebCore線程接到訊息會把下載任務交給下載線程,下載線程下載完畢後通知WebCore線程進行html的解析,WebCore線程解析完html後通知UI線程重新整理介面。
回顧一下這個圖:
Webcore線程就是通過ResourceHandleAndroid來啟動下載線程下載html的,然後WebCore線程進行html解析,最後產生能夠描述整個html頁面的Main frame,再通知UI線程繪製頁面。假設UI線程在onDraw函數裡需要通過分析Mainframe來繪圖,那麼很可能給人不流暢的感覺。所以WebCore線程在產生Frame結構體之後,又做了一個工作,就是解析Main frame並繪製到一塊記憶體上面,而UI線程在onDraw時只需把這塊記憶體上的內容繪製到螢幕上即可,這塊記憶體叫PictureSet。Picture是一個圖片集合,Main frame是Frame的集合,PictureSet裡面的Picture和Main frame裡面得Frame都是一一對應的關係。
還記得以前提到的代碼層次嗎?PictureSet是屬於Webkit的Andriod移植層的,而Frame是屬於WebCore層的,也就是說WebCore產生Frame後任務就完成了,然後會通過固定的介面通知Android移植層,Android移植層隨後分析Frame來產生PictureSet。在PictureSet產生後才會通知UI線程更新介面,WebView.onDraw中便會將PictureSet繪製到畫布上。
最後需要補充說明的是,Android的Webkit移植層比其它平台相比多了jni部分,通過閱讀代碼你會發現WebView,WebViewCore等類在Java和C++部分都存在,它們是通過Java和c++一起實現的。
5. Android Webkit外掛程式
5.1. 本章的目的
引導讀者理解Android Webkit的外掛程式架構, 通過介紹本人研究過程閱讀過並認為有價值的文章,並對Android Framework源碼中的SampleBrowserPlugin裡難懂的地方進行解釋, 希望能夠清除讀者對怎樣寫Android Webkit外掛程式的疑惑。 SampleBrowserPlugin 是Android Webkit外掛程式的常式, 位於源碼中的development/samples/BrowserPlugin目錄下, 先看一下裡面的README檔案吧。
5.2. 推薦先看幾篇文章
l https://developer.mozilla.org/en/Gecko_Plugin_API_Reference:雖然firefox是gecko的引擎,但是gecko和webkit的外掛程式同樣遵循NPAPI的標準, 這篇看完後就會弄如下幾個問題:
(1)什麼是Webkit外掛程式, 有什麼作用
(2)NPAPI一些函數和結構的作用。
l http://www.bkjia.com/kf/201203/123782.html:這篇文章會協助你對Android Webkit外掛程式有個簡單的瞭解。
l http://www.bkjia.com/kf/201203/123783.html:這篇文章提到了Android Webkit外掛程式的兩種模式。
5.3. 架構
外掛程式是用來擴充webkit的, 是webkit的智囊團。 例如有一種新類型的資料嵌在網頁裡,如所示, webkit不知如何處理, 此時webkit就會問他的外掛程式們如何處理"application/x-shockwave-flash”類型的資料。
<embed src="http://player.youku.com/player.php/Type/Folder/Fid/13005645/Ob/1/Pt/0/sid/XMzAxNDEwMTY4/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" allowFullScreen="true" mode="transparent" type="application/x-shockwave-flash"> </embed> |
webkit寫在先,外掛程式產生再後。Webkit的設計者寫基類,外掛程式編寫者寫子類來實現基類的虛函數。同時,webkit也提供api供外掛程式的子類調用。這樣解釋來看,架構和Android Framework的思想一致,外掛程式系統的控制點在webkit (framework)。
的NPPluginFuncs是外掛程式必須要實現的介面,而如果你的外掛程式要擴充瀏覽器規定的介面,向通過網頁中的javascript指令碼調用到外掛程式的函數,就需要繼承NPClass來做擴充。下面代碼示範如何在網頁中調用外掛程式的擴充介面:
1. <embed type="application/plugin-mimetype"> 2. <script> 3. var embed = document.embeds[0]; 4. embed.nativeMethod(); 5. alert(embed.nativeProperty); 6. embed.nativeProperty.anotherNativeMethod(); 7. </script> |
5.14. 編譯安裝SampleBrowserPlugin
l 進入android源碼頂層目錄運行:make SampleBrowserPlugin
會產生out/target/product/generic/data/app/SampleBrowserPlugin.apk
l 開啟模擬器(模擬器建立時需要添加sdcard支援)
l 運行adb install “path to SampleBrowserPlugin.apk”
5.15. 運行常式
l 寫個s.html檔案內容如下:
<objecttype="application/x-testbrowserplugin" height=50 width=250>
<param name="DrawingModel"value="Surface" />
<param name="PluginType"value="Background" />
</object>
l 運行adb push “path to s.html” /mnt/sdcard
l 在模擬器中啟動Browser程式, 在地址欄輸入:file:///mnt/sdcard/s.html或file:///sdcard/s.html,結果如說明瀏覽器正確載入運行了外掛程式。
5.16. SampleBrowserPlugin.apk裡麵包含了什麼
l libsampleplugin.so: NPAPI中plugin方的實現, 會安裝到/data/data/com.android.sampleplugin/lib目錄下。
l 一些配合libsampleplugin.so工作的java class。
l 一個“莫名其妙”的Service。
為什麼說它“莫名其妙”?讓我們看看SamplePlugin.java的內容:
public class SamplePluginextends Service {
@Override
public IBinder onBind(Intentintent) {
// TODO Auto-generated method stub
return null;
}
}
初看覺得寫代碼的人是在開玩笑吧, 這樣的Service能有什麼用呢? 再看這個Service在AndroidManifest.xml裡的描述:
<service android:name=".SamplePlugin">
<intent-filter>
<action android:name="android.webkit.PLUGIN" />
</intent-filter>
<meta-data android:name="type" android:value="native"/>
</service>
不過沒有這個Service, 現在有點啟發了嗎?試想一下Webkit是怎樣知道這個外掛程式的存在的呢?
Webkit是通過PackageManager查詢系統中能夠響應android.webkit.PLUGIN的Service來得到外掛程式安裝的路徑的。
5.17. 對Surface模式和Bitmap模式的理解
l Surface模式:如果plugin要繪製的內容是動態就需要在單獨的surface上繪圖。
l Bitmap模式:Webkit為Plugin提供繪圖的Bitmap, Plugin在上面繪圖後Webkit再把Bitmap 的內容繪製到WebView所在的Surface上面。
5.18. 具體分析
百聞不如一見, 去看SampleBrowserPlugin的代碼吧, 結合上面提到的文檔, 條理就會很清晰。
摘自 愚蠢概念