Android个人项目插件化总结——插件apk资源加载

Android插件化完美实现代码资源加载及原理讲解 附可运行demo *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 。 我们通过前4篇的分解,分别将插件化设计到的知识点全部梳理了一遍,如果没有看过的,建议先看前面4篇 1. Binder机制 2. 插件化知识详细分解及原理 之代理,hook,反射, 3. 类加载及dex加载 4. 应用启动过程及类加载过程 5. Android插件化完美实现代码资源加载及原理讲解 附可运行demo 阅读详情

为什么加载不了资源

我们知道获取资源时实际都是使用了Resource。

mResource这个变量在Activity父类ContextThemeWrapper中。

ContextThemeWrapper的mResource的获得是通过Context的实现类得到的。

Context实现类ContextImpl中获得Resources 是通过

 Resources resources = packageInfo.getResources(mainThread);

packageInfo 是一个LoadedApk对象。

这个对象相当于是一个apk在内存中的表示, Apk文件的相关信息、资源都可以通过此对象获取。

这个LoadedApk是怎么创建的呢。

在ActivityThread的getPackageInfo方法中:

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,  ClassLoader baseLoader, boolean securityViolation, boolean includeCode,boolean registerPackage) {
    synchronized (mResourcesManager) {
        WeakReference<LoadedApk> ref;
        if (includeCode) {
            ref = mPackages.get(aInfo.packageName);
        } else {
            ref = mResourcePackages.get(aInfo.packageName);
        }
        LoadedApk packageInfo = ref != null ? ref.get() : null;
        if (packageInfo == null || (packageInfo.mResources != null
                && !packageInfo.mResources.getAssets().isUpToDate())) {
            if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                    : "Loading resource-only package ") + aInfo.packageName
                    + " (in " + (mBoundApplication != null
                            ? mBoundApplication.processName : null)
                    + ")");
                    //在这里创建
            packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader,
                        securityViolation, includeCode 
                        && aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
          ······
        }
        return packageInfo;
    }
} 

可以看到构造参数里面有个参数是ApplicationInfo 。

所以说不管前面是hook IActivityManager合并dex还是hook Instrumentation 传入我们的ClassLoader,这个LoadedApk里传入的ApplicationInfo其实还是我们宿主的,并不是插件apk中的。

所以我们在插件apk中直接使用资源,等到插件apk被宿主调用器后,使用的是宿主的资源库,而宿主的资源中并没有我们插件apk中的资源,所以一运行的时候就会报错。

那么怎么解决呢。

我们创建一个Resource给我们的插件Activity就可以了。

如何加载资源

我们知道一般得到资源调用的是getResource方法,最终调用了Context.getResources()方法。

我们看看Context实现类ContextImpl对应的方法。

 @Override
public Resources getResources() {
    return mResources;
}

再看看mResource是如何赋值的。

private ContextImpl(ContextImpl container, ActivityThread mainThread,
        LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
        Display display, Configuration overrideConfiguration, int createDisplayWithId) {

    ...

    mPackageInfo = packageInfo;

    //这里拿到了一个ResourcesManager,单例的,说明我们应用当中使用的都是同一套资源
    mResourcesManager = ResourcesManager.getInstance();

    ...

    //LoadedApk对象中得到Resources对象
    Resources resources = packageInfo.getResources(mainThread);
    if (resources != null) {
        if (activityToken != null
                || displayId != Display.DEFAULT_DISPLAY
                || overrideConfiguration != null
                || (compatInfo != null && compatInfo.applicationScale
                        != resources.getCompatibilityInfo().applicationScale)) {
            resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
                    packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
                    packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
                    overrideConfiguration, compatInfo, activityToken);
        }
    }
    //给mResources赋值
    mResources = resources;
    ...

} 

ResourcesManager是一个单例,通过它获得mResources,保证了我们每个Context获取的都是同样的资源。

resources通过getTopLevelResources方法赋值,我们具体看看是如何创建的

