腾讯面试官:说说王者荣耀里面Android的换肤原理和Android的皮肤,装载机框架解析(2)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正文

换肤本质上是对资源的一中替换包括,字体,颜色,背景,图片,大小等等。当然这些我们都有成熟的API可以通过控制代码逻辑做到。比如查看的修改背景颜色setBackgroundColor,TextView中的setTextSize。修改字体等等但是作为程序员我们怎么能忍受对每个页面的每个元素一个行行代码做换肤处理呢?我们需要用最少的代码实现最容易维护和使用效果完美(动态切换,及时生效)的换肤框架。

1.换肤方式一:切换使用主题主题

使用相同的资源ID,但在不同的主题下边自定义不同的资源。我们通过主动切换到不同的主题从而切换界面元素创建时使用的资源。这种方案的代码量不多发,而且有个很明显的缺点不支持已经创建界面的换肤,必须重新加载界面元素.GitHub Demo

2. 换肤方式二:加载资源包

加载资源包是各种应用程序都在使用的换肤方法,例如我们最常用的输入法皮肤,浏览器皮肤等等。我们可以将皮肤的资源文件放入安装包内部,也可以进行下载缓存到磁盘上.Android的应用程序可以使用这种方式进行换肤.GitHub上面有一个开始非常高的换肤框架Android的皮肤下载器就是通过加载资源包对应用程序进行换肤。对这个框架的分析这个也是这篇文章主要的讲述内容。

对比一下发现切换主题可以进行小幅度的换肤设置(比如某个自定义组件的主题),而如果我们想要对整个应用程序做主题切换那么通过加载资源包的这种方式目前应该说是比较好的了。

三 Android的换肤知识点

1. 换肤相应的API

我们先来看一下Android的提供的一些基本的API,通过使用这些API可以在应用程序内部进行资源对象的替换。

