插件换肤

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

插件换肤的重点在于如何加载插件包的资源。
插件包其实就是一个apk的压缩包,加载插件包的资源,其实就是加载apk的资源。

如何才能加载apk中的资源呢?
首先android中的各种资源都对应一个唯一id,比如获取一个view,通过findViewById(int id)方法,就可以得到布局的唯一view。
同样,
设置图片
setBackground(getResources().getDrawable(id))
设置颜色
setTextColor(getResources().getColor(id))

唯一的id值怎么获得呢?
id可以通过Resources下的getIdentifier方法根据icon的名字、view设置的id名、color的名字等得到:

    public int getIdentifier(String name, String defType, String defPackage) {
        return mResourcesImpl.getIdentifier(name, defType, defPackage);
    }

所以获取插件包的资源的核心就在:Resources,如果可以获取插件包的Resources,就可以根据名字,获取插件包的资源了。

如何获取插件包的Resources?

    /**
     * Create a new Resources object on top of an existing set of assets in an
     * AssetManager.
     *
     * @deprecated Resources should not be constructed by apps.
     * See {@link android.content.Context#createConfigurationContext(Configuration)}.
     *
     * @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).
     */
    @Deprecated
    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
        this(null);
        mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
    }

而创建Resources的关键在于

Create a new Resources object on top of an existing set of assets in an AssetManager.

通过AssetManager最新的设置创建一个新的Resouces对象

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) {
        return  addAssetPathInternal(path, false);
    }

这个设置可以是一个文件或者压缩包路径,即我们的插件包。


通过上面的分析,接下来实现插件换肤

    /**
     * 加载皮肤
     */
    public void loadSkin(){
        boolean grant = ClientUtils.addPermission(READ_EXTERNAL_STORAGE, context);
        if(!grant){
            Looklog.i(TAG, "loadSkin error: 无sd卡读取权限");
            //如果是外部目录,需要判断权限,否则crash
            return;
        }

        //加载皮肤
        new Thread(new Runnable() {
            @Override
            public void run() {
                //本地存储的路径
                String skinPkgPath = Local.getDVStringData(SKINPKGPATH);
                //插件名
                String skinName = "skin";
                //缓存path
                String cachePath = context.getCacheDir() + "/" + skinName;
                boolean needCopy = true;
                if(!TextUtils.isEmpty(skinPkgPath)){
                    if(skinPkgPath.equals(cachePath)){
                        File file = new File(skinPkgPath);
                        if(file != null){
                            if(file.exists()){
                                needCopy = false;
                            }
                        }
                    }
                }
                //如果缓存为空,copy
                if(needCopy){
                    Looklog.i(TAG,"无skin缓存,需要拷贝 " + skinPkgPath + "  cachePath:" + cachePath);
                    skinPkgPath = copySkinToCache(skinName);
                    Looklog.i(TAG,"无skin缓存,需要拷贝  拷贝成功>>>>:" + skinPkgPath);
                }else {
                    Looklog.i(TAG, "有skin缓存,直接使用 " + skinPkgPath);
                }
                //判断是否copy成功
                if(TextUtils.isEmpty(skinPkgPath)){
                    return;
                }

                //判断路径合法性
                File file = new File(skinPkgPath);
                if ((file == null) || !file.exists()) {
                    return;
                }

                //读取
                PackageManager mPm = context.getPackageManager();
                PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
                if(mInfo == null)return;
                skinPackageName = mInfo.packageName;
                try {
                    AssetManager assetManager = AssetManager.class.newInstance();
                    Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
                    addAssetPath.invoke(assetManager, skinPkgPath);
                    Resources superRes = context.getResources();
                    Resources skinResource = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
                    if(skinResource != null){
                        isDefaultSkin = false;
                        skinResources = skinResource;
                        clz = skinResources.getClass();
                        skinVersionCode = mInfo.versionCode;
                        skinVersionName = mInfo.versionName;
                        skinLoaded = true;
                        Looklog.i(TAG, "Skin  versionCode:" + skinVersionCode + "  versionName:" + skinVersionName + "  clz:" + clz + "  packageName:" + skinPackageName  + "  path:" + skinPkgPath);
                        saveSkinData(skinVersionName, skinVersionCode, skinPkgPath);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Looklog.i(TAG, "Skin load error " + e.getMessage());
                }
            }
        }).start();
    }

上面的skin路径,是由于将skin的插件包放在了assets目录下,如果是sd卡目录下,修改对应路径即可。

加载assets文件的话,需要先将assets路径下的skin插件包拷贝到缓存目录。

    private String copySkinToCache(String fileName) {
        File cacheFile = new File(context.getCacheDir(), fileName);
        InputStream inputStream = null;
        FileOutputStream outputStream = null;
        try {
            inputStream = context.getAssets().open(fileName);
            outputStream = new FileOutputStream(cacheFile);
            byte[] buf = new byte[1024];
            int len;
            while ((len = inputStream.read(buf)) > 0) {
                outputStream.write(buf, 0, len);
            }
        } catch (IOException e) {
            cacheFile = null;
            e.printStackTrace();
        }finally {
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                outputStream = null;
            }
            if(inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                inputStream = null;
            }
        }
        if(cacheFile == null){
            return "";
        }
        return cacheFile.getAbsolutePath();
    }

定义获取color或drawable的方法:



    private int getIdentifier(String name, String defType) {
        int id = skinResources.getIdentifier(name, defType, skinPackageName);
        if(id <= 0){
            id = appResources.getIdentifier(name, defType, appPackageName);
        }
        return id;
    }

    private int getColorId(String name){
        return getIdentifier(name, "color");
    }

    private int getDrawableId(String name){
        return getIdentifier(name, "drawable");
    }


    public int getColor(String name){
        int id = getColorId(name);
        int color = getColor(id, skinResources);
        if(color == -1){
            color = getColor(id, appResources);
        }
        if(color == -1){
            color = Color.WHITE;
        }
        return color;
    }

    public Drawable getDrawable(String name){
        int id = getDrawableId(name);
        Drawable drawable = getDrawble(id, skinResources);
        if(drawable == null){
            drawable = getDrawble(id, appResources);
        }
        if(drawable == null){
            drawable = new Drawable() {
                @Override
                public void draw(@NonNull Canvas canvas) {

                }

                @Override
                public void setAlpha(int alpha) {

                }

                @Override
                public void setColorFilter(@Nullable ColorFilter colorFilter) {

                }

                @Override
                public int getOpacity() {
                    return PixelFormat.TRANSPARENT;
                }
            };
        }
        return drawable;
    }

    private Drawable getDrawble(int id, Resources resources){
        try{
            return resources.getDrawable(id);
        } catch (Exception e){}
        return null;
    }

    private int getColor(int id, Resources resources){
        try {
            return resources.getColor(id);
        }catch (Exception e){}
        return -1;
    }

使用方式:

在values下新建theme_colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="background_color_default">#ffffff</color>
</resources>

代码使用:

 public int getBackgroundColor() {
        return getColor("background_color_default");
    }

只需要在插件包中添加一样的background_color_default,改成其他颜色

以上步骤即可实现插件换肤。

猜你喜欢

转载自blog.csdn.net/likuan0214/article/details/78564583