文章目录

  • 1. 前言
  • 2. 分析
  • 3. 加载外部资源文件代码
  • 4. References

1. 前言

在上篇Android插件化开发指南——Hook技术(一)【长文】中提到最终的效果其实在插件中的MainActivity加载的资源文件activity_main.xml其实加载的还是宿主appactivity_main.xml文件。所以在这篇中将解决如何从插件apk中加载资源文件的问题。首先我们需要知道资源存储在apk包的什么位置,不妨在AS中打开插件的apk文件,可以看见其文件结构为:

也就是在resources.arse文件中。不妨来看看在Android中是如何加载资源的。

2. 分析

比如下面从strings.xml文件中获取值:

// MainActivity.java
String string = getString(R.string.app_name);

就来看看这个方法的背后是怎么实现的。追踪可以看到:

// Context.java
public final String getString(@StringRes int resId) {return getResources().getString(resId);
}

也就是说是通过context上下文对象的getResources方法,然后再通过getString来得到的。换句话说,在上下文context调用getResources方法后,就持有了资源本身,所以才可以通过getString来得到。为了验证,这里不妨先追踪下getString()方法:

// Resources.java
private ResourcesImpl mResourcesImpl;public String getString(@StringRes int id) throws Resources.NotFoundException {return getText(id).toString();
}public CharSequence getText(@StringRes int id) throws Resources.NotFoundException {CharSequence res = mResourcesImpl.getAssets().getResourceText(id);if (res != null) {return res;}throw new Resources.NotFoundException("String resource ID #0x"+ Integer.toHexString(id));
}

最终通过mResourcesImpl.getAssets()来得到一个AssetManager对象,然后再通过AssetManager对象来获取到资源。所以说这里其实流程为:

通过context来获取到资源对象Resources,然后在资源对象Resources中,通过ResourcesImpl类的实例对象来获取到AssetManager对象,然后再获取到资源对象。

所以如果我们可以仿写一个得到我们自己资源的Resources,并把他赋值给当前contextgetResources()方法,那么就可以做到资源的替换。那么思路为在App中定义一个继承自Application的父类,在这个方法中重写getResources()方法,如果调用getResources()方法能够获取到我们自定义的插件的资源,就直接返回;如果获取不到那么就使用当前应用自己的getResources()方法。也就是这里的重点在于如何仿写一个获取到插件Resources对象的包装类。

在前面提到了,插件资源文件位于resources.arse文件中。所以说如果我们需要加载插件中的资源文件,类似的还是需要从apk文件中读取。我们知道要得到Resources对象,首先需要封装一个AssetManager对象,所以这里看看AssetManager.java的实现。当然首先需要解决的问题是如何通过反射来获取到这个对象,在这个类中提供了一个加载外部资源文件的方法:

