插件换肤

来源:互联网 发布:数据库表结设计例子 编辑:程序博客网 时间:2024/06/05 02:35

插件换肤的重点在于如何加载插件包的资源。
插件包其实就是一个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,改成其他颜色

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

原创粉丝点击