【Android】自定义换肤框架02之自定义AssetManager和Resource

ResourceId是如何变成对应Resource的

在上一章中,我们已经讲过,apk中有个资源索引文件

其中保存了每个资源对应的id,name,type,path

资源文件的解析,主要涉及两个类,AssetManager和Resource

  • AssetManager,用于管理apk中的原生资源文件,包括asset和resource
  • AssetManager通过调用addAssetPath方法,来添加提供资源的apk
  • addAssetPath默认使用的是context.packageResourcePath,及当前安装包的位置
  • 如果想加载其它apk里面的资源,就得自定义AssetManager
  • AssetManager的构造函数是因此功能,必须通过反射才能自己创建新的实例
  • Resource,用于管理resource文件夹下的资源,如color,drawable等
  • Resource解析资源前,首先要拿到apk中的资源索引文件,和屏幕信息,配置信息
  • Resource对象的构建依赖于AssetManager,DisplayMetrics,Configuration三个对象
  • 如果我们想从其它apk中加载资源,则需要提供自定义的AssetManager给Resource
  • 由于DisplayMetrics和Configuration信息是固定的,因此不需要自定义
设计思路
  • 当我们想根据皮肤去替换某个资源时,在skin.apk中创建一份同名,但内容不同的资源
  • 自定义SkinnerAssetManager,并绑定skin.apk
  • 自定义SkinnerResources,并绑定SkinnerAssetManager
  • 相同名称的资源,在不同apk中的id是不一样,但我们可以通过name+type+package的方式去找到对应的id
  • 通过OriginResourceId+OriginResources,得到name+type+package
  • 通过SkinnerResources,以及name+type+package,拿到SkinnerResourceId
  • 通过SkinnerResources+SkinnerResourceId,解析出skin.apk中的color或drawable
  • 由于并不是所有属性都会跟随皮肤而变换,因此SkinnerResourceId有可能不存在
  • 如果SkinnerResourceId不存在,则使用OriginResources去加载原来的资源,这样大致实现了资源的自动加载
自定义SkinnerAssetManager
kotlin 复制代码
package com.android.library.skinner

import android.app.Application
import android.content.res.AssetManager
import android.content.res.Resources
import android.graphics.drawable.Drawable

@Suppress("Deprecated")
object SkinnerAssetManager {

    lateinit var context: Application
    lateinit var assetManager: AssetManager
    lateinit var skinnerResources: Resources
    lateinit var originResources: Resources

    fun init(application: Application, resourcePath: String) = apply {
        context = application
        createHookedAssetManager(resourcePath)
    }

    private fun createHookedAssetManager(resourcePath: String) {
        val assetManager = AssetManager::class.java.newInstance()
        val method = AssetManager::class.java.getDeclaredMethod("addAssetPath", String::class.java)
        method.invoke(assetManager, resourcePath)
        this.originResources = context.resources
        val resources = Resources(assetManager, originResources.displayMetrics, originResources.configuration)
        this.assetManager = assetManager
        this.skinnerResources = resources
    }

    fun skinResId(resId: Int): Int {
        return skinnerResources.getIdentifier(
            originResources.getResourceName(resId),
            originResources.getResourceTypeName(resId),
            originResources.getResourcePackageName(resId)
        )
    }

    fun skinColor(resId: Int): Int {
        val skinResId = skinResId(resId)
        if (skinResId > 0) {
            return skinnerResources.getColor(skinResId)
        }
        return originResources.getColor(resId)
    }

    fun skinDrawable(resId: Int): Drawable {
        val skinResId = skinResId(resId)
        if (skinResId > 0) {
            return skinnerResources.getDrawable(skinResId)
        }
        return originResources.getDrawable(resId)
    }
}
拷贝测试皮肤包到存储卡

这里我们将测试包放在asset文件夹里面,在应用启动时拷贝到存储卡,从而省去人工操作

kotlin 复制代码
private fun copySkinPackage() {
    val fis = application.assets.open("skin.apk")
    val fos = FileOutputStream("sdcard/skin.apk")
    val buffer = ByteArray(fis.available())
    fis.read(buffer)
    fos.write(buffer)
}
通过指定皮肤包初始化SkinnerAssetManager
kotlin 复制代码
SkinnerAssetManager.init(application, "sdcard/skin.apk")
使用自定义的SkinnerAssetManager加载资源
kotlin 复制代码
val drawable = SkinnerAssetManager.skinDrawable(R.drawable.icon_app)
binding.image.setImageDrawable(drawable)
十万个为什么

到目前为止,我们已经实现了从指定apk中加载同名资源

下一步问题是,如何让Activity/Fragment/View/Xml使用SkinnerResources,而不是默认的OriginResources

且听下回分解!

相关推荐
·云扬·5 分钟前
MySQL四大系统库详解:作用、核心表与实用SQL查询
android·sql·mysql
普马萨特5 分钟前
移动网络信号指标与单位整理(2G/3G/4G/5G Android vs IoT)
android·网络·物联网
de之梦-御风14 分钟前
【电视投屏】针对“局域网投屏开源项目(Android 手机 ↔ Android TV)
android·智能手机·开源
threelab29 分钟前
Merge3D 三维引擎中 GeoJSON 数据加载的整体设计
android·3d
优选资源分享1 小时前
Escrcpy 便携版 v2.0.0:安卓手机电脑同屏软件
android·智能手机·电脑
2501_915918411 小时前
介绍如何在电脑上查看 iPhone 和 iPad 的完整设备信息
android·ios·小程序·uni-app·电脑·iphone·ipad
TheNextByte11 小时前
如何通过蓝牙将照片从 iPhone 分享到Android ?
android·gitee·iphone
2501_916008892 小时前
没有 Mac 如何在 Windows 上创建 iOS 应用描述文件
android·macos·ios·小程序·uni-app·iphone·webview
Android系统攻城狮4 小时前
Android ALSA进阶之处理PCM的ioctl命令snd_pcm_lib_ioctl:用法实例(一百)
android·pcm·alsa·音频进阶
诸神黄昏EX12 小时前
Android Build系列专题【篇六:VINTF机制】
android