公共类资源{
public String getString(int id)throws NotFoundException {
CharSequence res = mAssets.getResourceText(id);
if(res!= null){
返回资源;
}
抛出新的NotFoundException(“字符串资源ID#0x”

  • Integer.toHexString(id));
    }
    public Drawable getDrawable(int id)throws NotFoundException {
    / *部分代码省略 /
    }
    public int getColor(int id)throws NotFoundException {{
    / *部分代码省略 /
    }
    / *部分代码省略 /
    }

这个是我们常用的资源类的API,我们通常可以使用在资源文件中定义的@+id字符串类型,然后在编译出的R.java中对应的资源文件生产的编号(INT类型),从而通过这个ID(INT类型)调用资源提供的这些API获取到对应的资源对象。这个在同一个应用程序下没有任何问题,但是在皮肤包中我们怎么获取这个ID值呢。

公共类资源{
/ *部分代码省略 /
/ **
*通过给的资源名称返回一个资源的标识id。

  • @paramname描述资源的名称
  • @ paramdefType资源的类型
  • @paramdefPackage包名
  • @返回返回资源ID,0标识未找到该资源
  • /
    public int getIdentifier(String name,String defType,String defPackage){
    if(name == null){
    抛出新的NullPointerException(“name is null”);
    }
    尝试{
    return Integer.parseInt(name);
    } catch(例外e){
    // 忽视
    }
    return mAssets.getResourceIdentifier(name,defType,defPackage);
    }
    }

资源提供了可以通过@+id,类型,PACKAGENAME这三个参数就可以在AssetManager中寻找相应的软件包名中有没有输入类型并且ID值都能与参数对应上的ID,进行返回。然后我们可以通过这个ID再调用资源的获取资源的API就可以得到相应的资源。

我们这里需要注意的一点一的英文getIdentifier(String name, String defType, String defPackage)方法状语从句:getString(int id)方法所调用资源对象的mAssets对象必须是同一个,并且包含有PACKAGENAME这个资源包。

2.AssetManager构造

怎么构造一个包含特定的packageName资源的AssetManager对象实例呢?

public final class AssetManagerimplements AutoCloseable {
/ *部分代码省略 /
/ **
*创建仅包含基本系统资产的新AssetManager。
*应用程序通常不会使用此方法,而是检索

  • {@ linkResources#getAssets}的适当资产经理。不是为了
    *由应用程序使用。
  • {@hide}
  • /
    public AssetManager(){
    synchronized(this){
    if(DEBUG_REFS){
    mNumRefs = 0;
    incRefsLocked(this.hashCode());
    }
    INIT(假);
    if(localLOGV)Log.v(TAG,“新资产经理:”+这个);
    ensureSystemAssets();
    }
    }

从AssetManager构造的函数来看有{@hide}的朱姐,所以在其他类里面是直接创建AssetManager实例。但是不要忘记的Java中还有反射机制可以创建类对象。

1
AssetManager assetManager = AssetManager.class.newInstance();

让创建的assetManager包含特定的PACKAGENAME的资源信息,怎么办?我们在AssetManager中找到相应的API可以调用。

public final class AssetManagerimplements AutoCloseable {
/ *部分代码省略 /
/ **
*向资产经理添加一组额外资产。这可以
*目录或ZIP文件。不适用于应用程序。返回
*添加资产的cookie,或失败时为0。

  • {@hide}
  • /
    public final int addAssetPath(String path){
    synchronized(this){
    int res = addAssetPathNative(path);
    if(mStringBlocks!= null){
    makeStringBlocks(mStringBlocks);
    }
    返回资源;
    }
    }
    }

同样改方法也不支持外部调用,我们只能通过反射的方法来调用。

/ **

  • apk路径
  • /
    String apkPath = Environment.getExternalStorageDirectory()+“/ skin.apk”;
    AssetManager assetManager = null;
    尝试{
    AssetManager assetManager = AssetManager.class.newInstance();
    AssetManager.class.getDeclaredMethod(“addAssetPath”,String.class).invoke(assetManager,apkPath);
    } catch(Throwable th){
    th.printStackTrace();
    }

至此我们可以构造属于自己换肤的资源了。

3.换肤资源构造

public Resources getSkinResources(Context context){
/ **
*插件apk路径

  • /
    String apkPath = Environment.getExternalStorageDirectory()+“/ skin.apk”;
    AssetManager assetManager = null;
    尝试{
    AssetManager assetManager = AssetManager.class.newInstance();
    AssetManager.class.getDeclaredMethod(“addAssetPath”,String.class).invoke(assetManager,apkPath);
    } catch(Throwable th){
    th.printStackTrace();
    }
    返回新资源(assetManager,context.getResources()。getDisplayMetrics(),context.getResources()。getConfiguration());
    }

4.使用资源包中的资源换肤

我们将上述所有的代码组合在一起就可以实现,使用资源包中的资源对应用程序进行换肤。

public Resources getSkinResources(Context context){
/ **
*插件apk路径

  • /
    String apkPath = Environment.getExternalStorageDirectory()+“/ skin.apk”;
    AssetManager assetManager = null;
    尝试{
    AssetManager assetManager = AssetManager.class.newInstance();
    AssetManager.class.getDeclaredMethod(“addAssetPath”,String.class).invoke(assetManager,apkPath);
    } catch(Throwable th){
    th.printStackTrace();
    }
    返回新资源(assetManager,context.getResources()。getDisplayMetrics(),context.getResources()。getConfiguration());
    }
    @覆盖
    protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    的setContentView(R.layout.activity_main);
    ImageView imageView =(ImageView)findViewById(R.id.imageView);
    TextView textView =(TextView)findViewById(R.id.text);
    / **
    *插件资源对象
  • /
    Resources resources = getSkinResources(this);
    / **
    *获取图片资源
  • /
    Drawable drawable = resources.getDrawable(resources.getIdentifier(“night_icon”,“drawable”,“com.tzx.skin”));
    / **
    *获取文本资源
  • /
    int color = resources.getColor(resources.getIdentifier(“night_color”,“color”,“com.tzx.skin”));

imageView.setImageDrawable(绘制);
textView.setText(文本);

}

通过上述介绍,我们可以简单的对当前页面进行换肤了。但是想要做出一个一个成熟换肤框架那么仅仅这些还是不够的,提高一下我们的思维高度,如果我们在查看创建的时候就直接使用皮肤资源包中的资源文件,那么这无疑就使换肤更加的简单已维护。

5. LayoutInflater.Factory

我看过一篇前遇见LayoutInflater及工厂文章的这部分可以省略掉。

很幸运的Android给我们在查看生产的时候做修改提供了法门。

公共抽象类LayoutInflater {
/ 部分代码省略* /
公共接口工厂{
public View onCreateView(String name,Context context,AttributeSet attrs);
}

public interface Factory2extends Factory {
public View onCreateView(查看父级,字符串名称,上下文上下文,AttributeSet attrs);
}
/ 部分代码省略* /
}

我们可以给当前的页面的窗口对象在创建的时候设置工厂,那么在窗口中的视图进行创建的时候就会先通过自己设置的工厂进行创建.Factory方式使用相关状语从句:注意事项请移位到遇见LayoutInflater及工厂,关于工厂的相关知识点尽在其中。

四 Android的皮肤,装载机解析

1. 初始化

  • 初始化换肤框架,导入需要换肤的资源包(当前为一个APK文件,其中只有资源文件)。

公共类SkinApplicationextends Application {
public void onCreate(){
super.onCreate();
initSkinLoader();
}
/ **
*必须先调用init

  • /
    private void initSkinLoader(){
    。SkinManager.getInstance()的init(本);
    SkinManager.getInstance()负载();
    }
    }

2.构造换肤对象

导入需要换肤的资源包,并构造换肤的资源实例。

/ **
*在asyc任务中从apk加载资源

  • @ paramskinPackagePath皮肤路径apk
  • @paramcallback回调通知用户
  • /
    public void load(String skinPackagePath,final ILoaderListener callback){

新的AsyncTask(){

protected void onPreExecute(){
if(callback!= null){
callback.onStart();
}
};

@覆盖
protected资源doInBackground(String … params){
尝试{
if(params.length == 1){
String skinPkgPath = params [0];

File file = new File(skinPkgPath);
if(file == null ||!file.exists()){
return null;
}

PackageManager mPm = context.getPackageManager();
//检索程序外的一个安装包文件
PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath,PackageManager.GET_ACTIVITIES);
//获取安装包报名
skinPackageName = mInfo.packageName;
//构建换肤的AssetManager实例
AssetManager assetManager = AssetManager.class.newInstance();
方法addAssetPath = assetManager.getClass()。getMethod(“addAssetPath”,String.class);
addAssetPath.invoke(assetManager,skinPkgPath);
//构建换肤的资源实例
资源superRes = context.getResources();
资源skinResource = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());
//存储当前皮肤路径
SkinConfig.saveSkinPath(context,skinPkgPath);

skinPath = skinPkgPath;
isDefaultSkin = false;
return skinResource;
}
return null;
} catch(例外e){
e.printStackTrace();
return null;
}
};

protected void onPostExecute(参考资料结果){
mResources =结果;

if(mResources!= null){
if(callback!= null)callback.onSuccess();
//更新多有可换肤的界面
notifySkinUpdate();
}其他{
isDefaultSkin = true;
if(callback!= null)callback.onFailed();
}
};

} .execute(skinPackagePath);
}

定义基类
换肤页面的基类的通用代码实现基本换肤功能。

public class BaseFragmentActivityextends FragmentActivityimplements ISkinUpdate,IDynamicNewView {

最后

在此为大家准备了四节优质的Android高级进阶视频:

架构师项目实战——全球首批Android开发者对Android架构的见解

附相关架构及资料

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。**

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-eZZO13it-1713155361105)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值