在上一篇文章Android动态换肤(一、应用内置多套皮肤)中,我们了解到,动态换肤无非就是调用view的setBackgroundResource(R.drawable.id)等方法设置控件的背景或者文字等资源,跟踪源码看看这些方法都是怎么根据资源ID找到对应的资源的:

  • View:
 @RemotableViewMethod
 public void setBackgroundResource(int resid) {
     if (resid != 0 && resid == mBackgroundResource) {
         return;
     }

     Drawable d = null;
     if (resid != 0) {
         d = mContext.getDrawable(resid);
     }
     setBackground(d);

     mBackgroundResource = resid;
 }
  • mContext:
public final String getString(int resId) {
    return getResources().getString(resId);
}
public final String getString(int resId, Object... formatArgs){
   return getResources().getString(resId, formatArgs);
}
public final Drawable getDrawable(int id) {
   return getResources().getDrawable(id, getTheme());
}
...
  • Resources:

Resources中有一系列根据资源ID获取资源的方法,所以最关键的就是得到皮肤文件的Resources对象和资源ID。资源ID在皮肤apk打包的时候已经编译好,可以通过反射的方式获取R然后获取到资源ID。Android资源管理框架实际就是由AssetManager和Resources两个类来实现的,其中,Resources类可以根据ID来查找资源,而AssetManager类根据文件名来查找资源。事实上,如果一个资源ID对应的是一个文件,那么Resources类是先根据ID来找到资源文件名称,然后再将该文件名称交给AssetManager类来打开对应的文件的。

