Android 插件式换肤实现

原创 2018年04月15日 14:15:56

写在前头

    Android的换肤机制有不少,通过加载不同资源文件进行换肤,通过不同的Style文件进行换肤,但是最主流的还是插件式换肤,将资源文件打成一个包,通过AssetManager去加载这个包中的资源文件来换肤。


换肤代码

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 皮肤管理器
 * Created by db on 2018/4/14.
 */

public class SkinManager {

    private static SkinManager mInstance;
    private Resources mResource;
    private String mSkinName;
    private String mSkinPath;
    private static Context mContext;
    private boolean IsInit = false;
    private String mPackageName;

    public static SkinManager getInstance(Context context){
        if(mInstance==null){
            mInstance = new SkinManager();
            mContext = context;
        }
        return mInstance;
    }

    public void setSkinName(String path){
        if(!IsInit){
            this.mSkinName = path;
            mSkinPath = mContext.getFilesDir()+ "/"+ mSkinName;
            File file = new File(mSkinPath);
            if(!file.exists()){
                mResource = mContext.getResources(); //使用默认资源
                mPackageName = mContext.getPackageName();
                return;
            }
            try {
                //读取本地皮肤资源
                Resources superRes = mContext.getResources();
                //通过反射创建AssetManger
                AssetManager asset = AssetManager.class.newInstance();
                //添加本地下载好的皮肤
                @SuppressLint("PrivateApi")
                Method method = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
                method.invoke(asset, mSkinPath);
                mResource = new Resources(asset,superRes.getDisplayMetrics(),superRes.getConfiguration());
                // 获取skinPath包名
                mPackageName = mContext.getPackageManager().getPackageArchiveInfo(
                        mSkinPath, PackageManager.GET_ACTIVITIES).packageName;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            IsInit = true;
        }

    }

    /**
     * 通过名字获取Drawable
     * @param resName
     * @return
     */
    public Drawable getDrawableByName(String resName){
        try {
            int resId = mResource.getIdentifier(resName, "drawable", mPackageName);
            Drawable drawable = mResource.getDrawable(resId);
            return drawable;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 通过名字获取mipmap
     * @param resName
     * @return
     */
    public Drawable getMipmapByName(String resName){
        try {
            int resId = mResource.getIdentifier(resName, "mipmap", mPackageName);
            Drawable drawable = mResource.getDrawable(resId);
            return drawable;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 通过名字获取颜色
     * @param resName
     * @return
     */
    public ColorStateList getColorByName(String resName){
        try {
            int resId = mResource.getIdentifier(resName, "color", mPackageName);
            ColorStateList color = mResource.getColorStateList(resId);
            return color;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
  
}


资源文件打包方法


(1)新建一个不带有activity的android项目



(2)将资源文件放入这个项目之中

    这里有两种方法来加载包里的资源文件,通过资源文件名直接加载,或者通过创建一个类来返回资源文件的方法加载。后者更便于修改资源文件名称,前者要求资源文件名称必须相同,但是更为简便。各有各的优势,看自己的取舍了。通过第二种方法加载的话我们需要修改一下代码,需要去加载类而不是加载资源文件。


皮肤包中的资源工具类
public class UIUtil {  
      
    public static String getTextString(Context ctx){  
        return ctx.getResources().getString(R.string.app_name);  
    }  
      
    public static Drawable getImageDrawable(Context ctx){  
        return ctx.getResources().getDrawable(R.drawable.ic_launcher);  
    }  
      
    public static View getLayout(Context ctx){  
        return LayoutInflater.from(ctx).inflate(R.layout.activity_main, null);  
    }  
      
    public static int getTextStringId(){  
        return R.string.app_name;  
    }  
      
    public static int getImageDrawableId(){  
        return R.drawable.ic_launcher;  
    }  
      
    public static int getLayoutId(){  
        return R.layout.activity_main;  
    }  
  
}  

修改后的加载类的方法
  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();
    }

(3)打包

    这里打包不管是不是签名包都是可以的,然后将打好的包放入手机指定路径中,就可以加载了。一般会把这些包保存到服务器,让用户选择喜欢的皮肤并进行下载即可。



版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38520096/article/details/79948794

Android插件换肤功能实战

Android App实现换肤有很多方式,有的是通过内置资源的方式,有的是通过设置相同签名并且AndroidManifest.xml中配置相同android:sharedUserId使得两个apk运行...
  • goodlixueyong
  • goodlixueyong
  • 2016-04-08 00:53:22
  • 1824

Android中插件开发篇之----应用换肤原理解析

一、前言今天又到周末了,感觉时间过的很快呀.这周媳妇生气了,所以就不能和她happy了,那只能写blog了。那么今天就来看看应用的换肤原理解析。在之前的一篇博客中我说道了Android中的插件开发篇的...
  • jiangwei0910410003
  • jiangwei0910410003
  • 2015-08-15 13:09:09
  • 23010

android 换肤(1)——插件式无缝换肤(解析鸿洋大神的换肤流程)

对于app换肤,这是一个常见而又常用的功能。虽然我做的项目中还没涉及到换肤,但是还是想研究下。于是,下载了鸿洋大神的换肤demo来研究。 先看效果图:(尊重鸿洋大神的代码,效果图上原创) 鸿洋...
  • yehui928186846
  • yehui928186846
  • 2016-06-02 19:35:30
  • 2556

换肤插件Demo

  • 2015年08月15日 13:02
  • 14.04MB
  • 下载

android 换肤(2)——插件式无缝换肤(解析鸿洋大神的换肤流程)

上一篇我说到tag式换肤的流程。小结: 你只需要在每一个需要换肤的activity中注册SkinManager就可以换肤了,并且在需要换肤的资源中xml或者代码中都要设置tag,且这个tag是严格按...
  • yehui928186846
  • yehui928186846
  • 2016-06-02 21:57:22
  • 1675

Android 轻量级动态换肤框架

  • 2016年05月12日 16:39
  • 17KB
  • 下载

【Android-View】浅谈Android换肤的几种实现方式

Android平台api没有特意为换肤提供一套简便的机制,这可能是外国的软件更注重功能和易用,不流行换肤。系统不提供直接支持,只能自行研究。 换肤,可以认为是动态替换资源(文字、颜色、字体大小、...
  • tantion
  • tantion
  • 2017-02-16 10:14:11
  • 2877

一种完全无侵入的换肤方式,支持插件式和应用内,无需重启Activity.

开源框架地址:https://github.com/hongyangAndroid/AndroidChangeSkinAndroidChangeSkin特点插件式换肤应用内换肤支持插件或者应用内多套皮...
  • wangwangli6
  • wangwangli6
  • 2018-03-20 17:03:32
  • 25

Android换肤功能实现与换肤框架QSkinLoader使用方式介绍

框架地址:https://github.com/qqliu10u/QSkinLoader 效果图 如果想要看框架使用,请直接看第三部分。一、综述此框架脱胎于项目需要实现夜间模式的需求,在上一篇文章...
  • u013478336
  • u013478336
  • 2016-11-08 15:48:27
  • 7080

android换肤你所应该知道的一切------换肤精华

Android中常采用的几种换肤方式按照我们所熟悉的换肤无非分为两类换肤,一类是应用内换肤,另外一类分为插件式换肤,也分为应用外换肤。换肤一直是一个老生常谈的话题,但是我们需要怎么来统一管理我们的资源...
  • shengbo1992
  • shengbo1992
  • 2015-11-12 21:17:49
  • 1082
收藏助手
不良信息举报
您举报文章:Android 插件式换肤实现
举报原因:
原因补充:

(最多只允许输入30个字)