動態加載APK原理分享

(一) 綜述

    隨着智能手機硬件性能的逐步提高,移動應用也作的愈來愈複雜,android平臺上應用的apk包體積也愈來愈大,而後同類產品開始比拼誰的體積小,實現方案呢,而後很容易想到"插件化",也就是說能夠發佈內核很小的產品,隨着添加功能的需求而動態下載功能模塊,促使插件化的另外一個動機是App應用固有的問題,那就是不少組件須要註冊,更新功能的話不能像Web應用那樣可在用戶無察覺的狀況下經過升級服務器而方便升級,只能彈出個框讓用戶從新下載整個程序包,而後調取系統安裝流程。
 
    被加載的apk稱之爲插件,由於機制相似於生物學的"寄生",加載了插件的應用也被稱爲宿主。
每每不是全部的apk均可做爲插件被加載,每每須要遵循必定的"開發規範",還須要插件項目引入某種api類庫,業界一般都是這麼作的。
這裏介紹一種無須規範限制的動態加載解決方案,插件不須要依賴任何API,這也是本人突發異想,靈感所致。
項目地址:https://github.com/houkx/android-pluginmgr/歡迎star andfork java

(二)功能介紹

      特色: android

  •      插件爲普通apk,無須依賴任何jar git

  •     Activity生命週期由系統本身管理 github

  •     使用簡單,只須要了解一個類PluginManager的兩個方法 數據庫

  •     啓動Activity的效率高 api

  •     不修改插件,被加載的插件仍然能夠獨立安裝。 服務器

     功能點: app

  1. 可加載任意apk中的 Activity (包括子類 ActionBarActivity 、FragmentActivity)的派生類(不包括違反限制條件的Activity) 框架

  2. 支持插件自定義Application ide

  3. 支持插件Apk中的Activity跳轉到別的Activity(插件內部的或系統的,外部已安裝apk的,甚至是別的插件中的),也沒有任何限制

  4. 支持Activity設置主題(與系統的主題應用規則同樣,若是Activity沒指定Theme,但所在Application指定了Theme,則使用Application的Theme)

  5. 初步支持.so

  6. 支持插件使用 SharedPreference 或 SQLite數據庫(還沒有完善)

 



將要支持的特性:
        PackageManager service 等等,詳情都列在開源項目android-pluginmgr
  https://github.com/houkx/android-pluginmgr/tree/experiment/android-pluginmgr下的TODO文件
限制條件(永不支持的):

  •   插件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(類加載器結構圖):

  

package androidx.pluginmgr;


import android.util.Log;


/**
 * 框架類加載器(Application 的 classLoder被替換成此類的實例)
 * 
 *
 */
class FrameworkClassLoader extends ClassLoader {
	private String[] plugIdAndActname;//表明插件上下文


	public FrameworkClassLoader(ClassLoader parent) {
		super(parent);
	}
    //在外部或插件內部的 startActivity 時調用這個方法設置插件上下文
	String newActivityClassName(String plugId, String actName) {
		plugIdAndActname = new String[] { plugId, actName };
		return ActivityOverider.targetClassName;//targetClassName即宿主manifest配置的androidx.pluginmgr.PluginActivity
	}


