Android 插件化资源加载,Android插件化——资源加载

本文详细阐述了在Android插件化开发中如何加载插件资源,包括通过仿照APK安装流程获取插件的Resources,利用AssetManager包装插件APK并生成资源,以及通过反射获取插件R.java中的资源ID。核心思路是通过宿主Context获取插件资源,并展示了关键步骤和代码实现。
摘要由CSDN通过智能技术生成

前言

资源,是APK包体积过大的病因之一。插件化技术将模块解耦,通过插件的形式加载。插件化技术中,每个插件都能够作为单独的APK独立运行。宿主启动插件的类,难免要涉及插件类中的资源问题。

那么,如何加载插件资源,就成为一个待解决的问题。

原理

参考APK打包流程:Android插件化基础-APK打包流程

Android工程在打包成apk时,会使用aapt将工程中的资源名与id在R.java中一一映射起来。

R.java

public static final int ic_launcher=0x7f060054;

public static final int ic_launcher_background=0x7f060055;

public static final int ic_launcher_foreground=0x7f060056;

public static final int notification_action_background=0x7f060057;

我们每次加载资源时,先要获取Resources。然后通过:

Drawable drawable = resources.getDrawable(resId);

获取对应的资源。

因此,我们的核心思路就是:获取插件的Resources和插件的resId。

实践

那么我们该如何获得插件的Resources呢?

ContextThemeWrapper.java

@Override

public Resources getResources() {

return getResourcesInternal();

}

private Resources getResourcesInternal() {

if (mResources == null) {

if (mOverrideConfiguration == null) {

mResources = super.getResources();

} else {

final Context resContext = createConfigurationContext(mOverrideConfiguration);

mResources = resContext.getResources();

}

}

return mResources;

}

Resources.java是App资源的管理类。

/**

* Create a new Resources object on top of an existing set of assets in an

* AssetManager.

*

* @param assets Previously created AssetManager.

* @param metrics Current display metrics to consider when

* selecting/computing resource values.

* @param config Desired device configuration to consider when

* selecting/computing resource values (optional).

*/

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {

this(null);

mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());

}

通过注释我们可以清晰的看到,真正让Resources与众不同的是AssetManager。

我们注意到这个构造方法在PackageParser#parseBaseApk方法中调用。在此我们可以想到,我们是不是可以仿照Apk的安装过程,为一个未安装的Apk创建一个Resources呢?

因此,我们继续看AssetManager.java:

/**

* Provides access to an application's raw asset files; see {@link Resources}

* for the way most applications will want to retrieve their resource data.

* This class presents a lower-level API that allows you to open and read raw

* files that have been bundled with the application as a simple stream of

* bytes.

*/

public final class AssetManager implements AutoCloseable {

...

}

通过注释我们可以看到,这个类提供了我们访问资源文件的方式。读一下源码,可以找到一个方法:

AssetManager.java

/**

* 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) {

return addAssetPathInternal(path, false);

}

通过注释我们可以看到,这个方法提供了包装ZIP文件的方法。注释中说这不是用于Applications。但我们知道APK文件其实就是ZIP文件。

看到这里我们的思路就有了。通过这个方法,我们将插件APK的path传入,包装一个AssetManager。然后用AssetManager生成Resources,那么这个Resources就是插件的Resources。虽然插件APK并未安装,但我们仿照了安装的流程。

通过上面的分析,我们能够得到一个获取插件Resources的方法:

/**

* 获取对应插件的Resource对象

* @param context 宿主apk的上下文

* @param pluginPath 插件apk的路径,带apk名

* @return

*/

public static Resources getPluginResources(Context context, String pluginPath) {

try {

AssetManager assetManager = AssetManager.class.newInstance();

// 反射调用方法addAssetPath(String path)

Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);

// 将插件Apk文件添加进AssetManager

addAssetPath.invoke(assetManager, pluginPath);

// 获取宿主apk的Resources对象

Resources superRes = context.getResources();

// 获取插件apk的Resources对象

Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());

return mResources;

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

至此我们获得了插件的Resources,我们通过获取资源,如Drawable的方法是:

Drawable drawable = resources.getDrawable(resId);

因此,我们还缺一个resId,即插件资源在插件R.java中对应的id。

我们可以通过反射的方式,获取R.java中的id:

/**

* 加载apk获得内部资源id

*

* @param context 宿主上下文

* @param pluginPath apk路径

*/

public static int getResId(Context context, String pluginPath, String apkPackageName, String resName) {

try {

//在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建

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

// 构建插件的DexClassLoader类加载器,参数:

// 1、包含dex的apk文件或jar文件的路径,

// 2、apk、jar解压缩生成dex存储的目录,

// 3、本地library库目录,一般为null,

// 4、父ClassLoader

DexClassLoader dexClassLoader = new DexClassLoader(pluginPath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());

//通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id

Class> clazz = dexClassLoader.loadClass(apkPackageName + ".R$drawable");

Field field = clazz.getDeclaredField(resName);//得到名为resName的这张图片字段

return field.getInt(R.id.class);//得到图片id

} catch (Exception e) {

e.printStackTrace();

}

return 0;

}

完成Resources和resId的获取后,我们就可以获取插件的资源了:

int resId = getResId(MainActivity.this.getApplication(), PATH, PLUGIN_PACKAGE_NAME, "ic_launcher");

Resources resources = getPluginResources(MainActivity.this.getApplication(), PATH);

Drawable drawable = resources.getDrawable(resId);

mIvTest.setImageDrawable(drawable);

至此,就是插件化加载的资源的基本思路和原理。

总结

明确思路,通过获取插件的Resources和resId来加载资源

通过仿APK解析过程,获取插件Resources

通过对插件的R.java的反射,获取resId

完成加载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值