今晚实在不想coding,于是想着整理点知识点,那么简单整理了下插件化开发实现动态更换皮肤。插件化开发大家应该不陌生或多或少用过或听过,插件化开发在项目业务拓展、模块化等方面有不小优势,当然实现一个完美的插件是有困难的。本文如果存在问题恳请指正!欢迎评论交流哦!
效果图:

1、换肤方案分析

  1. res下放多种皮肤的资源文件

  2. 加载插件apk使用其中的皮肤资源

方案一:
优点:容易实现。
缺点:res下放多种皮肤的资源文件,无疑会加大apk文件大小,而且资源文件是写死的,不利于后期拓展,若有其他皮肤需求只能通过版本迭代。
方案二:
优点:不会加大apk包,更容易扩展,有新的皮肤只需下载新的插件包即可,无需更新。(相对于方案一)
缺点:相对于方案一实现较困难,若有更多业务处理需要插件,如使用插件中的四大组件及处理它们的生命周期则是比较麻烦的,需用到动态代理。不过还好有360手机助手团队,在github上开源出来了DroidPlugin插件框架,https://github.com/Qihoo360/DroidPlugin,大家以后用这个就OK啦!

2、换肤实现分析

通过插件化实现更换皮肤,其实做的就是把插件apk中的皮肤资源文件拿到当前apk中使用而已。那么不妨先看下如何获取资源文件;

getResources().getDrawable(R.drawable.ic_launcher);

以上代码通过getResources()获取Resources对象,通过getDrawable(int id)获取Drawable对象,其中参数id R.drawable.ic_launcher是R文件类中的静态内部类drawable类的ic_launcher成员变量的值,如下。

public final class R {public static final class drawable {public static final int ic_launcher=0x7f020000;}}

那么如何加载一个插件apk中的资源文件呢?肯定也是先获取Resources对象,在获取Drawable对象。不过使用getResources()方法显然是不可行的,因为插件是一个apk,要想拿到里面的资源必须先把该apk加载到内存中来,然后利用反射拿到相关类的方法并使用。如果你对反射技术不是很了解可以看下http://blog.csdn.net/magic_jss/article/details/52187726;
基本步骤:

  • 通过DexClassLoader把插件apk加载到内存
  • 利用反射技术获取相关类的方法并使用,构造Resources对象

通过DexClassLoader加载插件apk,只需指定插件路径及创建一个优化目录即可,不在赘述,本文主要结合源码讲解如何通过反射构造Resources对象。

查看getResources()源码:

    //ContextThemeWrapper.java@Overridepublic Resources getResources() {if (mResources != null) {return mResources;}if (mOverrideConfiguration == null) {mResources = super.getResources();return mResources;} else {Context resc = createConfigurationContext(mOverrideConfiguration);//通过源码可以看出mResources对象是通过Context的getResources()方法获取的,查看Context源码mResources = resc.getResources();return mResources;}}

查看Context源码:

 // 抽象类  则通过查看该类的实现类ContextImplpublic abstract class Context {/** Return a Resources instance for your application's package. */// 抽象方法public abstract Resources getResources();}

