天天看點

android插件開發機制

插件機制實質上就是由主體程式定義接口,然後由插件去實作這些接口,以達到功能子產品化。android系統是基于linux核心的,其安全機制也繼承了linux的特性,再加上android framework沒有提供插件化程式設計的接口,使得在android上做插件開發顯得很困難。經過與同僚的研究和讨論,想到了一種在android上做開發插件的方法。下面直接通過一個demo來說明。

step1:定義主程式中的接口。

public interface myinterface {  

    public void test();  

}  

然後将接口打包成.jar包,提供給插件去實作。

step2:建立插件工程,實作接口。

将step1中的jar包放到lib檔案夾中,并把它加入build path,但千萬記得在order and export項不要勾選,即build的時候不把這個jar包build進去,因為在運作時會把這個接口與主程式的接口當做兩個不同的類。如下圖:

android插件開發機制
android插件開發機制

實作接口的代碼為:

public class plugappactivity extends activity implements myinterface{  

    /** called when the activity is first created. */  

    @override  

    public void oncreate(bundle savedinstancestate) {  

        super.oncreate(savedinstancestate);  

        setcontentview(r.layout.main);  

    }  

    public void test() {  

    system.out.println(getapplicationinfo().sourcedir);  

為什麼這裡要繼承activity呢?這個在下一步說明,這裡的activity可以替代成service、receiver或provider。

在androidmanifest加入這個activity(其他元件同理)。

<?xml version="1.0" encoding="utf-8"?>  

<manifest xmlns:android="http://schemas.android.com/apk/res/android"  

    package="com.intsig.plugapp"  

    android:versioncode="1"  

    android:versionname="1.0" android:shareduserid="com.main">  

    <uses-sdk android:minsdkversion="7" />  

    <application  

        android:icon="@drawable/ic_launcher"  

        android:label="@string/app_name" >  

        <activity  

            android:name=".plugappactivity"  

            android:label="@string/app_name" >  

            <intent-filter>  

                <action android:name="com.intsig.appmain.plugin" />  

                <category android:name="android.intent.category.default" />  

            </intent-filter>  

        </activity>  

    </application>  

</manifest>  

這裡的shareduserid是指插件與主程式共用一個uid,這樣就消除了權限的壁壘。android系統繼承了linux系統管理檔案的方法,為每一個應用程式配置設定一個獨立的使用者id和使用者組id,而由這個應用程式建立出來的資料檔案就賦予相應的使用者以及使用者組讀寫的權限,其餘使用者則無權對該檔案進行讀寫。例如,如果我們進入到android系統月曆應用程式資料目錄com.android.providers.calendar下的databases檔案中,會看到一個用來儲存月曆資料的資料庫檔案calendar.db,它的權限設定如下所示:

root@android:/data/data/com.android.providers.calendar/databases # ls -l    

-rw-rw---- app_17   app_17      33792 2011-11-07 15:50 calendar.db    

這裡的app_17就是系統自動配置設定的uid。

至于給activity添加的intent-filter中的action也會在後面解釋。

step3:在主程式中擷取插件,并調用接口方法。

<span style="font-size: 18px">public class mainactivity extends activity {  

    //</span><span style="font-size: 12px">預定義的action</span><span style="font-size: 18px">  

    public static final string action_plugin = "com.intsig.mainapp.plugin";  

        try {  

            //</span><span style="font-size: 12px">查找符合這個action的所有activity即插件,若插件使用的是其他元件換成對應的方法</span><span style="font-size: 18px">  

            list<resolveinfo> infos = getpackagemanager().queryintentactivities(  

                    new intent(action_plugin), packagemanager.match_default_only);  

            activityinfo plugininfo;  

            for(resolveinfo info:infos){  

            <span style="white-space: pre"> </span>plugininfo = info.activityinfo;  

                //</span><span style="font-size: 12px">根據插件的安裝路徑獲得classloader</span><span style="font-size: 18px">  

                classloader cl = new pathclassloader(plugininfo.applicationinfo.sourcedir,getclassloader());  

                //</span><span style="font-size: 12px">獲得插件類的執行個體</span><span style="font-size: 18px">  

                myinterface plugin = (myinterface) cl.loadclass(plugininfo.name).newinstance();  

                plugin.test();  

            }  

        } catch (exception e) {  

            e.printstacktrace();  

        }  

}</span>  

<span style="font-size:18px;">public class mainactivity extends activity {  

    //</span><span style="font-size:12px;">預定義的action</span><span style="font-size:18px;">  

            //</span><span style="font-size:12px;">查找符合這個action的所有activity即插件,若插件使用的是其他元件換成對應的方法</span><span style="font-size:18px;">  

                //</span><span style="font-size:12px;">根據插件的安裝路徑獲得classloader</span><span style="font-size:18px;">  

                //</span><span style="font-size:12px;">獲得插件類的執行個體</span><span style="font-size:18px;">  

這裡通過intent來找到所有符合條件的activity,即我們之前實作的插件,通過動态的加載類來獲得插件執行個體。主程式的androidmanifest如下:

    package="com.intsig.mainapp"  

            android:name=".mainactivity"  

                <action android:name="android.intent.action.main" />  

                <category android:name="android.intent.category.launcher" />  

插件中的shareduserid要與這裡的保持一緻。

上面三步描述了用android的四大元件來實作插件,但除此之外還有另一種方式。從上面的demo可以發現所有的插件與主程式的shareduserid都是一緻的,那麼就可以通過檢索所有安裝程式的shareduserid,隻要與主程式的一緻便可當做是它的插件。在上面的方法中我們獲得了插件的路徑以及實作接口類的類名,進而能夠動态的加載這個類,而通過檢索shareduserid能夠獲得到路徑卻無法獲得到類名,那麼可以在插件中加入一個xml檔案來說明插件中包含的實作類,通過讀取這個xml來擷取出類名和其他一些可能需要的描述資訊,這個就會比第一種要複雜一些。總結一下,當插件的功能比較簡單,選擇第一種方法比較容易實作;當插件功能較多,邏輯複雜時,可以将插件再細分成子產品,同時xml檔案可以表現出插件的組織結構,那麼第二種方法更好一些。

上面所講的兩種方法都是适用于将安裝的apk作為插件,實作插件開發還可以通過在sd卡中的指定目錄放入插件的jar包或apk檔案,原理與上述類似,隻是将pathclassloader換成dexclassloader,換成它的原因是dexclassloader的文檔描述有一句:“a

class loader that loads classes from <code>.jar</code> and <code>.apk</code> files

containing a <code>classes.dex</code> entry. this can be used to execute code not installed as part of an application.”二者的差別我還沒來得及研究,希望有興趣的同學去研究下。