動態載入APK原理分享

yangxi_001發表於2016-01-07

轉自:http://blog.csdn.net/hkxxx/article/details/42194387

(一) 綜述

    隨著智慧手機硬體效能的逐步提升,移動應用也做的越來越複雜,android平臺上應用的apk包體積也越來越大,然後同類產品開始比拼誰的體積小,實現方案呢,然後很容易想到"外掛化",也就是說可以釋出核心很小的產品,隨著新增功能的需求而動態下載功能模組,促使外掛化的另一個動機是App應用固有的問題,那就是很多元件需要註冊,更新功能的話不能像Web應用那樣可在使用者無察覺的情況下通過升級伺服器而方便升級,只能彈出個框讓使用者重新下載整個程式包,然後調取系統安裝流程。
 
    被載入的apk稱之為外掛,因為機制類似於生物學的"寄生",載入了外掛的應用也被稱為宿主。
往往不是所有的apk都可作為外掛被載入,往往需要遵循一定的"開發規範",還需要外掛專案引入某種api類庫,業界通常都是這麼做的。
這裡介紹一種無須規範限制的動態載入解決方案,外掛不需要依賴任何API,這也是本人突發異想,靈感所致。


(二)功能介紹

      特點:
  •      外掛為普通apk,無須依賴任何jar
  •     Activity生命週期由系統自己管理
  •     使用簡單,只需要瞭解一個類PluginManager的兩個方法
  •     啟動Activity的效率高
  •     不修改外掛,被載入的外掛仍然可以獨立安裝。
     功能點:
  1. 可載入任意apk中的 Activity (包括子類 ActionBarActivity 、FragmentActivity)的派生類(不包括違反限制條件的Activity)
  2. 支援外掛自定義Application
  3. 支援外掛Apk中的Activity跳轉到別的Activity(外掛內部的或系統的,外部已安裝apk的,甚至是別的外掛中的),也沒有任何限制
  4. 支援Activity設定主題(與系統的主題應用規則一樣,如果Activity沒指定Theme,但所在Application指定了Theme,則使用Application的Theme)
  5. 初步支援.so
  6. 支援外掛使用 SharedPreference 或 SQLite資料庫(尚未完善)

 

