插件式换肤框架搭建思路。
思路:
1、 悄悄从网上下载一套皮肤 apk(zip 文件),把它运行起来,它无主界面。
每次获取皮肤的时候,利用反射;
2、 不需要运行,直接可以获取本地皮肤,前提已经从服务器上下载到本地了。
3、 android Support library 支持白天黑夜主题切换(最简单的方式),切换主题加载不同的资源。皮肤在我们的apk里面。
要解决的问题:
如何去获取另外一个apk中的资源。
换肤方案分析
资源加载 Resource 源码阅读
ImageView 如何加载 android:src="@drawable/logo" 这个文件的?
资源的加载,都是通过 Resource 类去读取的。想去获取另外一个apk中的资源,是否可以自己实例化一个?
// ImageView
Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
setImageDrawable(d);
// TypedArray
mResource.loadDrawable(value, value.resourceId, mTheme);
// Resource 实例化
ContextThemeWrapper:: super.getResources();
ContextWrapper:: mBase.getResources();
Context:: getResources(); // 抽象方法
ContextImpl.java:: 实现类
\--return mResource;
\--Resources resources = packageInfo.getResources(mainThread);
LoadedApk.java.java:: getResources(ActivityThread mainThread)
\--mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT\_DISPLAY, null, this);
ActivityThread.java::
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] legacyOverlayDirs,
String[] overlayPaths, String[] libDirs, LoadedApk pkgInfo,
Configuration overrideConfig) {
return mResourcesManager.getResources(null, resDir, splitResDirs, legacyOverlayDirs,
overlayPaths, libDirs, null, overrideConfig, pkgInfo.getCompatibilityInfo(),
pkgInfo.getClassLoader(), null);
}
ResourcesManager.java::
String resDir apk安装的路径。
r = new Resources(assets, dm, config, compatInfo); // API 23
// assets是 AssetManager资源管理。
Resource(AssetManager assets, DisplayMetrics metrics, Configuration config)
参数详解:
assets AssetManager 资源管理的创建
AssetManager assets = new AssetManager();
assets.addAssetPath(resDir); // resDir 就是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;
}
}
资源加载的总结:
- 所有的资源加载,是通过Resource-> 构建对象是直接new的对象;-> AssetManager 其实是Resource的核心实例。
->最终是通过AssetManager 获取资源。- AssetManager 实例化:
AssetManager assets = new AssetManager();
assets.addAssetPath(resDir); // 这个resDir可以是一个zip的路径。
- 自己创建 Resource 的实例,去读取资源。
原来项目中,添加资源图片 drawable/xhdpi/welcome.jpg
新建一个项目,SkinPlugin, 里面有资源 drawable/xhdpi/image_src.jpg。生成apk(red.skin),
将skin拷贝到 手机目录中(根目录中)
// 更换皮肤测试
private void testSkin() {
ImageView imageIv = findViewById(R.id.iv_image);
findViewById(R.id.tv_text).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
// 读取本地 .skin中的资源。
Resources superRes = getResources();
AssetManager asset = AssetManager.class.newInstance();
// 添加本地已经下载好的皮肤资源
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
method.setAccessible(true);
String resPath = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "red.skin";
method.invoke(asset, resPath); // 反射执行方法
Resources resources = new Resources(asset, superRes.getDisplayMetrics(), superRes.getConfiguration());
// 获取资源id: 根据名字取拿
int drawableId = resources.getIdentifier("image_src", "drawable", "com.hc.skinplugin");
Drawable drawable = resources.getDrawable(drawableId);
imageIv.setImageDrawable(drawable);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