2025-05-17

 最近做android瀏覽器插件學到一些東西和大傢分享:
需要瞭解的有以下幾個方面的知識:
1.插件是什麼
2.android瀏覽器怎樣加載插件和創建實例
3瀏覽器插件和腳本語言的交互
4插件內部的數據流

一瀏覽器插件介紹:
   1.1 概述
瀏覽插件本質是一個功能模塊,是瀏覽器功能的一種擴充。其載體是dll或則so文件。它依附瀏覽器完成某一特定的功能。插件需要實現瀏覽器規定的一些函數這些函數叫著NPAPI.正是插件實現瞭這些函數才可以和瀏覽器交互。同時瀏覽器也為插件提供一些函數。在android平臺下還有一些專有的函數。他們的函數名字都有約定。插件提供的方法以NPP_打頭。瀏覽器提供的方法是NPN_,android提供的函數是以ANP開頭的。
     插件作為一個共享庫那麼它什麼時候被加載。有導出瞭什麼接口讓瀏覽器調用。
     瀏覽器插件是被瀏覽器加載的。在android下也是被webkit加載。通常一個共享庫被加載都是調用loadlibray函數,然後使用getentrypoint函數得到共享庫導出函數的地址。同樣插件也是這樣被瀏覽器加載的。既然要調用loadlibrary函數那麼我們必須知道共享庫在系統中的位置。對於windows,mac,linux系統都可以存放在固定的位置。在linux下還可以通過設置環境變量來設定路徑。但是在android系統下不是這樣的。在1.5的時候有個固定的路徑data/data/com.android.browser/plug-in.但是在2.0以後就沒有存到這個路徑。而是存放在插件APK包安裝目錄下的lib文件夾下。比如插件的包名是com.android.plugin,那麼共享庫的存儲路徑應該是data/data/com.android.plugin/lib/。對於各種插件路徑不一樣,那麼瀏覽器是如何找到這些lib的路徑,下面將詳細介紹。
1.2 android瀏覽器插件
     對於android瀏覽器插件,是以apk包形式發佈的。並且在該工程中我們有定義一個service。這個service可以響應PLUGIN_ACTION。這個是在AndroidMainefest.xml中設定的。而插件的註冊也是通過service完成的。
二、android瀏覽器加載插件
    
     2.1 總述
1.1中提到瞭瀏覽器加載插件是通過loadlibary方式加載。並且需要知道路徑。其實瀏覽器加載插件總的分為三步:
1.瀏覽器尋求插件路徑,這個是通過插件apk包安裝時運行的service來找到的。
2.瀏覽器獲取插件的信息。得到插件的名字,描述和MIME信息並保存到自己的plugindatabase類實例中。
3.瀏覽器創建插件實例。這個過程創建插件實例,並對插件的內部數據初始化。
下面詳細描述這三個過程:
2.2瀏覽器獲取插件的路徑。
每次在瀏覽器啟動刷新頁面時,便會刷新自己的插件信息庫即更新自己的pluginDatabase。然後PluginDataBase的函數refresh便會調用plugin manager的函數得到插件路徑。而plugin manager通過pack manager找到所有能夠響應PLUGIN-ACTION intent的service。然後通過每一個service信息得到包的名字就可以找到插件瞭。具體可以參考frameworks/base/core/java/android/webkit/ PluginManager.java。
     
   圖1描述瞭瀏覽器得到插件路徑的流程。詳細可以參考源碼。路徑:/frameworks/base/core/java/android/webkit
external/webkit/WebCore/plugins
external/webkit/WebKit/android/jni
     2.3 瀏覽器獲取插件信息
     得到插件路徑後,我們可以得到插件導出的函數地址瞭,首先我們瞭解一下插件導出的函數和他們的功能。
        
   圖2顯示瞭一個插件共享庫導出的函數。下面詳細說明每個函數的功能。
   在瀏覽器調用refresh後將會調用NP_GetValue得到插件的名字和描述信息。然後調用NP_GetMIMEDescription得到插件的MIME類型、支持文件的擴展名和描述。將這些信息保存到pluginDatabase中。NP_Shutdown是在銷毀pluginview時會調用,作用釋放插件的資源。關於NP_Initialize函數是在創建插件實例時才會調用。具體過程如圖3所示。
 
              
    在找不到MIMEType時瀏覽器會根據數據文件的擴展名來匹配插件。
2.4瀏覽器創建插件實例:
 NP_Initialize函數比較重要。它是瀏覽器和插件進行交互的關鍵。他的功能主要有三個:
1.得到瀏覽器定義的NPN_函數地址。
2.將插件定義的NPP_函數地址返回給瀏覽器
3.得到Android提供的一些ANP_函數。
下面列出瞭這些函數:
//NPP函數,插件和瀏覽器交互的主要函數

    上面的函數都有一個NPP instance參數,其實它是一個指向pluginview成員變量的指針。而該成員變量又有兩個指針pdata和ndata。pdata指向我們的插件NPOject對象。在NPP_New中可以看到這個附值。ndata存地則是pluginview自己的this指針
//android提供的函數

上面這些函數的初始化是通過NPN_Getvalue得到的。
//插件提供的另外一些函數,這些函數主要是完成插件和javasript的交互使用。
  