public Resources getTopLevelResources(String resDir, String[] splitResDirs,
            String[] overlayDirs, String[] libDirs, int displayId,
            Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {

		······
        //创建一个AssetManager对象
        AssetManager assets = new AssetManager();
        // resDir can be null if the 'android' package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the 'android' package
        // already.
        if (resDir != null) {'
            //添加资源路径
            if (assets.addAssetPath(resDir) == 0) {
                return null;
            }
        }

        if (splitResDirs != null) {
            for (String splitResDir : splitResDirs) {
                if (assets.addAssetPath(splitResDir) == 0) {
                    return null;
                }
            }
        }

        if (overlayDirs != null) {
            for (String idmapPath : overlayDirs) {
                assets.addOverlayPath(idmapPath);
            }
        }

        if (libDirs != null) {
            for (String libDir : libDirs) {
                if (assets.addAssetPath(libDir) == 0) {
                    Slog.w(TAG, "Asset path '" + libDir +
                            "' does not exist or contains no resources.");
                }
            }
        }

        //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
        DisplayMetrics dm = getDisplayMetricsLocked(displayId);
        Configuration config;
        boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
        final boolean hasOverrideConfig = key.hasOverrideConfiguration();
        if (!isDefaultDisplay || hasOverrideConfig) {
            config = new Configuration(getConfiguration());
            if (!isDefaultDisplay) {
                applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
            }
            if (hasOverrideConfig) {
                config.updateFrom(key.mOverrideConfiguration);
            }
        } else {
            config = getConfiguration();
        }'
        //创建一个Resource对象
        r = new Resources(assets, dm, config, compatInfo, token);
        if (false) {
            Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
                    + r.getConfiguration() + " appScale="
                    + r.getCompatibilityInfo().applicationScale);
        }

        synchronized (this) {
            WeakReference<Resources> wr = mActiveResources.get(key);
            Resources existing = wr != null ? wr.get() : null;
            if (existing != null && existing.getAssets().isUpToDate()) {
                // Someone else already created the resources while we were
                // unlocked; go ahead and use theirs.
                r.getAssets().close();
                return existing;
            }

            // XXX need to remove entries when weak references go away
            mActiveResources.put(key, new WeakReference<Resources>(r));
            return r;
        }
    } 

从上述源码可以得到如下创建Resource的步骤

  • 创建一个AssetManager对象
  • 通过addAssetPath方法将资源路径添加给AssetManager,需要注意的是,这个方法是hide的,需要反射调用。
  • 通过AssetManager对象创建一个Resource对象,构造方法中,需要一个AssetManager,其他的可以使用宿主的。

这样就好办了。

我们在activity刚创建好时,将我们的Resource通过反射添加到ContextThemeWrapper中。

这个时机该是什么时候呢?

那就是Instrumentation的newActivity方法中。

看一下代码实现:


    public PluginApp loadPluginApk(String apkPath) {
        String addAssetPathMethod = "addAssetPath";
        PluginApp pluginApp = null;
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod,
                    String.class);
            addAssetPath.invoke(assetManager, apkPath);
            Resources pluginRes = new Resources(assetManager,
                    mContext.getResources().getDisplayMetrics(),
                    mContext.getResources().getConfiguration());
            pluginApp = new PluginApp(pluginRes);
            pluginApp.mClassLoader = createDexClassLoader(apkPath);
        } catch (IllegalAccessException
                | InstantiationException
                | NoSuchMethodException
                | InvocationTargetException e) {
            e.printStackTrace();
        }
        return pluginApp;
    }


    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {

        if (mPluginManager.hookToPluginActivity(intent)) {
            String targetClassName = intent.getComponent().getClassName();
            PluginApp pluginApp = mPluginManager.getLoadedPluginApk();
            Activity activity = mBase.newActivity(pluginApp.mClassLoader, targetClassName, intent);
            ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);
            return activity;
        }

        return super.newActivity(cl, className, intent);
    }

先创建好我们的Resource,然后在newActivity方法内通过反射赋值。

插件化开发—动态加载技术加载已安装和未安装的apk 首先引入一个概念,动态加载技术是什么?为什么要引入动态加载?它有什么好处呢?首先要明白这几个问题,我们先从 应用程序入手,大家都知道在Android App中,一个应用程序dex文件的方法数最大不能超过65536个,否则,你的app 将出异常了,那么如果越大的项目那肯定超过了,像美团、支付宝等都是使用动态加载技术,支付宝在去年的一个技 术分享类会议上就推崇让应用程序插件化,而美团也公布了他们 阅读详情

相关推荐

Android插件化资源的使用及动态加载 附demo

上一篇我们已经完成了一个真正可运行的插件化demo,而且demo中也解决了插件中不可以使用资源的问题,但是由于篇幅的问题我们并没有对原理讲解,所以这一篇是对上一篇的一个收尾,如果没有看过上一篇建议先看Android插件化完美实现代码资源加载及原理讲解 附可运行demo.demo地址 : https://github.com/ljqloveyou123/LiujiaqiAndroid我们的宿主应用调用

yulong0809的专栏 7566

Android插件化学习之加载插件资源

在前面两篇我们学习了如何类加载机制的原理以及如何启动插件activity;Android插件化学习之初识类加载机制Android插件化学习之启动插件Activity我们前面也提到过启动插件activity时如果直接加载layout资源会出错,那是因为什么原因呢?想要了解这个问题的原因,就需要对资源加载流程有一定的认知,接下来我们就来一起看下系统是如何加载resource资源的;通过上面的学习发现,归根到底还是对源码的阅读以及反射的运用,这里感叹一下,反射真是太强大了,许多黑科技都是通过反射实现的,当然。..

故不积跬步无以至千里 1078

浅谈Android插件化

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!资料⬅专栏获取到这里基本的框架就差不多结束了。

2401_85493350的博客 1480

【Android】插件化系列之加载异常问题如何解决?

目前我们国内的游戏 SDK 采用了插件化的技术,优点是 SDK 可以通过热更新来完成自更新,缺点是会遇到各种各样奇奇怪怪的问题,最近就我个人遇到的一些插件化问题来给大家做一次分享,主要分为两个部分: 排查和解决资源加载不到导致的报错 排查和解决 so 库加载不到导致的报错.........

Eqiqi的博客 3448

android插件化

概述 插件化是一种动态升级app功能的解决方案,不同于热修复(仅仅是修复功能),类似于RN、Weex(目的类似)。都是为了在不发版本的情况下,可以让用户用上最新的功能。不过RN、Weex还额外支持跨平台。相对于RN和Weex,插件化有以下的优缺点: 优点: 对于业务方,无额外的学习成本,基本无感知 性能等同于原生、可以做任何原生可以做的事情 天然代码隔离、使得插件化的代码更加的“高内聚、低耦合” 插件并发开发,开发之间互不影响 缺点: 稳定性差,使用了大量大反射...

long8313002的博客 4985

Android 插件化

1.插件化 插件可以理解为免安装的Apk,而支持插件的app称为宿主。 在Android系统中,应用是以Apk的形式存在的,应用都需要安装才能使用。实际上Android系统安装应用的方式相当简单,就是把应用Apk拷贝到系统不同的目录下,然后把so解压出来而已。 常见的应用安装目录有: /system/app:系统应用 /system/priv-app:系统应用 /data/app:用户应用 一个Apk会包含如下几个部分: classes.dex:Java代码字节码 res:资源文件 .

zenmela2011的专栏 3941

Android res文件找不到

在编一个应用时,res文件添加了一个png图片,可是一直报错找不到。 后面直接把下面这个文件删掉就ok了,目录如下: out\target\common\obj\APPS\SystemUI_intermediates

wuxy_shenzhen的专栏 1892

Python实战:5分钟搞定N-CMAPSS航空发动机数据集解析(附完整代码)

本文提供了一份针对N-CMAPSS航空发动机数据集的快速解析实战指南。通过Python的h5py库,详细演示了如何读取HDF5文件结构、理解数据分类(如传感器、健康参数、场景描述符)并加载为Pandas DataFrame。文章还澄清了数据集中常见的变量数量疑惑,并给出了从数据加载到构建剩余使用寿命预测模型预处理流水线的完整代码,帮助工程师和研究人员高效入门航空发动机预测性维护分析。

weixin_29057065的博客 422

Android 插件化开发之获取APK的代码与资源