/*** 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);
}

所以这里也是通过反射这个方法来加载外部资源对象。比如:

private static String pluginPath = "/sdcard/plugin-debug.apk";AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPath.setAccessible(true);
addAssetPath.invoke(assetManager, pluginPath);

因为在getResources()方法返回的是一个Resources对象,所以这里继续查看Resources.java类中的和资源文件关联的方法。可以看到这么一个方法:

@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {this(null);mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}

故而尝试使用下面的代码:

resources = new Resources(assetManager, context.getResources().getDisplayMetrics(),context.getResources().getConfiguration());

3. 加载外部资源文件代码

public static Resources loadPluginResource(Context context){Resources resources = null;try {AssetManager assetManager = AssetManager.class.newInstance();Method addAssetPath = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);addAssetPath.setAccessible(true);addAssetPath.invoke(assetManager, pluginPath);resources = new Resources(assetManager, context.getResources().getDisplayMetrics(),context.getResources().getConfiguration());}catch (Exception e){e.printStackTrace();}return resources;
}

为了能满足资源文件要么找自己应用程序的资源文件,要么找外部插件中的资源文件的逻辑,这里构建一个BaseApplication

public class BaseApplication extends Application {private static final String pluginPath = "/sdcard/plugin-debug.apk";private Resources pluginResources;@Overridepublic void onCreate() {super.onCreate();LoadUtils.loadClass(this, pluginPath); // 原init方法,修改了名字HookAMSUtils.getActivityManagerService(this, ProxyActivity.class);HookAMSUtils.hookActivityThreadToLaunchActivity();pluginResources = LoadUtils.loadPluginResource(this, pluginPath);}@Overridepublic Resources getResources() {if (pluginResources != null) return pluginResources;return super.getResources();}
}

然后配置一下清单文件:

android:name="BaseApplication"

当然因为写代码是在Activity中,所以这里还需要定义一个BaseActivity,在这个类的getResources方法中调用BaseApplicationgetResources方法,即:

public class BaseActivity extends AppCompatActivity {@Overridepublic Resources getResources() {if(getApplication() != null && getApplication().getResources() != null)return getApplication().getResources();return super.getResources();}
}

案例地址:https://github.com/baiyazi/pluginLearn/tree/main/demo1


4. References

References

  • Android插件化开发指南——Hook技术(一)【长文】
  • AssetManager.java
  • 29讲玩转插件化:深入底层分析Android插件化原理,从0到1手写实现360插件化项目架构

Android插件化开发指南——Hook技术(二)相关推荐

  1. Android插件化开发指南——Hook技术(一)【长文】

    文章目录 1. 前言 2. 将外部dex加载到宿主app的dexElements中 3. 插件中四大组件的调用思路 4. Hook 2.1 对startActivity进行Hook 2.1.1 AMS ...

  2. Android插件化开发指南——插件化技术简介

    文章目录 1. 为什么需要插件化技术 2. 插件化技术的历史 3. 插件化实现思路 3.1 InfoQ:您在 GMTC 中的议题叫做<Android 插件化:从入门到放弃>,请问这个标题代 ...

  3. Android插件化开发指南——实践之仿酷狗音乐首页

    文章目录 1. 前言 2. 布局分析 3. 底部导航栏的实现 4. 顶部导航栏和ViewPager+Fragment的关联 1. 前言 在Android插件化开发指南--2.15 实现一个音乐播放器A ...

  4. Android插件化开发指南——实践之Activity转场效果(仿酷狗音乐启动页)

    文章目录 1. 前言 2. Activity退出动画 2.1 简单使用 2.2 overridePendingTransition 3. 后记 1. 前言 在Android插件化开发指南--2.15 ...

  5. Android插件化开发指南——实践之仿酷狗音乐首页(自定义ImageView控件)

    文章目录 1. 前言 2. 基础环境--实现RecyclerView的网格布局 3. 自定义ImageView 3. 后记 1. 前言 拟定实现效果部分为下图的歌单列表部分,也就是图中红线框出来的部分 ...

  6. android 禁止插件化,Android 插件化实现方式(Hook)

    一.首先我们要找到Hook的点 1. 分析 我们先大概看下activity的启动流程(图片来自Android 插件化开发指南) image 当我们调用startActivity的时候,AMS对我们要启 ...

  7. Android 插件化原理解析——Hook机制之AMSPMS

    在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...

  8. Android 插件化原理学习 —— Hook 机制之动态代理

    前言 为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有很多,由于 webview 本身的性能问题,也随之出现了很多基于 JS 引擎实现的原生渲染的方案,例如 React Nat ...

  9. Android插件化开发之解决OpenAtlas组件在宿主的注冊问题

    Android插件化开发之解决OpenAtlas组件在宿主的注冊问题 OpenAtlas有一个问题,就是四大组件必须在Manifest文件里进行注冊,那么就必定带来一个问题,插件中的组件都要反复在宿主 ...

最新文章

  1. Cocos2d-x3.2 屏幕截图
  2. 二分查找算法java
  3. 云市场合作伙伴-袋鼠云获A轮融资,成立一年半获三轮投资超亿元
  4. 拓端tecdat|R语言Bass模型进行销售预测
  5. Pick定理 有趣的证明
  6. VideoView源码分析
  7. flash一直提示要重新安装,都已经是最新的了,但是还要求更新
  8. linux parallel指令参数,GNU Parallel的具体使用
  9. 帝国CMS安全设置大全
  10. 华硕路由搭建php网站,华硕路由器操作模式
  11. 谷歌:加入账号其他设备登陆通知功能
  12. The bean 'llWebSocketHandler' could not be injected because it is a JDK dynamic proxy that implemen
  13. jQuery实现图片卡片层叠式切换效果
  14. C语言中Switch语句的范围比较解决方案(学习笔记)
  15. 外泌体的三种分离方法及其临床意义
  16. 低市值高业绩的TCL,能否借“元宇宙”风口说新故事?
  17. java 装配模式_java23种设计模式代码 Java装配模式
  18. AI-制作纸张纹理效果
  19. 【论文阅读】《Efficient and privacy-preserving range-max query in fog-based agricultural IoT》
  20. Building FFplay for Windows

热门文章

  1. 路由器进不去的解决办法
  2. python execjs 网站_Python ExecJS JavaScript引擎使用Python类
  3. matlab pca和逆pca函数,PCA的原理及MATLAB实现
  4. PPT中实现滚动字幕
  5. Nature Communications: MOGONET使用图卷积网络集成多组学数据,允许患者分类和生物标志物识别
  6. 使用maven和springMVC上传和下载文件
  7. 解封攻略 拯救你的ChatGPT账号
  8. FFmpeg之ffprobe
  9. Wordnet简单实用画树形结构
  10. 怎样linux部署web应用程序,Linux系统部署WEB项目(2020最新最详细)