/**
 * 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(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
}

根据上面的构造方法,创建Resources对象,需要传入三个参数,分别是AssetManager对象(用于获取资源文件),DisplayMetrics对象(封装一些关于显示的通用信息,如显示大小,分辨率和字体),Configuration对象(专门用来描述手机设备上的配置信息。这些配置信息包括用户特定的配置项,也包括系统的动态设备配置);后面两个参数是关于Android设备的,同一台设备这两个对象封装内容也是相同的,所以直接传入当前应用中获取的即可,现在关键问题就是如何获取到皮肤apk的AssetManager对象。

/**
 * 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;
    }
}

AssetManager中有一个addAssetPath方法,此方法将一个资源文件的路径添加到资源管理器中,这个资源文件的路径就是皮肤apk的路径;此方法被隐藏了,可以通过反射的方式调用。

到此为止,皮肤apk的资源管理器已经拿到了,代码如下:

public class BaseActivity extends Activity{
    protected AssetManager mAssetManager;   //资源管理器
    protected Resources mResources;         //资源  

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    protected void loadResources(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            mAssetManager = assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();
        superRes.getDisplayMetrics();
        superRes.getConfiguration();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
    }
    @Override
    public Resources getResources() {
        return mResources == null ? super.getResources() : mResources;
    }
}

获取皮肤apk中的资源ID

Android中提供了DexClassLoader这个类加载器用来从.jar和.apk类型的文件内部加载classes.dex文件。通过这种方式可以用来执行非安装的程序代码,作为程序的一部分进行运行。获取资源ID有两种方式:

  • 一种是在皮肤应用中编写工具类返回资源ID,然后通过反射调用此工具类:
public class UIUtil {
    public static int getBtnBgId(){
        return R.drawable.btn_selector;
    }
    public static int getMyActivityBg(){
        return R.drawable.login_bg;
    }
    public static int getTilteString(){
        return R.string.app_name;
    }
....
}
@SuppressLint("NewApi")
private void loadOtherSkin(){
    try {
        Class clazz = classLoader.loadClass("com.openxu.skin.UIUtil");
        Method method = clazz.getMethod("getTilteString");
        titleID = (int) method.invoke(null);
        Log.d(TAG, "获取标题ID:" + titleID);
        method = clazz.getMethod("getBtnBgId");
        btn_bg = (int) method.invoke(null);
        Log.d(TAG, "获取按钮背景ID:" + btn_bg);
        method = clazz.getMethod("getMyActivityBg");
        activty_bg = (int) method.invoke(null);
        Log.d(TAG, "获取背景ID:" + activty_bg);
        showSkin();
    } catch (Exception e) {
        Log.e(TAG, "获取失败:" + Log.getStackTraceString(e));
    }
}
  • 另外一种方式是直接通过反射得到R文件中的各种资源类从而得到对应的资源ID:
@SuppressLint("NewApi")
private int getDrawableId(String name) {
    try {
        Class clazz = classLoader.loadClass("com.openxu.skin.R$drawable");
        Field field = clazz.getField("ic_launcher");
        int resId = (int) field.get(null);
        return resId;
    } catch (Exception e) {
        Log.i("Loader", "error:" + Log.getStackTraceString(e));
    }
        return 0;
}

效果图:

下载源码后运行工程,在sd卡根目录新建skinchange文件夹,然后将工程下assets目录中的SkinChange2Skin.apk皮肤apk(zip为皮肤apk源码)拷贝到skinchange文件夹下,点击更换皮肤

源码下载:

https://github.com/openXu/SkinChange2

Android动态换肤(二、apk免安装插件方式)的更多相关文章

  1. Android动态换肤(三、安装主题apk方式)

    相比之前免安装的方式,这种方法需要用户下载并安装皮肤apk,程序写起来比免安装的要简单很多,像很多系统主题就是通过这种方式实现的. 这种方式的思路是,从所有已安装的应用程序中遍历出皮肤程序(根据特定包 ...

  2. Android动态换肤(一、应用内置多套皮肤)

    动态换肤在很多android应用中都有使用,用户根据自己的喜好设置皮肤主题,可以增强用户使用应用的舒适度. Android换肤可以分为很多种,它们从使用方式,用户体验以及项目框架设计上体现了明显的差异 ...

  3. Android主题换肤实现

    本系列文章主要是对一个Material Design的APP的深度解析,主要包括以下内容 基于Material Design Support Library作为项目整体框架.对应博文:Android ...

  4. Android主题换肤 无缝切换

    2016年7月6日 更新:主题换肤库子项目地址:ThemeSkinning,让app集成换肤更加容易.欢迎star以及使用,提供改进意见. 更新日志: v1.3.0:增加一键切换切换字体(初版)v1. ...

  5. Android实现换肤功能(一)

    上周有个朋友给建议说讲讲换肤吧,真巧这周公司的工作安排也有这个需求,换的地方之多之繁,让人伤神死了.正所谓磨刀不误砍柴工,先磨下刀,抽出一个工具类,写了个关于换肤的简单demo. Android中换肤 ...

  6. duilib入门之贴图描述、类html文本描述、动态换肤、Dll插件、资源打包

    转载自duilib入门文档 贴图描述: Duilib的表现力丰富很大程度上得益于贴图描述的简单强大.Duilib的贴图描述分为简单模式和复杂模式两种. 简单模式使用文件名做为贴图描述内容,在这种方式下 ...

  7. element-ui 动态换肤

    1.在安装好 element-ui@2.x 以后,首先安装sass-loader npm i sass-loader node-sass -D 2.安装 element-theme npm i ele ...

  8. hybird之web动态换肤实现

    前言 最近在重构个hybird(原生的壳包着Web页面)的UI框架,进行到了做换肤功能的阶段,所以这里是我思考的解决的方法. 预想 目前实现换肤的功能无非就两种做法. 1.写几个皮肤文件,然后切换使用 ...

  9. CocoStudio基础教程(4)骨骼动画的动态换肤

    1.概述 游戏中人物的状态会发生改变,而这种改变通常要通过局部的变化来表现出来.比如获得一件装备后人物形象的改变,或者战斗中武器.防具的损坏等.这些变化的实现就要通过动态换肤来实现. 2.运行到程序 ...

随机推荐

  1. [BJOI 2010]次小生成树Tree

    Description 小 C 最近学了很多最小生成树的算法,Prim 算法.Kurskal 算法.消圈算法等等. 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了.小 P 说,让小 C 求出一 ...

  2. [POI2009]KAM-Pebbles

    题目描述 Johny and Margaret are playing "pebbles". Initially there is a certain number of pebb ...

  3. Go学习——new()和 make()的区别详解(转载)

    这篇文章主要介绍了Go语言中new()和 make()的区别详解,本文讲解了new 的主要特性.make 的主要特性,并对它们的区别做了总结,需要的朋友可以参考下 概述 Go 语言中的 new 和 m ...

  4. bzoj 3191: [JLOI2013]卡牌游戏

    Description N个人坐成一圈玩游戏.一开始我们把所有玩家按顺时针从1到N编号.首先第一回合是玩家1作为庄家.每个回合庄家都会随机(即按相等的概率)从卡牌堆里选择一张卡片,假设卡片上的数字为X ...

  5. hdu 3016 dp+线段树

    Man Down Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total S ...

  6. [bzoj4883][Lydsy2017年5月月赛]棋盘上的守卫

    来自FallDream的博客,未经允许,请勿转载, 谢谢. 在一个n*m的棋盘上要放置若干个守卫.对于n行来说,每行必须恰好放置一个横向守卫:同理对于m列来说,每列 必须恰好放置一个纵向守卫.每个位置 ...

  7. [bzoj4893]项链分赃

    来自FallDream的博客,未经允许,请勿转载,谢谢. 有一串长度为n(n<=10^5)的项链,上面有红绿蓝三种颜色的珠子,每种颜色的珠子数目都是偶数,现在要你把它切几刀分成若干段,把其中一些 ...

  8. 智能优化算法对TSP问题的求解研究

    要求: TSP 算法(Traveling Salesman Problem)是指给定 n 个城市和各个城市之间的距离,要 求确定一条经过各个城市当且仅当一次的最短路径,它是一种典型的优化组合问题,其最 ...

  9. JavaTCP和UDP套接字编程

    在我们刚开始入门Java后端的时候可能你会觉得有点复杂,包含了很多杂七杂八的知识,例如文件上传下载,监听器,JDBC,请求重定向,请求转发等等(当然也没有很多),但是我们自己真正的去开发一个小型网站( ...

  10. centos6.8下weblogic12c静默安装

    环境: centos6.8 无桌面环境 jdk1.7.0_25 关闭iptables.selinux 安装前准备: 1.新建weblogic用户,设置weblogic密码 useradd weblog ...