Android 插件化开发之获取APK的代码与资源插件化开发,可以分为3种形式:1,通过下载zip文件来应用资源。2,通过下载并安装APK来应用资源。3,通过下载但不安装APK的方式来应用资源。 第一种比较简单,就不做介绍了。 获取已安装的APK中的资源和代码 获取未安装的APK中的资源和代码 1.获取已安装的APK中的资源和代码1.1获取插件APK的ContextContext packageCo

qq_21146289的博客 1418

实战指南 | 快速修复Adobe Acrobat 2020安装过程中的常见注册表错误

本文提供了Adobe Acrobat 2020安装过程中遇到“无法打开键”注册表错误的详细修复指南。通过安全编辑注册表,精准定位并删除残留键值,可快速解决此常见安装报错,确保软件顺利安装。同时,文章也介绍了其他安装问题的应对策略与预防措施。

weixin_29200485的博客 560

Android图形系统(三)系统篇:渲染/合成的底层原理浅析

众所周知,渲染工作是由GPU负责,那么在聊GPU之前首先得知道什么是“渲染”?我们来举个例子感受一下在系统中申请一块10*101、把图层背景这玩意染成绿的2、从左上(0,0)到右下(10,10)画一条宽度为1颜色为红色的直线3、以坐标点(5,5)为中心,画一个半径为3的实心圆,颜色要蓝色好了,接下来操作系统会把这些“绘图指令”同步给GPU去执行渲染任务图片来源:自己画的GPU完成渲染工作后,我们将会得到一个大小为10*10二维数组,数组中的每个元素都保存着坐标点的颜色信息、深度信息。

1355

Android 插件化之—— 加载插件中的资源

Android 资源分类: res目录下的资源 res目录下的资源可以通过Resource对象进行访问,通过分析Resource源码可知,Resource访问res目录下的资源其实还是调用的AssetManager类的方法访问的。 assets目录下的资源 assets目录的访问,通常在Activity中,是获取到AssetManager对象后,使用AssetManager来访问asset中的资...

hujin2017的博客 1985

飞牛NAS一键部署小雅Emby全家桶:解锁海量影音资源与智能刮削

本文详细介绍了如何在飞牛NAS上通过Docker一键部署小雅Emby全家桶,完美解决传统WebDAV挂载无法刮削元数据的问题。该方案整合了小雅Alist海量影音资源与Emby媒体服务器,通过预下载超百GB元数据包,自动实现海报、简介等信息的完美匹配,打造出精美的个人影音库。部署过程需准备阿里云盘令牌与充足存储空间,并详解了从环境配置、镜像拉取到最终验证的完整步骤及优化技巧。

weixin_28312391的博客 919

Android插件化原理(三)——加载插件资源

讲解插件化换肤原理,如何加载插件资源

blankmargin的博客 1474

Rockchip开发板FUSB302快充调试避坑指南:从设备树配置到驱动修改全流程

本文详细解析了在Rockchip开发板上调试FUSB302快充功能的全流程,重点涵盖设备树配置、驱动修改及硬件设计避坑指南。针对PD协议协商失败、电流倒灌等常见问题,提供了从软件配置到硬件排查的实战解决方案,帮助嵌入式工程师高效完成快充功能开发。

weixin_28256233的博客 114

LTspice introduction - 2 LTspice的界面

LTspice的界面主要有两个:Schematic Capture和Waveform Viewer Schematic Capture 它主要是用来画电路图的,画完电路图之后,添加一些dot 命令同时选择一些仿真的参数,点击执行(Run)Waveform Viewer 如果你的仿真不是op point的话,一个都会打开它,在它里面你可以画图相应的图形。 有两种方式可以在Viewer

贾亮的专栏 3220

【Android 插件化】插件化简介 ( 组件化与插件化 )

一、组件化与插件化、 二、插件化示例、 三、插件化标准引入、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 9732

12、知识图谱数据处理与推理全解析

本文深入解析了知识图谱中的数据处理与推理技术,涵盖SHACL的多种约束类型(如值约束、范围约束、字符串约束、逻辑约束等)及其在数据质量控制中的应用,并介绍了常用的SHACL工具与实际案例。文章对比了SPARQL与SHACL的特点,探讨了RDF(S)的局限性,引出表达能力更强的OWL和OWL2语言及其在语义网和生物医学等领域的应用。此外,还阐述了规则在知识表示中的作用,包括产生式规则和Datalog规则,并总结了知识图谱中推理的重要性与典型流程,为构建高质量、可推理的知识图谱提供了全面的技术指导。