(ps:

第一個外掛程式碼來自 https://github.com/viacheslavtitov/NDKBegining

作者是個老外,不過也比較粗心,要正常執行你需要在sd卡下建立目錄:FFMPEG

第二個外掛程式碼來自這篇博文:http://blog.csdn.net/caihanyuan/article/details/7367351

第三個外掛程式碼來自大名鼎鼎的 PullToRefresh :

https://github.com/johannilsson/android-pulltorefresh

第6第7個外掛程式碼是自測專案,分別測試ActionBarActivity和Activity基本載入和跳轉
其他apk還沒能正常載入,框架還在不斷完善中,不過騰訊的開心消消樂可以幫助記錄crashlog,這點真不錯~


將要支援的特性:
        PackageManager service 等等,詳情都列在開源專案android-pluginmgr
  https://github.com/houkx/android-pluginmgr/tree/experiment/android-pluginmgr下的TODO檔案中

(ps:修正專案主頁README描述的限制條件1,目前已經沒有這個限制了,在外掛中呼叫context.getPackageName()返回外掛的清單檔案中宣告的包名)
限制條件(永不支援的):
  •   外掛apk中不能假定自己已經安裝,以及由此造成的影響,比如認為applicationInfo.dataDir==/data/data/packageName
  •           不能依賴清單檔案中的程式宣告,被載入的apk以及裡面的任何元件目前都在同一個程式管理。
  •   外掛中的許可權,無法動態註冊,外掛中的許可權都得在宿主中註冊(暫無解決方案)

(三) 實現

    動態載入需要處理很多問題,雖然有很多問題,但是核心問題就是載入Activity,因為Activity是可見的,人們對可以看到的東西總是那麼重視,視覺資訊占人所處理資訊的90%以上。
Activity如何調起來?資源的載入等等已經有大牛的文章介紹汗牛充棟了,我本菜鳥,不再贅述。 
目前Activity的載入或許有很多處理方式,但是可以分為兩種:一是自己new 二是系統new 。很多動態載入框架基於第一種方式。我這個方案基於第二種
,既然要系統new,就要系統自己可以找到相應的Activity. 由於Activity需要在清單檔案註冊了才能使用,所以要註冊Activity,但是如何註冊呢?
我在網上看到有人用極端的方式:外掛裡的所有Activiy都在宿主裡註冊,既然宿主總要修改升級,何必要外掛呢,這已經違背了動態載入的初衷:不修改框架而動態擴充套件功能更多的是這麼做,註冊一個Activity基類,供外掛中的Activity繼承,在這個基類裡做動態載入的核心邏輯,這就要求外掛必須依賴某種API類庫。
我的方案通俗的說是這樣,依賴倒轉,不讓外掛依賴框架API,而是反過來,自動生成一個Activity類依賴(繼承)外掛中的Activity,這個自動生成的類就叫PluginActivity
並且宣告在框架的清單檔案中,如下: 
<activity name="androidx.pluginmgr.PluginActivity" /> 
聰明的讀者會想,等一下,外掛裡面Activity可不止一個,你就註冊一個?
是的,就一個,自動生成的Activity類名都是androidx.pluginmgr.PluginActivity,不過放在不同的檔案中,最簡單的對映,原始Activity類名.dex檔案中儲存對應的子類:PluginActivity
其實也是偷樑換柱了,如果你想啟動外掛裡的Activity,如com.test.MyPlugActivity, 我就把啟動目標修改為androidx.pluginmgr.PluginActivity類,
然後從com.test.MyPlugActivity.dex檔案中找到 public class androidx.pluginmgr.PluginActivity extends com.test.MyPlugActivity{....}

以啟動SthActivity為例:



好了,核心思想已經表達清楚了,下面介紹如何讓系統按你說的路徑去找類檔案,這涉及到類載入器。自定義類載入器比較簡單,繼承java.lang.ClassLoader即可.
在我的開源專案原始碼中對應的類是 FrameworkClassLoader, PluginManager初始化時就去修改Application的類載入器,替換為 FrameworkClassLoader.
FrameworkClassLoader 其實不幹什麼實際載入工作,只是分發任務:
            public Class loadClass(String className){
  if(當前上下文外掛不為空) {
      if( className 是 PluginActivity){
          找到當前實際要載入的原始 Activity
         return  使用外掛對應的 ActivityClassLoader 從 (自動生成的)原始Activity類名.dex 檔案 載入PluginActivity
      }else{
         return  使用對應的 PluginClassLoader 載入普通類
      }  
   }else{
         return super.loadClass()//即委派給宿主Application的原始類載入器載入
   }   
}
  其中, PluginClassLoader 是一個DexClassLoader, parent 指向 FrameworkClassLoader,
  ActivityClassLoader 也是一個DexClassLoader, parent 指向 PluginClassLoader

  

  插圖2(類載入器結構圖):


  
[java] view plaincopy
  1. package androidx.pluginmgr;  
  2.   
  3.   
  4. import android.util.Log;  
  5.   
  6.   
  7. /** 
  8.  * 框架類載入器(Application 的 classLoder被替換成此類的例項) 
  9.  *  
  10.  * 
  11.  */  
  12. class FrameworkClassLoader extends ClassLoader {  
  13.     private String[] plugIdAndActname;//代表外掛上下文  
  14.   
  15.   
  16.     public FrameworkClassLoader(ClassLoader parent) {  
  17.         super(parent);  
  18.     }  
  19.     //在外部或外掛內部的 startActivity 時呼叫這個方法設定外掛上下文  
  20.     String newActivityClassName(String plugId, String actName) {  
  21.         plugIdAndActname = new String[] { plugId, actName };  
  22.         return ActivityOverider.targetClassName;//targetClassName即宿主manifest配置的androidx.pluginmgr.PluginActivity  
  23.     }  
  24.   
  25.   
  26.     protected Class<?> loadClass(String className, boolean resolv)  
  27.             throws ClassNotFoundException {  
  28.         Log.i("cl""loadClass: " + className);  
  29.         String[] plugIdAndActname = this.plugIdAndActname;  
  30.         Log.i("cl""plugIdAndActname = " + java.util.Arrays.toString(plugIdAndActname));  
  31.         if (plugIdAndActname != null) {  
  32.             String pluginId = plugIdAndActname[0];  
  33.               
  34.             PlugInfo plugin = PluginManager.getInstance().getPluginById(  
  35.                     pluginId);  
  36.             Log.i("cl""plugin = " + plugin);  
  37.             if (plugin != null) {  
  38.                 try {  
  39.                     if (className.equals(ActivityOverider.targetClassName)) {  
  40.                         String actClassName = plugIdAndActname[1];//被(繼承)代理的Activity類名  
  41.                         return plugin.getClassLoader().loadActivityClass(  
  42.                                 actClassName);// 載入動態生成的Activity類  
  43.                     }else{  
  44.                         return plugin.getClassLoader().loadClass(className);//載入普通類  
  45.                     }  
  46.                 } catch (ClassNotFoundException e) {  
  47.                     e.printStackTrace();  
  48.                 }  
  49.             }  
  50.         }  
  51.           
  52.         return super.loadClass(className, resolv);  
  53.     }  
  54. }  


到此為止,一個動態載入框架的核心雛形已經有了,但是還有許多細節待完善。

------------------------------------------------------------------------------------------------------------------------

附加:另外,關於動態生成類,對於davikvm環境,我所知道的工具有asmdex和dexmaker,我在專案中選用的是dexmaker

用什麼不是重點,看生成的程式碼吧:(ActivityOverider 類負責與自動生成的Activity類互動)

[java] view plaincopy
  1. package androidx.pluginmgr;  
  2.   
  3. import android.content.ComponentName;  
  4. import android.content.Intent;  
  5. import android.content.ServiceConnection;  
  6. import android.content.res.AssetManager;  
  7. import android.content.res.Resources;  
  8. import android.os.Bundle;  
  9. import androidplugdemo.SthActivity;  
  10.   
  11. public final class PluginActivity  
  12.   extends SthActivity  
  13. {  
  14.   private static final String _pluginId = "activityTest_v1";  
  15.   private AssetManager mAssertManager;  
  16.   private Resources mResources;  
  17.     
  18.   public boolean bindService(Intent paramIntent, ServiceConnection paramServiceConnection, int paramInt)  
  19.   {  
  20.     return ActivityOverider.overrideBindService(this, _pluginId, paramIntent, paramServiceConnection, paramInt);  
  21.   }  
  22.     
  23.   public AssetManager getAssets()  
  24.   {  
  25.     AssetManager localAssetManager = this.mAssertManager;  
  26.     if (localAssetManager == null) {  
  27.       localAssetManager = super.getAssets();  
  28.     }  
  29.     return localAssetManager;  
  30.   }  
  31.     
  32.   public Resources getResources()  
  33.   {  
  34.     Resources localResources = this.mResources;  
  35.     if (localResources == null) {  
  36.       localResources = super.getResources();  
  37.     }  
  38.     return localResources;  
  39.   }  
  40.     
  41.   public void onBackPressed()  
  42.   {  
  43.     if (ActivityOverider.overrideOnbackPressed(this, _pluginId)) {  
  44.       super.onBackPressed();  
  45.     }  
  46.   }  
  47.     
  48.   protected void onCreate(Bundle paramBundle)  
  49.   {  
  50.     String str = _pluginId;  
  51.     AssetManager localAssetManager = ActivityOverider.getAssetManager(str, this);  
  52.     this.mAssertManager = localAssetManager;  
  53.     Resources localResources = super.getResources();  
  54.     this.mResources = new Resources(localAssetManager, localResources.getDisplayMetrics(), localResources.getConfiguration());  
  55.     ActivityOverider.callback_onCreate(str, this);  
  56.     super.onCreate(paramBundle);  
  57.   }  
  58.     
  59.   protected void onDestroy()  
  60.   {  
  61.     String str = _pluginId;  
  62.     super.onDestroy();  
  63.     ActivityOverider.callback_onDestroy(str, this);  
  64.   }  
  65.     
  66.   protected void onPause()  
  67.   {  
  68.     String str = _pluginId;  
  69.     super.onPause();  
  70.     ActivityOverider.callback_onPause(str, this);  
  71.   }  
  72.     
  73.   protected void onRestart()  
  74.   {  
  75.     String str = _pluginId;  
  76.     super.onRestart();  
  77.     ActivityOverider.callback_onRestart(str, this);  
  78.   }  
  79.     
  80.   protected void onResume()  
  81.   {  
  82.     String str = _pluginId;  
  83.     super.onResume();  
  84.     ActivityOverider.callback_onResume(str, this);  
  85.   }  
  86.     
  87.   protected void onStart()  
  88.   {  
  89.     String str = _pluginId;  
  90.     super.onStart();  
  91.     ActivityOverider.callback_onStart(str, this);  
  92.   }  
  93.     
  94.   protected void onStop()  
  95.   {  
  96.     String str = _pluginId;  
  97.     super.onStop();  
  98.     ActivityOverider.callback_onStop(str, this);  
  99.   }  
  100.     
  101.   public void startActivityForResult(Intent paramIntent, int paramInt, Bundle paramBundle)  
  102.   {  
  103.     super.startActivityForResult(ActivityOverider.overrideStartActivityForResult(this, _pluginId, paramIntent, paramInt, paramBundle), paramInt, paramBundle);  
  104.   }  
  105.     
  106.   public ComponentName startService(Intent paramIntent)  
  107.   {  
  108.     return ActivityOverider.overrideStartService(this, _pluginId, paramIntent);  
  109.   }  
  110.     
  111.   public boolean stopService(Intent paramIntent)  
  112.   {  
  113.     return ActivityOverider.overrideStopService(this, _pluginId, paramIntent);  
  114.   }  
  115.     
  116.   public void unbindService(ServiceConnection paramServiceConnection)  
  117.   {  
  118.     ActivityOverider.overrideUnbindService(this, _pluginId, paramServiceConnection);  
  119.   }  
  120. }  
專案地址:https://github.com/houkx/android-pluginmgr/

相關文章