dynamic-load-apk外掛程式原理整理,dynamicloadapk

來源:互聯網
上載者:User

dynamic-load-apk外掛程式原理整理,dynamicloadapk

  因為當前項目功能越來越多,編譯速度越來越慢(公司電腦配置也挺差的...),並且方法數已超出65535的限制了,雖然通過multidex暫時解決了,但是這並不是一個好的解決方式。所以通過外掛程式來加快編譯速度以及解決方案數的限制,算是一個越來越重要的任務了,工作中還有很多新需求,所以趁放假的2天研究了下現在比較流行的外掛程式架構dynamic-load-apk,並整理了下。

 

架構github地址:https://github.com/singwhatiwanna/dynamic-load-apk

lib module的svn地址:https://github.com/singwhatiwanna/dynamic-load-apk/trunk/DynamicLoadApk/lib

 

一、載入apk總流程:

//外掛程式檔案File plugin = new File(apkPath);PluginItem item = new PluginItem();//外掛程式檔案路徑item.pluginPath = plugin.getAbsolutePath();//PackageInfo = PackageManager.getPackageArchiveInfoitem.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);//launcherActivityif (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {   item.launcherActivityName = item.packageInfo.activities[0].name;}//launcherServiceif (item.packageInfo.services != null && item.packageInfo.services.length > 0) {    item.launcherServiceName = item.packageInfo.services[0].name;}//載入apk資訊DLPluginManager.getInstance(this).loadApk(item.pluginPath);

 

 

二、loadApk資訊過程:
1、createDexClassLoader:

