在上一篇文章 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

更多相关文章

  1. 格式化字符串android 格式化时间
  2. Android(安卓)自动检测版本并升级
  3. 属性资源与Android命名空间
  4. linux学习笔记《一.烧写篇_android》
  5. android 从第三方app打开方式添加自己的app
  6. 转:Activity_dialog效果
  7. android Thumbnail攻略
  8. Android(安卓)RRO机制的运用-----google开机向导客制化
  9. Mac下用Charles实现Android(安卓)http和https抓包

随机推荐

  1. 关于android系统Binder机制解析
  2. 深入Android(安卓)'M' Doze
  3. Android(安卓)消息机制之Message
  4. android笔记--android的进程与线程
  5. Android性能分析工具Systrace和TraceView
  6. Android(安卓)App 开源项目使用统计
  7. Android学习笔记19:ImageView实现图片适屏
  8. android 静默安装,含获取各种应用信息方法
  9. Android手机AP模式下本机IP
  10. Android(安卓)Hal 分析