	protected Class<?> loadClass(String className, boolean resolv)
			throws ClassNotFoundException {
		Log.i("cl", "loadClass: " + className);
		String[] plugIdAndActname = this.plugIdAndActname;
		Log.i("cl", "plugIdAndActname = " + java.util.Arrays.toString(plugIdAndActname));
		if (plugIdAndActname != null) {
			String pluginId = plugIdAndActname[0];
			
			PlugInfo plugin = PluginManager.getInstance().getPluginById(
					pluginId);
			Log.i("cl", "plugin = " + plugin);
			if (plugin != null) {
				try {
					if (className.equals(ActivityOverider.targetClassName)) {
						String actClassName = plugIdAndActname[1];//被(繼承)代理的Activity類名
						return plugin.getClassLoader().loadActivityClass(
								actClassName);// 加載動態生成的Activity類
					}else{
						return plugin.getClassLoader().loadClass(className);//加載普通類
					}
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
		
		return super.loadClass(className, resolv);
	}
}



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

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

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

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

package androidx.pluginmgr;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import androidplugdemo.SthActivity;

public final class PluginActivity
  extends SthActivity
{
  private static final String _pluginId = "activityTest_v1";
  private AssetManager mAssertManager;
  private Resources mResources;
  
  public boolean bindService(Intent paramIntent, ServiceConnection paramServiceConnection, int paramInt)
  {
    return ActivityOverider.overrideBindService(this, _pluginId, paramIntent, paramServiceConnection, paramInt);
  }
  
  public AssetManager getAssets()
  {
    AssetManager localAssetManager = this.mAssertManager;
    if (localAssetManager == null) {
      localAssetManager = super.getAssets();
    }
    return localAssetManager;
  }
  
  public Resources getResources()
  {
    Resources localResources = this.mResources;
    if (localResources == null) {
      localResources = super.getResources();
    }
    return localResources;
  }
  
  public void onBackPressed()
  {
    if (ActivityOverider.overrideOnbackPressed(this, _pluginId)) {
      super.onBackPressed();
    }
  }
  
  protected void onCreate(Bundle paramBundle)
  {
    String str = _pluginId;
    AssetManager localAssetManager = ActivityOverider.getAssetManager(str, this);
    this.mAssertManager = localAssetManager;
    Resources localResources = super.getResources();
    this.mResources = new Resources(localAssetManager, localResources.getDisplayMetrics(), localResources.getConfiguration());
    ActivityOverider.callback_onCreate(str, this);
    super.onCreate(paramBundle);
  }
  
  protected void onDestroy()
  {
    String str = _pluginId;
    super.onDestroy();
    ActivityOverider.callback_onDestroy(str, this);
  }
  
  protected void onPause()
  {
    String str = _pluginId;
    super.onPause();
    ActivityOverider.callback_onPause(str, this);
  }
  
  protected void onRestart()
  {
    String str = _pluginId;
    super.onRestart();
    ActivityOverider.callback_onRestart(str, this);
  }
  
  protected void onResume()
  {
    String str = _pluginId;
    super.onResume();
    ActivityOverider.callback_onResume(str, this);
  }
  
  protected void onStart()
  {
    String str = _pluginId;
    super.onStart();
    ActivityOverider.callback_onStart(str, this);
  }
  
  protected void onStop()
  {
    String str = _pluginId;
    super.onStop();
    ActivityOverider.callback_onStop(str, this);
  }
  
  public void startActivityForResult(Intent paramIntent, int paramInt, Bundle paramBundle)
  {
    super.startActivityForResult(ActivityOverider.overrideStartActivityForResult(this, _pluginId, paramIntent, paramInt, paramBundle), paramInt, paramBundle);
  }
  
  public ComponentName startService(Intent paramIntent)
  {
    return ActivityOverider.overrideStartService(this, _pluginId, paramIntent);
  }
  
  public boolean stopService(Intent paramIntent)
  {
    return ActivityOverider.overrideStopService(this, _pluginId, paramIntent);
  }
  
  public void unbindService(ServiceConnection paramServiceConnection)
  {
    ActivityOverider.overrideUnbindService(this, _pluginId, paramServiceConnection);
  }
}


(ps:

第一個插件代碼來自 https://github.com/viacheslavtitov/NDKBegining

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

第二個插件代碼來自這篇博文這篇博文

第三個插件代碼來自大名鼎鼎的 Pulltorefresh

第6第7個插件代碼是自測項目,分別測試ActionBarActivity和Activity基本加載和跳轉

其餘apk還沒能正常加載,框架還在不斷完善中,不過騰訊的開心消消樂能夠幫助記錄crashlog,這點真不錯~

相關文章
相關標籤/搜索