private DexClassLoader createDexClassLoader(String dexPath) {    dexOutputPath = mContext.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath();    DexClassLoader loader = new DexClassLoader(dexPath,            dexOutputPath,  //getDir("dex", Context.MODE_PRIVATE)            mNativeLibDir,  //optimizedDirectory=getDir("pluginlib", Context.MODE_PRIVATE)            mContext.getClassLoader());  //host.Appliceation.getClassLoader()    return loader;}

 

2、createAssetManager:

private AssetManager createAssetManager(String dexPath) {    try {        AssetManager assetManager = AssetManager.class.newInstance();        //通過反射調用addAssetPath方法,將apk資源載入到AssetManager        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);        addAssetPath.invoke(assetManager, dexPath);        return assetManager;    } catch (Exception e) {        e.printStackTrace();        return null;    }}

後面會重寫DLProxyActivity的getAssets()方法,返回此處產生的AssetManager,從而實現從外掛程式apk載入資源:

@Overridepublic AssetManager getAssets() {    return impl.getAssets() == null ? super.getAssets() : impl.getAssets();}

 

3、createResources:

private Resources createResources(AssetManager assetManager) {    //通過剛建立的assetManager以及宿主程式的Resources建立Plugin的Resources    Resources superRes = mContext.getResources();    Resources resources = new Resources(assetManager,            superRes.getDisplayMetrics(),            superRes.getConfiguration());    return resources;}

後面會重寫DLProxyActivity的getResources()方法,返回此處產生的Resources,從而實現從外掛程式apk載入資源:

@Overridepublic Resources getResources() {    return impl.getResources() == null ? super.getResources() : impl.getResources();}

 

4、建立pluginPackage並通過外掛程式的packageName儲存外掛程式資訊:
pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
mPackagesHolder.put(packageInfo.packageName, pluginPackage);

5、copySoLib(拷貝so檔案到應用的pluginlib目錄下):
SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir);

 

三、調用外掛程式:
1、要向外掛程式Intent傳遞可序列化對象,必須通過DLIntent,設定Bundle的ClassLoader:

@Overridepublic Intent putExtra(String name, Parcelable value) {    setupExtraClassLoader(value);    return super.putExtra(name, value);}@Overridepublic Intent putExtra(String name, Serializable value) {    setupExtraClassLoader(value);    return super.putExtra(name, value);}private void setupExtraClassLoader(Object value) {    ClassLoader pluginLoader = value.getClass().getClassLoader();    DLConfigs.sPluginClassloader = pluginLoader;    setExtrasClassLoader(pluginLoader); //設定Bundle的ClassLoader}

 

2、startPluginActivity:
外掛程式內部的activity之間相互調用,需要使用此方法。

public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {    if (mFrom == DLConstants.FROM_INTERNAL) {        dlIntent.setClassName(context, dlIntent.getPluginClass());        performStartActivityForResult(context, dlIntent, requestCode);        return DLPluginManager.START_RESULT_SUCCESS;    }    String packageName = dlIntent.getPluginPackage();    //驗證intent的包名    if (TextUtils.isEmpty(packageName)) {        throw new NullPointerException("disallow null packageName.");    }    //檢測外掛程式是否載入    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);    if (pluginPackage == null) {        return START_RESULT_NO_PKG;    }    //要調用的外掛程式Activity的class完整路徑    final String className = getPluginActivityFullPath(dlIntent, pluginPackage);    //Class.forName    Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);    if (clazz == null) {        return START_RESULT_NO_CLASS;    }    //擷取代理Activity的class,DLProxyActivity/DLProxyFragmentActivity    Class<? extends Activity> proxyActivityClass = getProxyActivityClass(clazz);    if (proxyActivityClass == null) {        return START_RESULT_TYPE_ERROR;    }    //put extra data    dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);    dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);    dlIntent.setClass(mContext, proxyActivityClass);    //通過context啟動宿主Activity    performStartActivityForResult(context, dlIntent, requestCode);    return START_RESULT_SUCCESS;}

 

 

四、Activity生命週期的管理:
外掛程式apk中的activity其實就是一個普通的對象,不是真正意義上的activity(沒有在宿主程式中註冊且沒有完全初始化),不具有activity的性質,因為系統啟動activity是要做很多初始化工作的,而我們在應用程式層通過反射去啟動activity是很難完成系統所做的初始化工作的,所以activity的大部分特性都無法使用包括activity的生命週期管理,這就需要我們自己去管理。
DL採用了介面機制,將activity的大部分生命週期方法提取出來作為一個介面(DLPlugin),然後通過代理activity(DLProxyActivity)去調用外掛程式activity實現的生命週期方法,這樣就完成了外掛程式activity的生命週期管理,並且沒有採用反射,當我們想增加一個新的生命週期方法的時候,只需要在介面中聲明一下同時在代理activity中實現一下即可。

public interface DLPlugin {    public void onCreate(Bundle savedInstanceState);    public void onStart();    public void onRestart();    public void onActivityResult(int requestCode, int resultCode, Intent data);    public void onResume();    public void onPause();    public void onStop();    public void onDestroy();    public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);    public void onSaveInstanceState(Bundle outState);    public void onNewIntent(Intent intent);    public void onRestoreInstanceState(Bundle savedInstanceState);    public boolean onTouchEvent(MotionEvent event);    public boolean onKeyUp(int keyCode, KeyEvent event);    public void onWindowAttributesChanged(LayoutParams params);    public void onWindowFocusChanged(boolean hasFocus);    public void onBackPressed();    public boolean onCreateOptionsMenu(Menu menu);    public boolean onOptionsItemSelected(MenuItem item);}

 

DLBasePluginActivity的部分實現:

public class DLBasePluginActivity extends Activity implements DLPlugin {    /**     * 代理activity,可以當作Context來使用,會根據需要來決定是否指向this     */    protected Activity mProxyActivity;    /**     * 等同於mProxyActivity,可以當作Context來使用,會根據需要來決定是否指向this<br/>     * 替代this來使用(應為this指向的是外掛程式中的Activity,已經不是常規意義上的activity,所以this是沒有意義的)     * 如果是DLPlugin中已經覆蓋的Activity的方法,就不需使用that了,直接調用this即可     */    protected Activity that;    protected DLPluginManager mPluginManager;    protected DLPluginPackage mPluginPackage;    protected int mFrom = DLConstants.FROM_INTERNAL;    @Override    public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {        mProxyActivity = (Activity) proxyActivity;        that = mProxyActivity;        mPluginPackage = pluginPackage;    }    @Override    public void onCreate(Bundle savedInstanceState) {        if (savedInstanceState != null) {            mFrom = savedInstanceState.getInt(DLConstants.FROM, DLConstants.FROM_INTERNAL);        }        if (mFrom == DLConstants.FROM_INTERNAL) {            super.onCreate(savedInstanceState);            mProxyActivity = this;            that = mProxyActivity;        }        mPluginManager = DLPluginManager.getInstance(that);    }    @Override    public void setContentView(View view) {        if (mFrom == DLConstants.FROM_INTERNAL) {            super.setContentView(view);        } else {            mProxyActivity.setContentView(view);        }    }    ......}

 

在代理類DLProxyActivity中的實現:

public class DLProxyActivity extends Activity implements DLAttachable {    protected DLPlugin mRemoteActivity;    private DLProxyImpl impl = new DLProxyImpl(this);    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        impl.onCreate(getIntent());    }    @Override    public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {        mRemoteActivity = remoteActivity;    }    @Override    public AssetManager getAssets() {        return impl.getAssets() == null ? super.getAssets() : impl.getAssets();    }    @Override    public Resources getResources() {        return impl.getResources() == null ? super.getResources() : impl.getResources();    }    @Override    public Theme getTheme() {        return impl.getTheme() == null ? super.getTheme() : impl.getTheme();    }    @Override    public ClassLoader getClassLoader() {        return impl.getClassLoader();    }    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        mRemoteActivity.onActivityResult(requestCode, resultCode, data);        super.onActivityResult(requestCode, resultCode, data);    }    @Override    protected void onStart() {        mRemoteActivity.onStart();        super.onStart();    }    ......}

 

總結:

外掛程式主要的2個問題就是資源載入以及Activity生命週期的管理。

資源載入:

通過反射調用AssetManager的addAssetPath方法,我們可以將一個外掛程式apk中的資源載入到AssetManager中,然後再通過AssetManager來建立一個新的Resources對象,然後就可以通過這個Resources對象來訪問外掛程式apk中的資源了。

Activity生命週期管理:

採用介面機制,將activity的大部分生命週期方法提取出來作為一個介面(DLPlugin),然後通過代理activity(DLProxyActivity)去調用外掛程式activity實現的生命週期方法,這樣就完成了外掛程式activity的生命週期管理。

 

另外,一個需要注意的地方:

外掛程式項目引用 android-support-v4.jar、lib.jar等libs,產生apk時不能將這些打包到apk,只在編譯時間引用,只有host項目裡才編譯並打包,保證host以及外掛程式中的代碼只有一份。

在studio裡面使用provided而非compile:

dependencies {
  provided files('provide-jars/android-support-v4.jar')
  provided files('provide-jars/lib.jar')
}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.