在調用完NP_Initialize函數後瀏覽器就知道NPP_New函數的地址瞭。這時瀏覽器調用該函數創建瀏覽器實例。該函數是在pluginview中被調用的。
在NPP_New中我們創建插件的實例NPObject。NPObject的創建是調用瀏覽器的NPN_create函數創建的。在該create函數中又判斷NPObject自己提供create方法沒(creat方法的地址通過NPN_create第二個參數傳入的),如果沒有那麼瀏覽器自己調用malloc創建實例。並且將插件提供的NPObject函數地址(上面列出的static函數)保存在NPObject對象裡面。到此插件的實例就創建完瞭。
三、瀏覽器插件和腳本語言的交互
瀏覽器提供瞭插件和javascript交互的機制。
首先,看一下java script如何調用插件的方法的。
瀏覽器首先會調用NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value)取得NPObject對象的地址。Variable參數為NPPVpluginScriptableNPObject。在取得該對象後瀏覽器就可以調用插件提供的NPClass函數。最主要的函數有下面幾個:
pluginHasMethod :詢問插件是否支持某一js方法。
pluginHasProperty :詢問插件是否具有某一屬性
pluginInvoke :     當插件支持某一方法時,瀏覽器將會調用該函數執行插件為js提供的這一方法。那麼對於提供的很多方法插件如何在該函數內區分。
我們來分析一下該函數:
static bool pluginInvoke(NPObject *obj, NPIdentifier name, const NPVariant *args, uint32_t argCount, NPVariant *result);
obj是插件裡的NPObject對象地址。
Name表示插件提供方法的名字,通過對比這個參數來區分插件提供的不同方法。
Args和argcount分別表示js傳來的參數地址和參數個數。
Result得到的結果。
下來我們看一下插件是如何調用js提供的方法:
Js可以通過2種方式為插件設置回調函數。偽代碼如下:
<script language=javascript>
Plugin.Onfun = fun;//方式一  通過設置插件屬性傳入回調函數地址
Plugin.Onfun(fun);//方式二   通過調用插件函數傳入回調函數地址
Function fun(){}
</script>
在插件內部,當js函數地址傳到插件時,瀏覽器把它封裝為一個NPObject對象,裡面存有函數地址。
方式一: 在插件內部,瀏覽器會調用pluginHasproperty確認插件是否有該屬性。如果有然後瀏覽器調用pluginSetproperty函數。pluginSetProperty(NPObject *obj, NPIdentifier name, const NPVariant *variant)的第二個參數判斷是哪個屬性,第三個參數就是NPObject對象地址。
方式二:在插件內部,瀏覽器會調用pluginHasmethod確定是否支持該方法。然後調用pluginInvoke,這裡面的args參數包含瞭回調函數NPObject地址。
js設置完回調函數後,插件就可以調用該函數瞭。需要使用NPN_InvokeDefault,示例代碼如下:
bool bret = gBrowser->invokeDefault(npp, callbackNPObject, &pV, 1, &result);
另外,插件也可以直接調用js中的函數。在插件內部調用瀏覽器的getUrl函數。具體格式如下:
gBrowser->geturl(inst(), “javascript:function()”, "_self");
如果想傳入整數參數,上面函數第二個參數應寫成: “javascript:function(“+num+”)”。
如果傳入字符串參數,上面函數第二個參數為: “javascript:function(/’“+”string”+”/’)”。如果字符串含有中文,需要進行url encode。
四、插件數據流
插件可以通過瀏覽器向服務器請求數據,同時也可以向服務器發送數據。
如果插件需要向服務器請求數據時可以調用瀏覽器函數NPN_Geturl向服務器發送請求。裡面的target參數設置為NULL數據就可以傳給本頁面的插件。請求成功瀏覽器會調用插件的NPP_newstream函數,
通過NPP_Newstream創建流時,將傳遞一個流的模式參數,plug-in在它返回時設置這個參數,缺省設置是NP_Normal;通過NPP_DestroyStream刪除流.Plug-in也可以調用NPN_DestroyStream刪除流.這三種模式分別如下:
正常模式.當參數設置為NP_Normal時采用該模式,當有數據可發送時Netscape就把數據發送給plug-in,這些數據可能是以非正常順序到達。瀏覽器通過調用一系列的NPP_WriteReady和NPP_Write來發送數據。瀏覽器通過len這個參數告訴plug-in它將發送多少數據,Netscape調用NPP_WriteReady來確定plug-in每次準備接收多少字節的數據,再調用NPP_Write發送數據.此種模式的效率較高.
隨機存取模式.若調用NPP_NewStream時將其中的佈爾型參數Seekable設為真時,就采用此種模式.此時,流中的數據先由plug-in調用NPN_RequestRead加以指明所要獲取的數據的范圍,然後瀏覽器調用NPP_WriteReady和NPP_Write把數據傳送給plug-in.這種模式需要遠程服務器的支持或瀏覽器先將流數據存到本地的臨時文件中.用這種模式時,用戶可以從服務器的數據文件中任意讀取自己想要的記錄,就如同從本地硬盤上讀取一個記錄一樣.
文件模式.把參數設置為NP_AsFile即可.瀏覽器先將整個Url數據存到一個本地文件中,然後通過NPP_StreamAsFile將文件名傳給plug-in。Plug-in可以通過文件操作獲得所要數據.
 

五,總結
   對於瀏覽器插件的開發可以參考源碼的實例,development/samples/browseplugin實例。相關知識可以參考https://developer.mozilla.org/en/Gecko_Plugin_API_Reference

 

摘自 ownerwu的專欄

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *