Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android資訊 >> Android 動態加載技術三個關鍵問題詳解

Android 動態加載技術三個關鍵問題詳解

編輯:Android資訊

動態加載技術(也叫插件化技術)在技術驅動型的公司中扮演著相當重要的角色,當項目越來越龐大的時候,需要通過插件化來減輕應用的內存和CPU占用,還可以實現熱插拔,即在不發布新版本的情況下更新某些模塊。動態加載是一項很復雜的技術,這裡主要介紹動態加載技術中的三個基礎性問題,至於完整的動態加載技術的實現請參考筆者發起的開源插件化框架DL:。項目期間有多位開發人員一起貢獻代碼。

不同的插件化方案各有各的特色,但是它們都必須要解決三個基礎性問題:資源訪問、Activity生命周期的管理和ClassLoader的管理。在介紹它們之前,首先要明白宿主和插件的概念,宿主是指普通的apk,而插件一般是指經過處理的dex或者apk,在主流的插件化框架中多采用經過特殊處理的apk來作為插件,處理方式往往和編譯以及打包環節有關,另外很多插件化框架都需要用到代理Activity的概念,插件Activity的啟動大多數是借助一個代理Activity來實現的。

1.資源訪問

我們知道,宿主程序調起未安裝的插件apk,一個很大的問題就是資源如何訪問,具體來說就是插件中凡是以R開頭的資源都不能訪問了。這是因為宿主程序中並沒有插件的資源,所以通過R來加載插件的資源是行不通的,程序會拋出異常:無法找到某某id所對應的資源。

針對這個問題,有人提出了將插件中的資源在宿主程序中也預置一份,這雖然能解決問題,但是這樣就會產生一些弊端。首先,這樣就需要宿主和插件同時持有一份相同的資源,增加了宿主apk的大小;其次,在這種模式下,每次發布一個插件都需要將資源復制到宿主程序中,這意味著每發布一個插件都要更新一下宿主程序,這就和插件化的思想相違背了。

因為插件化的目的就是要減小宿主程序apk包的大小,同時降低宿主程序的更新頻率並做到自由裝載模塊,所以這種方法不可取,它限制了插件的線上更新這一重要特性。還有人提供了另一種方式,首先將插件中的資源解壓出來,然後通過文件流去讀取資源,這樣做理論上是可行的,但是實際操作起來還是有很大難度的。首先不同資源有不同的文件流格式,比如圖片、XML等,其次針對不同設備加載的資源可能是不一樣的,如何選擇合適的資源也是一個需要解決的問題,基於這兩點,這種方法也不建議使用,因為它實現起來有較大難度。為了方便地對插件進行資源管理,下面給出一種合理的方式。

我們知道,Activity的工作主要是通過ContextImpl來完成的, Activity中有一個叫mBase的成員變量,它的類型就是ContextImpl。注意到Context中有如下兩個抽象方法,看起來是和資源有關的,實際上Context就是通過它們來獲取資源的。這兩個抽象方法的真正實現在ContextImpl中,也就是說,只要實現這兩個方法,就可以解決資源問題了。

/** Return an AssetManager instance for your application's package. */

public abstract AssetManager getAssets();

/** Return a Resources instance for your application's package. */

public abstract Resources getResources();

下面給出具體的實現方式,首先要加載apk中的資源,如下所示。

protected void loadResources() {
	try {
		AssetManager assetManager = AssetManager.class.newInstance();
		Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
		addAssetPath.invoke(assetManager, mDexPath);
		mAssetManager = assetManager;
	} catch (Exception e) {
		e.printStackTrace();
	}
	Resources superRes = super.getResources();
	mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
			superRes.getConfiguration());
	mTheme = mResources.newTheme();
	mTheme.setTo(super.getTheme());
}

從loadResources()的實現可以看出,加載資源的方法是通過反射,通過調用AssetManager中的addAssetPath方法,我們可以將一個apk中的資源加載到Resources對象中,由於addAssetPath是隱藏API我們無法直接調用,所以只能通過反射。下面是它的聲明,通過注釋我們可以看出,傳遞的路徑可以是zip文件也可以是一個資源目錄,而apk就是一個zip,所以直接將apk的路徑傳給它,資源就加載到AssetManager中了。然後再通過AssetManager來創建一個新的Resources對象,通過這個對象我們就可以訪問插件apk中的資源了,這樣一來問題就解決了。

/**

 * Add an additional set of assets to the asset manager.  This can be

 * either a directory or ZIP file.  Not for use by applications.  Returns

 * the cookie of the added asset, or 0 on failure.

 * {@hide}

 */

public final int addAssetPath(String path) {

    synchronized (this) {

        int res = addAssetPathNative(path);

        makeStringBlocks(mStringBlocks);

        return res;

    }

}

接著在代理Activity中實現getAssets()和getResources(),如下所示。關於代理Activity的含義請參看DL開源插件化框架的實現細節,這裡不再詳細描述了。

@Override

public AssetManager getAssets() {

    return mAssetManager == null ? super.getAssets() : mAssetManager;

}

@Override

public Resources getResources() {

    return mResources == null ? super.getResources() : mResources;

}

通過上述這兩個步驟,就可以通過R來訪問插件中的資源了。

2.Activity生命周期的管理

管理Activity生命周期的方式各種各樣,這裡只介紹兩種:反射方式和接口方式。反射的方式很好理解,首先通過Java的反射去獲取Activity的各種生命周期方法,比如onCreate、onStart、onResume等,然後在代理Activity中去調用插件Activity對應的生命周期方法即可,如下所示。

@Override

protected void onResume() {

    super.onResume();

    Method onResume = mActivityLifecircleMethods.get("onResume");

    if (onResume != null) {

        try {

            onResume.invoke(mRemoteActivity, new Object[] { });

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

@Override

protected void onPause() {

    Method onPause = mActivityLifecircleMethods.get("onPause");

    if (onPause != null) {

        try {

            onPause.invoke(mRemoteActivity, new Object[] { });

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

    super.onPause();

}

使用反射來管理插件Activity的生命周期是有缺點的,一方面是反射代碼寫起來比較復雜,另一方面是過多使用反射會有一定的性能開銷。下面介紹接口方式,接口方式很好地解決了反射方式的不足之處,這種方式將Activity的生命周期方法提取出來作為一個接口(比如叫DLPlugin),然後通過代理Activity去調用插件Activity的生命周期方法,這樣就完成了插件Activity的生命周期管理,並且沒有采用反射,這就解決了性能問題。同時接口的聲明也比較簡單,下面是DLPlugin的聲明:

public interface DLPlugin {

    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 onCreate(Bundle savedInstanceState);

    public void setProxy(Activity proxyActivity, String dexPath);

    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();

…

}

在代理Activity中只需要按如下方式即可調用插件Activity的生命周期方法,這就完成了插件Activity的生命周期的管理。

...

@Override

protected void onStart() {

    mRemoteActivity.onStart();

    super.onStart();

}

@Override

protected void onRestart() {

    mRemoteActivity.onRestart();

    super.onRestart();

}

@Override

protected void onResume() {

    mRemoteActivity.onResume();

    super.onResume();

}

...

通過上述代碼應該不難理解接口方式對插件Activity生命周期的管理思想,其中mRemoteActivity就是DLPlugin的實現。

3.插件ClassLoader的管理

為了更好地對多插件進行支持,需要合理地去管理各個插件的DexClassLoader,這樣同一個插件就可以采用同一個ClassLoader去加載類,從而避免了多個ClassLoader加載同一個類時所引發的類型轉換錯誤。在下面的代碼中,通過將不同插件的ClassLoader存儲在一個HashMap中,這樣就可以保證不同插件中的類彼此互不干擾。

public class DLClassLoader extends DexClassLoader {

    private static final String TAG = "DLClassLoader";

    private static final HashMap<String, DLClassLoader> mPluginClassLoaders

    = new HashMap<String, DLClassLoader>();

    protected DLClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {

        super(dexPath, optimizedDirectory, libraryPath, parent);

    }

    /**

     * return a available classloader which belongs to different apk

     */

    public static DLClassLoader getClassLoader(String dexPath, Context

    context, ClassLoader parentLoader) {

        DLClassLoader dLClassLoader = mPluginClassLoaders.get(dexPath);

        if (dLClassLoader != null)

            return dLClassLoader;

        File dexOutputDir = context.getDir("dex", Context.MODE_PRIVATE);

        final String dexOutputPath = dexOutputDir.getAbsolutePath();

        dLClassLoader = new DLClassLoader(dexPath, dexOutputPath, null,

        parentLoader);

        mPluginClassLoaders.put(dexPath, dLClassLoader);

        return dLClassLoader;

    }

}

事實上插件化的技術細節非常多,這絕非一個章節的內容所能描述清楚的,另外插件化作為一種核心技術,需要開發者有較深的開發功底才能夠很好地理解,因此本節的內容更多是讓讀者對插件化開發有一個感性的了解,細節上還需要讀者自己去鑽研,也可以通過DL插件化框架去深入地學習。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved