- 浏览: 2204136 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (1240)
- mac/IOS (287)
- flutter (1)
- J2EE (115)
- android基础知识 (582)
- android中级知识 (55)
- android组件(Widget)开发 (18)
- android 错误 (21)
- javascript (18)
- linux (70)
- 树莓派 (18)
- gwt/gxt (1)
- 工具(IDE)/包(jar) (18)
- web前端 (17)
- java 算法 (8)
- 其它 (5)
- chrome (7)
- 数据库 (8)
- 经济/金融 (0)
- english (2)
- HTML5 (7)
- 网络安全 (14)
- 设计欣赏/设计窗 (8)
- 汇编/C (8)
- 工具类 (4)
- 游戏 (5)
- 开发频道 (5)
- Android OpenGL (1)
- 科学 (4)
- 运维 (0)
- 好东西 (6)
- 美食 (1)
最新评论
-
liangzai_cool:
请教一下,文中,shell、C、Python三种方式控制led ...
树莓派 - MAX7219 -
jiazimo:
...
Kafka源码分析-序列5 -Producer -RecordAccumulator队列分析 -
hp321:
Windows该命令是不是需要安装什么软件才可以?我试过不行( ...
ImageIO读jpg的时候出现javax.imageio.IIOException: Unsupported Image Type -
hp321:
Chenzh_758 写道其实直接用一下代码就可以解决了:JP ...
ImageIO读jpg的时候出现javax.imageio.IIOException: Unsupported Image Type -
huanghonhpeng:
大哥你真强什么都会,研究研究。。。。小弟在这里学到了很多知识。 ...
android 浏览器
Android动态加载技术在蘑菇街的第一次实践,还是在14年的时候,使用的就是之前网上广(tu)为(du)流(si)传(fang)的方式,这种方式有一个重大缺陷,就是插件内部对资源的访问只能通过自己定义的方式,包括对layout文件的inflate等,使用getResouces的方式,分分钟crash给你看,而且内部实现有些复杂,容易出现莫名其妙的ResourcesNotFound错误。在一段时间的使用之后,始终无法大面积推广,原因就是对开发人员来说,写一个“正常”的模块和写一个动态加载模块,写法是不一样的。这件事一直如哏在喉,如果这个框架无法做到对开发业务的同学们透明,那么就很难推广开去。如何做到对业务开发者透明呢,最重要的是对于各类系统api的使用,尤其是Android四大组件的使用和资源访问,都要遵循系统提供的方式。
抛开上面的东西,从头开始讲述一下动态加载的原理:
Android应用程序的.java文件在编译期会通过javac命令编译成.class文件,最后再把所有的.class文件编译成.dex文件放在.apk包里面。那么动态加载就是在运行时把插件apk直接加载到classloader里面的技术。
看完上面的原理,不知道你有没有什么疑问,反正我是有的。
上面两个问题是动态加载框架最重要的两点,无法动态安装dex或资源文件的动态加载框架都是耍流氓。我们在实现这个框架的时候同样也遇到了这两个问题。
如何动态加载插件代码:
关于代码加载,系统提供了 DexClassLoader 来加载插件代码。开发者可以对每一个插件分配一个 DexClassLoader (这是目前最常见的一种方式),也可以动态得把插件加载到当前运行环境的classloader中。蘑菇街采用的是后者,这种方式可以有效的防止各种莫名其妙的 ClassCastException ,当你在crash后台看到各种 A cast A错误而欲哭无泪的时候,我想你会喜欢上这种方式。
事情当然不会这么简单,系统提供的DexClassloader对外api中,只有一种方式可以向类加载器指定加载路径。就是在构造函数中传入apk/zip/dex路径。这完全不符合我们“动态”的原则,难道每次加载一个插件,都必须重新实例化一个类加载器出来吗?这个时候我们想到了google提供的multidex插件,这个插件旨在帮助函数超过65536上限的应用在编译期切割class到多个dex文件中。经过观察发现,5.0以下的Android系统,在应用安装的时候只认classes.dex文件,并在安装期对这个dex文件进行opt操作,生成的odex文件放在/data/dalivk-cache里面。那么剩下classes(N).dex怎么办呢,答案就是如果在编译期使用multidex插件的话,开发者还需要让自己的Application继承 MultiDexApplication ,这样说起来,这个 MultiDexApplication 应该就有加载剩下的classes(N).dex的能力了。查看 MultiDexApplication 代码,果然找到了线索:
public class MultiDexApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
[/code
可以看到,它在attachBaseContext函数调用了support包中 MultiDex 类的install函数来安装classes(N).dex,于是都是应用层代码,它能动态安装那表示我们也可以。有了以上的分析,剩下要做的就只是去扒一扒install这个函数了。
如何动态加载插件资源:
我们在开发的时候,当有需要用到资源的地方,可以直接调用 Context 的getResources()函数返回 Resources 的来访问打包在apk中的资源文件。在研究如何动态添加资源到系统的 Resources 对象的时候,有必要先了解一下 Resources 本身是如何访问到资源的。
查看系统的 Resources 源码,我们发现这个类主要做了两件事,首当其冲的当然是访问资源,另外一件就是管理资源配置信息。对于资源的动态加载来说,我们关心的是它如何做第一件事的。 实际上, Resources 对资源的访问,全部代理给了另一个重要的对象 AssetManager 。那么问题转化成了, AssetManager 是如何做到对资源的访问的。 Resources 类在它的构造函数里对 AssetManager 做了一些重要的初始化:
其中的重点就是调用了 AssetManager 对象的ensureStringBlocks()函数,这个函数的实现如下:
函数先判断mStringBlocks变量是否为空,如果不为空的话,表示需要被初始化,于是调用makeStringBlocks函数初始化mStringBlocks:
这里的mStringBlocks对象是一个StringBlock数组,这个类被标记为@hide,表示应用层根本不需要关心它的存在。那么它是做什么用的呢,它就是 AssetManager 能够访问资源的奥秘所在, AssetManager 所有访问资源的函数,例如getResourceTextArray(),都最终通过StringBlock再代理到native进行访问的。看到这里,依然没有任何看到能够指示为什么开发者可以访问自己应用的资源,那么我们再看得前面一点,看看传入 Resources 的构造函数之前,asset参数是不是被“做过手脚”。函数调用辗转到ResourceManager的getTopLevelResources函数:
函数代码有点多,截取最重要的部分,那就是系统通过调用 AssetManager 的addAssetPath函数,将需要加载的资源路径加了进去。addAssetPath函数返回一个int类型,它指示了每个被添加的资源路径在native层一个数组中的位置,这个数组保存了系统资源路径(framework-res.apk),和应用自己添加的所有的资源路径。再回过头看makeStringBlocks函数,就豁然开朗了:
有兴趣的同学可以继续深入native层的源码,可以看到不管是addAssetPath函数还是makeStringBlocks函数,使用的都是native层同一个数组,这样,这两个函数就被关联了起来。
到这里,我们已经知道了如何动态添加资源路径的“秘密”。
解决了以上两个问题,一个基本满足要求的动态加载框架就被搭了起来。
ps:查看native层Resources.cpp的代码,我们发现,Android5.0及以上版本是真正的支持动态添加资源路径到系统 Resources 对象, 直接反射调用getAsset.addAssetPath即可。5.0以下版本只是“伪动态”,需要自己重新实例化一个 Resources 对象和 AssetManager 对象,添加完所有需要的资源路径后,替换运行环境的 Resources 对象才可以做到“动态”。这个跟5.0以下的Resources.cpp在初始化完成之后,无法动态扩展resTable有关。
抛开上面的东西,从头开始讲述一下动态加载的原理:
Android应用程序的.java文件在编译期会通过javac命令编译成.class文件,最后再把所有的.class文件编译成.dex文件放在.apk包里面。那么动态加载就是在运行时把插件apk直接加载到classloader里面的技术。
看完上面的原理,不知道你有没有什么疑问,反正我是有的。
- 如何加载插件里面的.dex文件。
- apk里面的资源怎么办。
上面两个问题是动态加载框架最重要的两点,无法动态安装dex或资源文件的动态加载框架都是耍流氓。我们在实现这个框架的时候同样也遇到了这两个问题。
如何动态加载插件代码:
关于代码加载,系统提供了 DexClassLoader 来加载插件代码。开发者可以对每一个插件分配一个 DexClassLoader (这是目前最常见的一种方式),也可以动态得把插件加载到当前运行环境的classloader中。蘑菇街采用的是后者,这种方式可以有效的防止各种莫名其妙的 ClassCastException ,当你在crash后台看到各种 A cast A错误而欲哭无泪的时候,我想你会喜欢上这种方式。
事情当然不会这么简单,系统提供的DexClassloader对外api中,只有一种方式可以向类加载器指定加载路径。就是在构造函数中传入apk/zip/dex路径。这完全不符合我们“动态”的原则,难道每次加载一个插件,都必须重新实例化一个类加载器出来吗?这个时候我们想到了google提供的multidex插件,这个插件旨在帮助函数超过65536上限的应用在编译期切割class到多个dex文件中。经过观察发现,5.0以下的Android系统,在应用安装的时候只认classes.dex文件,并在安装期对这个dex文件进行opt操作,生成的odex文件放在/data/dalivk-cache里面。那么剩下classes(N).dex怎么办呢,答案就是如果在编译期使用multidex插件的话,开发者还需要让自己的Application继承 MultiDexApplication ,这样说起来,这个 MultiDexApplication 应该就有加载剩下的classes(N).dex的能力了。查看 MultiDexApplication 代码,果然找到了线索:
public class MultiDexApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
[/code
可以看到,它在attachBaseContext函数调用了support包中 MultiDex 类的install函数来安装classes(N).dex,于是都是应用层代码,它能动态安装那表示我们也可以。有了以上的分析,剩下要做的就只是去扒一扒install这个函数了。
如何动态加载插件资源:
我们在开发的时候,当有需要用到资源的地方,可以直接调用 Context 的getResources()函数返回 Resources 的来访问打包在apk中的资源文件。在研究如何动态添加资源到系统的 Resources 对象的时候,有必要先了解一下 Resources 本身是如何访问到资源的。
查看系统的 Resources 源码,我们发现这个类主要做了两件事,首当其冲的当然是访问资源,另外一件就是管理资源配置信息。对于资源的动态加载来说,我们关心的是它如何做第一件事的。 实际上, Resources 对资源的访问,全部代理给了另一个重要的对象 AssetManager 。那么问题转化成了, AssetManager 是如何做到对资源的访问的。 Resources 类在它的构造函数里对 AssetManager 做了一些重要的初始化:
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config, CompatibilityInfo compatInfo, IBinder token) { mAssets = assets; mMetrics.setToDefaults(); if (compatInfo != null) { mCompatibilityInfo = compatInfo; } mToken = new WeakReference<IBinder>(token); updateConfiguration(config, metrics); assets.ensureStringBlocks(); }
其中的重点就是调用了 AssetManager 对象的ensureStringBlocks()函数,这个函数的实现如下:
/*package*/ final void ensureStringBlocks() { if (mStringBlocks == null) { synchronized (this) { if (mStringBlocks == null) { makeStringBlocks(sSystem.mStringBlocks); } } } }
函数先判断mStringBlocks变量是否为空,如果不为空的话,表示需要被初始化,于是调用makeStringBlocks函数初始化mStringBlocks:
/*package*/ final void makeStringBlocks(StringBlock[] seed) { final int seedNum = (seed != null) ? seed.length : 0; final int num = getStringBlockCount(); mStringBlocks = new StringBlock[num]; if (localLOGV) Log.v(TAG, "Making string blocks for " + this + ": " + num); for (int i=0; i<num; i++) { if (i < seedNum) { mStringBlocks[i] = seed[i]; } else { mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true); } } }
这里的mStringBlocks对象是一个StringBlock数组,这个类被标记为@hide,表示应用层根本不需要关心它的存在。那么它是做什么用的呢,它就是 AssetManager 能够访问资源的奥秘所在, AssetManager 所有访问资源的函数,例如getResourceTextArray(),都最终通过StringBlock再代理到native进行访问的。看到这里,依然没有任何看到能够指示为什么开发者可以访问自己应用的资源,那么我们再看得前面一点,看看传入 Resources 的构造函数之前,asset参数是不是被“做过手脚”。函数调用辗转到ResourceManager的getTopLevelResources函数:
public Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) { ... AssetManager assets = new AssetManager(); if (resDir != null) { if (assets.addAssetPath(resDir) == 0) { return null; } } ... }
函数代码有点多,截取最重要的部分,那就是系统通过调用 AssetManager 的addAssetPath函数,将需要加载的资源路径加了进去。addAssetPath函数返回一个int类型,它指示了每个被添加的资源路径在native层一个数组中的位置,这个数组保存了系统资源路径(framework-res.apk),和应用自己添加的所有的资源路径。再回过头看makeStringBlocks函数,就豁然开朗了:
- makeStringBlocks函数的参数也是一个StringBlock数组,它表示系统资源,首先它调用getStringBlockCount函数得到当前应用所有要加载的资源路径数量。
- 然后进入循环,如果属于系统资源,就直接用传入参数seed中的对象来赋值。
- 如果是应用自己的资源,就实例化一个新的StringBlock对象来赋值。并在StringBlock的构造函数中调用getNativeStringBlock函数来获取一个native层的对象指针,这个指针被java层StringBlock对象用来调用native函数,最终达到访问资源的目的。
有兴趣的同学可以继续深入native层的源码,可以看到不管是addAssetPath函数还是makeStringBlocks函数,使用的都是native层同一个数组,这样,这两个函数就被关联了起来。
到这里,我们已经知道了如何动态添加资源路径的“秘密”。
解决了以上两个问题,一个基本满足要求的动态加载框架就被搭了起来。
ps:查看native层Resources.cpp的代码,我们发现,Android5.0及以上版本是真正的支持动态添加资源路径到系统 Resources 对象, 直接反射调用getAsset.addAssetPath即可。5.0以下版本只是“伪动态”,需要自己重新实例化一个 Resources 对象和 AssetManager 对象,添加完所有需要的资源路径后,替换运行环境的 Resources 对象才可以做到“动态”。这个跟5.0以下的Resources.cpp在初始化完成之后,无法动态扩展resTable有关。
发表评论
-
jni未释放资源问题。Failed adding to JNI local ref table (has 512 entries)
2016-02-01 14:51 983Native Code 本身的内存泄漏 JNI 编程首先是一 ... -
android ApkPlug使用
2015-12-09 15:14 734直接下载附件吧, 有两个是官方的demo包,还有一个是他们技术 ... -
dynamic-load-apk-Apk动态加载框架使用初体验
2015-12-03 10:40 799因为想要将本网站上的开源代码直接做成一个能显示效果的app,决 ... -
Android动态加载进阶 代理Activity模式
2015-11-30 17:20 885技术背景 简单模式中 ... -
Android NDK rb5 文档之本地活动和应用程序
2015-11-24 22:34 816Native Activities and Applicati ... -
Android NDK rb5 文档之 native_activity.h 文件翻译
2015-11-24 22:30 1010/** * Copyright (C) 2010 The A ... -
Android新技术学习——阿里巴巴免Root无侵入AOP框架Dexposed
2015-11-21 13:49 1793引用阿里巴巴无线事业部最近开源的Android平台下的无侵入运 ... -
Android NDK开发之JNI基础知识
2015-11-21 13:05 1128JAVA层与JNI层数据类型的对应 下面是一个测试方法 pu ... -
ANDROID2.2 JNI 配置luajit2
2015-11-21 11:18 744去http://luajit.org/官网下载最新的版本 在 ... -
在Android平台上加载本地库的危险性[转]
2015-11-13 09:30 1588在2012年KeepSafe的创业初期,我们试图找到一种为An ... -
JNI: 能否用 GetFieldID()/GetStaticFieldID()取得enum变量的属性?
2015-11-06 11:52 1846没有问题的,jni下面一样可以动态获取的 仅供参考! VOI ... -
ndk-stack定位不出崩溃代码行的问题
2015-10-30 08:51 1250NDK开发包中自带的NDK-STACK工具是可以查看崩溃栈信息 ... -
Android.mk文件详解
2015-10-27 09:23 1843Android.mk是Android提供的 ... -
NDK在增加断点时跳不进去,不管用的解决办法
2015-10-26 10:09 1106先看下面的错,如果报的不是这个那就不是我这个问题,那就不用再看 ... -
warning:deprecated conversion from string constant to 'char *' 解决方案
2015-09-25 09:01 1857char* createClass(){ ret ... -
jni内存释放
2015-09-24 12:03 3677调用GetStringUTFChars,GetDoubleAr ... -
如何不要让ndk-build自动删除.so
2015-08-04 15:33 1175在用ndk-build的时候突然发现在编译完成之后会自动删除a ... -
超简单的NDK单步调试方法
2015-08-03 21:19 603最近为了性能需求,开始搞JNI,白手起搞真心不容易。中间差点崩 ... -
JNI调用java中的类方法和静态方法
2015-08-03 16:46 2759在JNI调用中,肯定会涉及到本地方法操作Java类中数据和方法 ... -
[转]Android JNI层实现文件的read、write与seek操作
2015-08-03 14:41 12551、 在Andr ...
相关推荐
【网络虚拟化的基石——交换机堆叠与集群】 交换机的堆叠与集群技术是现代网络虚拟化的重要组成部分,能够显著提升网络设备的性能、扩展性和可靠性。在本文中,我们将详细探讨两种主要的堆叠技术:iStack(智能堆叠...
机器学习基石-林轩田-高清课件整理每页八个PPT页面。合并整理。
### 以应用为中心的智能化运维基石—CMDB #### 一、新IT管理需要强大的CMDB 在现代IT管理中,随着技术的发展和业务需求的变化,传统的IT服务管理(ITSM)模式面临着新的挑战。CMDB(Configuration Management ...
明基快速发展的基石--协同供应链管理.pptx
企业生产的基石----BOM 简介_CRM产品经理 需求规格说明书管理系统规格需求说明书模板.ppt
精品报告系列2019-华夏基石-中国医疗器械上市公司发展白皮书-2019.10-84页.pdf
机器学习基石-林轩田-高清课件整理每页两个PPT页面。合并整理。
AM01-侯震宇-人工智能云-AI时代基石-1 网络治理
华夏基石-中国创新药产业发展白皮书--147页.pdf
通信行业:IDC—数字中国发展的基石-20200328-东兴证券-18页.rar
网盘文件永久链接 00 -vSphere 7.0 ICM 01 vSphere 7.0 ICM-L2课程介绍-VCDX 00 云之基石系列之VMware虚拟化 00 云之基石-vSphere 7.0 ICM 00 云之基石系列之VMware虚拟化 01 vSphere 7.0 ICM-L1介绍
藏经阁-电子认证是构建网络安全诚信的基石-14.pdf
【DevOps的基石:重新定义测试自动化】 DevOps是一种旨在加强开发(Development)和运维(Operations)之间协作的软件开发方法论,它强调快速迭代、持续集成和持续交付,以提高软件开发的效率和质量。在构建DevOps...
【企业资产管理信息化】是针对资产密集型企业的一种信息化解决方案,旨在通过高效管理企业的固定资产,提高经济效益和市场...因此,设备主数据管理不仅是企业信息化建设的基石,也是企业资产管理和持续发展的关键要素。
[自己做个游戏服务器二] 游戏服务器的基石-Netty全解析,完整项目源码
构建和谐的海关人际关系是和谐海关建设的基石-文档
报告指出,机构资金的入场是推动股市长期上涨的重要基石。A股市场虽然与海外发达国家相比还存在一定差距,但随着中期外资流入的提速和长期居民资金通过公募基金等渠道进入权益市场,机构化将成为A股市场发展的重要...
稳固业务快速交付基石-一站式环境解决方案-QECon深圳站2021年全球软件质量&效能大会
《华夏基石-北京电力-人力资源管理业务流程梳理项目建议书》是华夏基石人力资源顾问有限公司为北京电力公司提供的一项专业咨询服务的提案。这份建议书详细阐述了华夏基石公司在人力资源管理领域的专业知识和实践经验...
Fastcms是基于SpringBoot前后端分离技术,且具有插件化架构的CMS系统,系统具有高扩展性,易维护性,可以快速搭建网站,微信小程序,是开发微信营销插件的基石。fastcms内置一套完整的CMS建站系统,完全融入微信生态...