查看ContextImpl源码:

    @Overridepublic Resources getResources() {return mResources;}@Overridepublic Context createConfigurationContext(Configuration overrideConfiguration) {if (overrideConfiguration == null) {throw new IllegalArgumentException("overrideConfiguration must not be null");}ContextImpl c = new ContextImpl();c.init(mPackageInfo, null, mMainThread);// mResources的获取时机 然后看下ResourcesManager源码c.mResources = mResourcesManager.getTopLevelResources(mPackageInfo.getResDir(),getDisplayId(), overrideConfiguration, mResources.getCompatibilityInfo(),mActivityToken);return c;}

查看ResourcesManager源码:

 /*** Creates the top level Resources for applications with the given compatibility info.*/public Resources getTopLevelResources(String resDir, int displayId,Configuration overrideConfiguration, CompatibilityInfo compatInfo,IBinder token) {// ...Resources r;AssetManager assets = new AssetManager();if (assets.addAssetPath(resDir) == 0) {return null;}r = new Resources(assets, dm, config, compatInfo, token);// ...return r;}

通过以上源码可以看出要想获取Resources对象,要先得到AssetManager对象,并执行addAssetPath()方法,那这样的话就可以通过反射进行处理了。想毕到这里,大家不用看下面的代码也能实现相关内容处理了吧?

3、换肤代码实现

由于上面对基本实现原理进行了梳理,下面实现代码则不做过多解读了,有问题可以留言哈。

PluginUtils.java

/*** Created by magic on 2016年8月10日.插件操作工具类*/
public class PluginUtils {/*** 获取插件Apk的AssetManager对象* * @param apk* @return*/@SuppressWarnings({ "unchecked", "rawtypes" })public static AssetManager getPluginAssetManager(File apk) throws Exception {// 字节码文件对象Class c = AssetManager.class;AssetManager assetManager=(AssetManager) c.newInstance();// 获取addAssetPath方法对象Method method = c.getDeclaredMethod("addAssetPath", String.class);method.invoke(assetManager, apk.getAbsolutePath());return assetManager;}/*** 获取插件Apk的Resources对象* * @param assets* @param metrics*            系统尺寸和分辨率描述对象* @param config*            配置* @return*/public static Resources getPluginResources(AssetManager assets,DisplayMetrics metrics, Configuration config) {Resources resources = new Resources(assets, metrics, config);return resources;}// public Resources(AssetManager assets, DisplayMetrics metrics,Configuration config) {//      this(assets, metrics, config,CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
//  }}

Activity.java

/*** Created by magic on 2016年8月10日.获取其他Apk的资源文件,实现动态换肤*/
public class MainActivity extends Activity implements OnClickListener {Button btn_daytime, btn_night;RelativeLayout container;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {btn_daytime = (Button) findViewById(R.id.btn_daytime);btn_night = (Button) findViewById(R.id.btn_night);container = (RelativeLayout) findViewById(R.id.container);btn_daytime.setOnClickListener(this);btn_night.setOnClickListener(this);getResources().getDrawable(R.drawable.ic_launcher);}@SuppressWarnings("deprecation")@SuppressLint("NewApi")@Overridepublic void onClick(View arg0) {switch (arg0.getId()) {case R.id.btn_daytime:container.setBackgroundResource(R.drawable.img_day);break;case R.id.btn_night:String skinName = "Test_Plugin_Skin.apk";String skinPath = this.getCacheDir() + File.separator + skinName;System.out.println("skinPath:" + skinPath);String skinPackageName = "com.magic.test_plugin_skin";File file = new File(skinPath);if (file.exists()) {Toast.makeText(this, "skin exists", Toast.LENGTH_SHORT).show();} else {// 网络下载皮肤逻辑try {InputStream inputStream = this.getAssets().open("Test_Plugin_Skin.apk");BufferedInputStream bis = new BufferedInputStream(inputStream);BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(skinPath));int len;byte[] bs = new byte[1024];while ((len = bis.read(bs)) != -1) {bos.write(bs, 0, len);}Toast.makeText(this, "skin download finish",Toast.LENGTH_SHORT).show();bis.close();bos.close();} catch (IOException e) {e.printStackTrace();}}try {// 获取插件Apk的AssetManager对象AssetManager assetManager = PluginUtils.getPluginAssetManager(file);// 获取插件Apk的Resources对象Resources resources = PluginUtils.getPluginResources(assetManager, this.getResources().getDisplayMetrics(),this.getResources().getConfiguration());// 类加载器DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), this.getDir(skinName,Context.MODE_PRIVATE).getAbsolutePath(), null,this.getClassLoader());// 反射拿到R.drawable类的字节码文件对象Class<?> c = dexClassLoader.loadClass(skinPackageName+ ".R$drawable");Field[] fields = c.getDeclaredFields();for (Field field : fields) {if (field.getName().equals("img_night")) {int imgId = field.getInt(R.drawable.class);Drawable background = resources.getDrawable(imgId);container.setBackgroundDrawable(background);}}} catch (Exception e) {e.printStackTrace();}break;}}}

xml文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent" ><Buttonandroid:id="@+id/btn_daytime"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="白天模式" /><Buttonandroid:id="@+id/btn_night"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@id/btn_daytime"android:text="夜间模式" /></RelativeLayout>

整个项目下载地址:http://download.csdn.net/detail/magic_jss/9619510;

END!晚安!

Android插件化开发实现动态换肤相关推荐

  1. Android插件化开发之动态加载本地皮肤包进行换肤

    Android插件化开发之动态加载本地皮肤包进行换肤 前言: 本文主要讲解如何用开源换肤框架 android-skin-loader-lib来实现加载本地皮肤包文件进行换肤,具体可自行参考框架原理进行 ...

  2. Android插件化开发之动态加载三个关键问题详解

    本文摘选自任玉刚著<Android开发艺术探索>,介绍了Android插件化技术的原理和三个关键问题,并给出了作者自己发起的开源插件化框架. 动态加载技术(也叫插件化技术)在技术驱动型的公 ...

  3. Android插件化开发之动态加载的类型

    https://segmentfault.com/a/1190000005113493 基本信息 Author:kaedea GitHub:android-dynamical-loading 现在网络 ...

  4. Android插件化开发之动态加载技术系列索引

    动态加载介绍 在Android开发中采用动态加载技术,可以达到不安装新的APK就升级APP功能的目的,可以用来到达快速发版的目的,也可以用来修复一些紧急BUG. 现在使用得比较广泛的动态加载技术的核心 ...

  5. Android插件化开发之动态加载技术简单易懂的介绍方式

    转载地方:https://segmentfault.com/a/1190000004062866 基本信息 Author:kaedea GitHub:android-dynamical-loading ...

  6. Android插件化开发之动态加载基础之ClassLoader工作机制

    类加载器ClassLoader 早期使用过Eclipse等Java编写的软件的同学可能比较熟悉,Eclipse可以加载许多第三方的插件(或者叫扩展),这就是动态加载.这些插件大多是一些Jar包,而使用 ...

  7. Hook源码+插件化实现无闪烁换肤

    现在的移动端应用,在实现功能需求的同时,还会在用户体验上进行突破,以增加用户黏性.例如,很多场景都加入了日/夜间主题切换,或者类似于网易云音乐的换肤.那么,当我们碰到类似的需求时,应该怎么来实现呢?更 ...

  8. Android插件化开发指南——Hook技术(一)【长文】

    文章目录 1. 前言 2. 将外部dex加载到宿主app的dexElements中 3. 插件中四大组件的调用思路 4. Hook 2.1 对startActivity进行Hook 2.1.1 AMS ...

  9. Android插件化开发指南——插件化技术简介

    文章目录 1. 为什么需要插件化技术 2. 插件化技术的历史 3. 插件化实现思路 3.1 InfoQ:您在 GMTC 中的议题叫做<Android 插件化:从入门到放弃>,请问这个标题代 ...

最新文章

  1. Python 学习日记 第八天
  2. win10家庭版 VMware Workstation 和 Device/Credential Guard 不兼容
  3. 前端入门技巧之浏览器调试
  4. pyecharts学习(part3)--简单图表绘制及参数优化
  5. 理解总结篇—List、Set、Map
  6. LeetCode 1277. 统计全为 1 的正方形子矩阵(DP)
  7. 2021中国新锐品牌发展报告
  8. 关于DP的一些解题总结
  9. Centos7配置ThinkPHP5.0完整过程(二)
  10. 遥感技术在水利行业的应用
  11. C++工程师的Rust迁移之路
  12. Android调用长截屏,Android实现长截屏功能
  13. iPhone6爆炸真是小概率事件吗?
  14. html调用网易云播放器无法自动播放,网页内嵌网易云插件全程(包括生成自己歌单的外链)...
  15. 好心情平台:30分钟就可改善抑郁情绪的运动处方
  16. Android | Sensor.TYPE_ORIENTATION被废弃后的解决办法
  17. MongoDB 分片集群故障RECOVERING 处理纪实
  18. 分享58个述职报告PPT模板,总有一款适合你
  19. 蓝牙BQB认证的过程与方式(SIG)
  20. Hi-C Data Browser:Hi-C数据浏览器

热门文章

  1. 企业直播营销的商业价值是什么?
  2. Qt中vertical spacer和horizontal spacer弹簧使用
  3. MPS | 光伏可再生能源利用 — 北京冬奥的另一块“金牌”
  4. 贝塞尔函数 matlab程序,bessel 贝塞尔函数的零点 MATLAB MAPLE
  5. HC小区物业管理系统一键部署方法
  6. Http服务传输图片的Python实现
  7. 电子邮件收发协议总结
  8. 用PowerBuilder制作指示灯
  9. MyEclipse快捷键main方法的设置
  10. 学习“HTML5 移动webapp阅读器”心得