Android插件化开发实现动态换肤
来源:互联网 发布:淘宝店铺网红 编辑:程序博客网 时间:2024/05/31 19:10
今晚实在不想coding,于是想着整理点知识点,那么简单整理了下插件化开发实现动态更换皮肤。插件化开发大家应该不陌生或多或少用过或听过,插件化开发在项目业务拓展、模块化等方面有不小优势,当然实现一个完美的插件是有困难的。本文如果存在问题恳请指正!欢迎评论交流哦!
效果图:
1、换肤方案分析
res下放多种皮肤的资源文件
加载插件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 @Override public 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源码:
// 抽象类 则通过查看该类的实现类ContextImpl public abstract class Context { /** Return a Resources instance for your application's package. */ // 抽象方法 public abstract Resources getResources(); }
查看ContextImpl源码:
@Override public Resources getResources() { return mResources; } @Override public 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; @Override protected 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") @Override public 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" > <Button android:id="@+id/btn_daytime" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="白天模式" /> <Button android: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插件化开发实现动态换肤
- android插件化开发---换肤
- Android插件化开发之动态加载本地皮肤包进行换肤
- android 实现动态换肤效果
- 动态加载插件开发换肤等技术文章
- android动态换肤
- Android插件化系列第(二)篇---动态加载技术之应用换肤
- Android插件开发之-换肤功能前篇
- Android插件开发之-换肤功能后篇
- android换肤实现
- android 动态切换主题,动态换肤
- Android插件化开发-hook动态代理
- Android插件化开发-hook动态代理
- Android动态换肤(二、apk免安装插件方式)
- Android换肤之——插件换肤
- 如何给插件实现自动换肤
- Android插件换肤功能实战
- Android 插件换肤原理解析
- python第六天学习记录——面向对象基础
- 电路与Multisim 利用示波器观察二极管的正向电压
- 从尾到头打印链表——剑指offer
- 第四章-----进程
- 互斥量、临界区、信号量——来来来,看你晕不晕
- Android插件化开发实现动态换肤
- lua module
- Android----菜单的使用
- ListView的优化
- 不能既添加又删除某属性;如果某元素有了该属性(例如动画);再添加并不会再次执行该属性(动画)
- Python 强行utf-8解码
- TCP四种计时器
- 次小生成树(poj2831)
- c语言回环树实现