1. 程式人生 > >android基於類裝載器DexClassloader設計“外掛框架”

android基於類裝載器DexClassloader設計“外掛框架”

外掛相關介紹     首先外掛只是一個邏輯概念,而不是什麼技術標準,主要包含如下幾個意思:
  • 外掛不能獨立執行,必須執行一個宿主程式中,宿主程式去呼叫外掛(ps:微信的遊戲算不算外掛?感覺算是一種)
  • 外掛一般情況下可以獨立安裝,android中就可以設計一個apk
  • 宿主程式中可以管理外掛,比如新增,刪除,禁用等。
  • 宿主程式應該保證外掛向下相容,新的宿主程式應該相容老的外掛
   新浪微博的主題就是通過外掛實現的切換。 由於ClassLoader具有動態裝載程式的特點,因此可以使用此技術來一種外掛框架。下面就是實現的細節。 使用DexClassLoader實現外掛框架 大家瞭解高煥堂老師的EIT造型嗎?如果不瞭解的話可以去網上搜一下,這種造型可以用來設計一個外掛,一個框架,甚至一個平臺。 高老師總結的確實精煉,容易理解,很多設計模式都是在這個EIT的之上或者變型生做的設計。 那麼今天咱們用到EIT造型了嗎?當然用到了,咱們直接上程式碼吧。 上一篇部落格中是通過反射呼叫外掛中的類的方法的,今天咱們要把介面作為聯絡主程式和外掛程式的橋樑。 要實現住程式通過介面呼叫外掛的程式,那麼主程式和外掛程式必須有相同的介面檔案,也就是兩個程式裡都有介面的java類檔案。 首先,在主程式裡定義一個介面檔案,然後原樣的copy到外掛程式中去,介面如下: package com.suchangli.plugin; public interface CommonInterface { int function1(int a, int b); } 包結構如下所示: 宿主程式:
外掛程式 主程式的程式碼修改成使用介面
  1. @Override
  2.     protectedvoid onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.         setContentView(R.layout.activity_main);  
  5.         useDexClassLoader2();  
  6.     }  
  7.     @SuppressLint("NewApi"privatevoid useDexClassLoader2(){  
  8.         //建立一個意圖,用來找到指定的apk
  9.         Intent intent = new Intent("com.suchangli.android.plugin"null);  
  10.         //獲得包管理器
  11.         PackageManager pm = getPackageManager();  
  12.         List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);  
  13.         //獲得指定的activity的資訊
  14.         ActivityInfo actInfo = resolveinfoes.get(0
    ).activityInfo;  
  15.         //獲得包名
  16.         String pacageName = actInfo.packageName;  
  17.         //獲得apk的目錄或者jar的目錄
  18.         String apkPath = actInfo.applicationInfo.sourceDir;  
  19.         //dex解壓後的目錄,注意,這個用宿主程式的目錄,android中只允許程式讀取寫自己
  20.         //目錄下的檔案
  21.         String dexOutputDir = getApplicationInfo().dataDir;  
  22.         //native程式碼的目錄
  23.         String libPath = actInfo.applicationInfo.nativeLibraryDir;  
  24.         //建立類載入器,把dex載入到虛擬機器中
  25.         DexClassLoader calssLoader = new DexClassLoader(apkPath, dexOutputDir, libPath,  
  26.                 this.getClass().getClassLoader());  
  27.         //利用反射呼叫外掛包內的類的方法
  28.         try {  
  29.             Class<?> clazz = calssLoader.loadClass(pacageName+".Plugin1");  
  30.             CommonInterface obj = (CommonInterface)clazz.newInstance();  
  31.             int ret = obj.function1(113);  
  32.             Log.i("Host""return result is " + ret);  
  33.         } catch (ClassNotFoundException e) {  
  34.             e.printStackTrace();  
  35.         } catch (InstantiationException e) {  
  36.             e.printStackTrace();  
  37.         } catch (IllegalAccessException e) {  
  38.             e.printStackTrace();  
  39.         } catch (IllegalArgumentException e) {  
  40.             e.printStackTrace();  
  41.         }    
  42.     }  
也就這幾句程式碼不同: 外掛程式的類現介面:
  1. package com.suchangli.plugin1;  
  2. import com.suchangli.plugin.CommonInterface;  
  3. publicclass Plugin1 implements CommonInterface{  
  4.     publicint function1(int a, int b){  
  5.         return a+b;  
  6.     }  
  7. }  
直接安裝兩個程式,呼叫的時候會報這種錯誤: copy過去報錯,並且這種方式也不太現實,因為提供給外掛開發者的時候肯定是以jar包的形式進行提供,而不是以原檔案的形式提供, 更何況現在還報錯。究其原因是什麼呢? 其實是這樣的,這個java檔案被當做程式的一部分(本來就是一部分)(jar包是以外部jar的方式新增進去的,外部jar包會作為程式的一部分被最終的程式檔案中,也會報同樣的錯誤),從而使得在主程式和外掛程式中存在包名相同但驗證碼不同的類檔案。 匯出jar包,這個大家應該都會,不會的到網上搜一下。 把jar包放進外掛的libs檔案加下 引用jar包 使用紅色框的“Add Libary”,而不是藍色框的“Add External JARs”. 如果還是不行就通過這種方式: 再重新安裝一次外掛,執行一次主程式,結果如下: 主程式如何搜尋到外掛的,是在外掛程式的menifest.xml檔案中,定義了一個activity,定義了一個action: 這樣主程式就可以使用PacageManager類的queryIntentActivites()方法查詢相關的外掛程式列表了。 程式主題外掛的實現 在主程式中新增如下一個方法:
  1. privatevoid useDexClassloader3(){  
  2.         //建立一個意圖,用來找到指定的apk
  3.         Intent intent = new Intent("com.suchangli.android.plugin"null);  
  4.         //獲得包管理器
  5.         PackageManager pm = getPackageManager();  
  6.         List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);  
  7.         //獲得指定的activity的資訊
  8.         ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;  
  9.         //獲得包名
  10.         String pacageName = actInfo.packageName;  
  11.         try {  
  12.             Resources res = pm.getResourcesForApplication(pacageName);  
  13.             int id = 0;  
  14.             id = res.getIdentifier("ic_launcher""drawable", pacageName);  
  15.             Log.i("""resId is " + id);  
  16.         } catch (NameNotFoundException e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
從上面的程式碼可以看出,我們能獲得外掛程式的資原始檔的id,那麼資原始檔就很容易獲得了,使用外掛的形式進行主題替換就會很容易實現了。 期待大家能在DexClassLoader的基礎上開發出一個開源的外掛框架,或者通用的程式主題替換程式架構。 如果想一塊寫這種開源框架的可以和我聯絡,我能幫忙的儘量幫忙。謝謝大家了。