【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
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文件夹里面,在应用启动时拷贝到存储卡,从而省去人工操作

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
SkinnerAssetManager.init(application, "sdcard/skin.apk")
使用自定义的SkinnerAssetManager加载资源
val drawable = SkinnerAssetManager.skinDrawable(R.drawable.icon_app)
binding.image.setImageDrawable(drawable)
十万个为什么

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

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

且听下回分解!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/776401.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式UI开发-lvgl+wsl2+vscode系列:6、布局(Layouts)

一、前言 这节总结一下整体页面的布局方式,lvgl的布局方式比较少,目前只有flex和grid两大类布局,即弹性布局和网格布局,弹性布局一般就是指定相对位置,网格布局就是将整个页面划分为网格状,我们做其它的UI…

日志自动分析-Web---360星图GoaccessALBAnolog

目录 1、Web-360星图(IIS/Apache/Nginx) 2、Web-GoAccess (任何自定义日志格式字符串) 源码及使用手册 安装goaccess 使用 输出 3-Web-自写脚本(任何自定义日志格式字符串) 4、Web-机器语言analog(任何自定义日…

【C++】 解决 C++ 语言报错:Invalid Conversion from ‘const char*’ to ‘char*’

文章目录 引言 在 C 编程中,类型转换错误(Invalid Conversion)是常见的编译错误之一。特别是当程序试图将一个常量字符指针(const char*)转换为非常量字符指针(char*)时,会导致编译…

【C++题解】1413. 切割绳子

问题:1413. 切割绳子 类型:贪心,二分,noip2017普及组初赛 题目描述: 有 n 条绳子,每条绳子的长度已知且均为正整数。绳子可以以任意正整数长度切割,但不可以连接。现在要从这些绳子中切割出 m…

TF-IDF和BM25原理和区别

TF-IDF TF-IDF是TF(词频,Term Frequency)和IDF(逆文档频率,Inverse Document Frequency)的乘积。我们先来看他们分别是怎么计算的: TF的计算有多种方式,常见的是 除以文章总词数是…

多点mGRE over IPsecVPN 配置及NHRP的使用

一、实验目的及拓扑 1、实验思路:FW1为总部固定IP,FW2和FW3为分支动态地址,通过mGRE over IPsec实现,并加载NHR解决多点隧道目的地址问题 2、网络拓扑 二、基本配置 (一)配置相关接口地址,并…

14-34 剑和诗人8 - 微调 LLM 的分块策略

​​​​​​ 介绍 使用大规模 LLM 会带来巨大挑战,特别是在内存管理和模型微调方面。一项可以缓解这些挑战的强大技术是分块,这是一种将大量输入或输出分解为更小、更易于管理的部分的策略。 让我们深入研究分块策略的复杂性,探索它们在微…

【C++题解】1561. 买木头

问题:1561. 买木头 类型:省赛、数组问题、二分答案、贪心、2015江苏省青少年信息学奥林匹克竞赛复赛 题目描述: 有 n 个木材供应商,每个供货商有长度相同一定数量的木头。长木头可以锯短,但短木头不能接长。有一个客…

使用ndoe实现自动化完成增删改查接口

使用ndoe实现自动化完成增删改查接口 最近工作内容比较繁琐,手里需要开发的项目需求比较多,常常在多个项目之间来回切换,有时候某些分支都不知道自己开发了什么、做了哪些需求, 使用手写笔记的方式去记录分支到头来也是眼花缭乱&a…

python库(5):Psutil库实现系统和硬件监控工具

1 psutil简介 psutil(process and system utilities)是一个跨平台库,用于检索运行中进程和系统利用率(包括 CPU、内存、磁盘、网络等)的信息,可以提供丰富的系统监控功能。 2 psutil安装 pip install -i …

新手教学系列——Git Stash踩坑

在之前的文章《如何彻底避免Git代码相互覆盖问题》中,我曾介绍过通过规范分支合并和使用git stash来避免代码覆盖问题。今天,我要深入探讨一下git stash的使用,并分享一些使用过程中遇到的坑,希望能帮你避免类似问题。 脚本mg.sh简介 为了更好地管理代码合并,我编写了一…

Pseudo-Label : The Simple and Efficient Semi-Supervised Learning Method--论文笔记

论文笔记 资料 1.代码地址 https://github.com/iBelieveCJM/pseudo_label-pytorch 2.论文地址 3.数据集地址 论文摘要的翻译 本文提出了一种简单有效的深度神经网络半监督学习方法。基本上,所提出的网络是以有监督的方式同时使用标记数据和未标记数据来训练的…

ANN文献综述

人工神经网络文献综述 摘要 人工神经网络(Artificial Neural Networks, ANNs)是由多个简单的、相互连接的处理单元组成的自适应系统,通过调整这些单元之间的连接强度,ANNs能够实现对复杂数据的建模和预测。本文综述了ANNs的基本…

excel批量修改一列单价的金额并保留1位小数

1.打开表格,要把单价金额变成现在的两倍,数据如下: 2.把单价这一列粘贴到一个新的sheet页面,在B2单元格输入公式:A2*2 然后按enter回车键,这时候吧鼠标放到B2单元格右下角,会出现一个黑色的小加号&#xf…

安装Linux虚拟机

点击创建新的虚拟机 选择高级 系统自定义推荐 选择稍后安装 选择Linux 虚拟机命名并且选择创建位置 系统自定义 系统自定义推荐 系统自定义推荐 选择安装好的iOS文件 点击完成 选择编辑虚拟机设置 进入后选择第一个Install red hat enterprise 选择常用语言 设置…

用户体验驱动开发:打造卓越数字产品的关键

目录 前言1. 用户体验驱动开发的定义1.1 用户体验的核心要素1.2 用户体验与用户界面 2. 用户体验驱动开发的重要性2.1 提升用户满意度2.2 增加用户忠诚度2.3 提升市场竞争力2.4 提高商业成功率 3. 用户体验驱动开发的方法论3.1 用户研究3.2 信息架构3.3 交互设计3.4 可用性测试…

一道有意思的简单题 [NOIP2010 普及组] 接水问题

题目&#xff1a; 题解&#xff1a; 每一次新来的同学的接水时间都加在现在已有的水龙头中接水时间最短的&#xff0c;总时间就为n次操作后水龙头中接水时间的最长值。 #include<bits/stdc.h> using namespace std; multiset<int>s;int main(){int n,m;scanf(&qu…

PMP–知识卡片--PDCA循环

记忆 PDCA&#xff1a;计划执行检查调整&#xff0c;计划观察动作&#xff1b;plan do check action 定义 PDCA循环的含义是将质量管理分为四个过程&#xff0c;即计划&#xff08;Plan&#xff09;、执行&#xff08;Do&#xff09;、检查&#xff08;Check&#xff09;、处…

美光科技在2024年1γ工艺技术在10纳米级别启动EUV试产

美光科技&#xff08;Micron&#xff09;在2024年针对其1γ&#xff08;1-gamma&#xff09;工艺技术在10纳米级别启动EUV&#xff08;极紫外光刻&#xff09;试产&#xff0c;这标志着存储行业巨头在EUV采用上的重要一步&#xff0c;尽管相比英特尔和台积电等其他半导体制造商…

查看java版本和安装位置-cnblog

查看java位置 进入设置&#xff0c;高级系统设置 打开环境变量 找到path双击 查看java版本 java -version