zz67890的博客 135

Android插件化技术

一、Android插件化技术 我们在平时的开发过程中,会经常遇到产品需求的变更或者出现bug,在传统的模式中,我们需要首先需要修改代码,然后重新打包Apk,再交给公司的运营去官网或者应用商店上线,用户在打开应用的时候就会进行更新了。但是这种模式有几个缺点,一是上线周期长,从修改代码到用户更新需要较长的时间;二是用户更新代价较大,每次用户更新都需要下载整个Apk包,整个Apk包包括了...

xiao_nian的博客 7888

python中视觉检测怎么将多个面拍照的图片合成一个3d图像,用什么技术或者什么模型?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 708

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Android插件化动态加载apk

插件化动态加载apk

qq_27938211的博客 3428

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

安卓插件化课程-第四篇:加载apk中的资源

序: 1.本文是安卓插件化课程的第四篇,完整课程链接参见下面链接: 安卓插件化课程-序章_分享+记录-优快云博客前言:目前安卓领域,插件化十分的流行,本以为这一类的文章会有很多,但是百度一搜,基本上讲的插件化都是皮毛,没有涉及到核心。所以就想写一系列的文章来一步一步深入的讲解插件化,通过实现逐渐增加难度的需求,最终实现插件化方案。章节:插件化系列文章主要包含以下几个篇章:1.加载插件apk中的类2.启动插件apk中的activity3.使用插桩的方式启动apk中的activity4.使用插件apk中.

分享+记录 2936

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Android插件化(三)加载插件apk中的Resource资源

Android加载插件apk中的Resource资源简介如何加载未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源码中发现,它有一个私有方法addAssetPath,只需要将apk的路径作为参数传入,我们就可以获得对应的AssetsManager对象,然后我们就可以使用AssetsManager对象,创建一个Resources对象,然后就

郑海波(莫川)的优快云博客 1万+

插件化之APK动态加载,看这篇就够了

最近在研究apk的动态加载,无论是在简书还是优快云上阅读了很多博客,但是发现很多博主虽然讲的很详细,但是很多文章都是14,15年的,而且有的文章并没有提供demo或者提供的demo根本跑不起来,搞得我一脸懵逼,学习遇到了很多阻力。但是呢,天道酬勤,最终在刻苦钻研几天后,对动态加载算是有了一定的眉目,且听我下文缓缓道来。 首先动态加载apk,一定会有一个宿主apk和一个插件apk,所谓的动态加载,无非是在宿主的apk中,加载插件apk里的activity,类似于支付宝中打开飞猪、淘票票等页面。而要做到上面这

qq_36378836的博客 4648

安卓插件化系列课程-一步步带你实现插件化

前言: 目前安卓领域,插件化十分的流行,本以为这一类的文章会有很多,但是百度一搜,基本上讲的插件化都是皮毛,没有涉及到核心。 所以就想写一系列的文章来一步一步深入的讲解插件化,通过实现逐渐增加难度的需求,最终实现插件化方案。 章节: 插件化系列文章主要包含以下几个篇章: 1.加载插件apk中的类 2.启动插件apk中的activity 3.使用插桩的方式启动apk中的activity 4.使用插件apk中的资源 5.使用插件apk中的so文件 6.插件化加载移动apk。 7.插件化目前所遇到的

分享+记录 2543

Android插件化之资源动态加载

Android插件化之资源动态加载 一.概述 Android插件化的一个重要问题就是插件资源访问问题,先列出会面对的问题 1.如何加载插件资源 2.如何处理插件资源与宿主资源的处突:插件化资源问题要做到的效果是,如果我们要获取的资源在插件中找得到,则加载优先加载插件的,如果找不到,则到宿主资源中找。这样能做到动态更新的效果。 3.如何确保插件和宿主使用到的是被修改过的资源。

scholarSu 4607
上一篇: Android个人项目插件化总结——方式二(Hook Instrumentation)
下一篇: 热更新原理总结
PdKingLiu
博客等级 码龄9年 89粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值