🖥️
Log4think
  • Introduction
  • Archive
  • 2016-12-13 JavaScript 中几种不同的基于 prototype 继承方式的区别
  • 2014-09-02 Facebook的Dalvik运行期补丁
  • 2014-08-19 理解AnguarJS中的模板编译
  • 2014-08-13 在Android中使用OSGi框架(Knopflerfish)
  • 2014-08-13 在Android中使用OSGi框架(Apache Felix)
  • 2014-07-30 3rd-party apt-key list for Ubuntu
  • 2014-07-21 'Failed to clone a large git repository: The remote end hung up unexpectedly'
  • 2014-07-02 genymotion Qt error in Ubuntu
  • 2014-04-30 可自动安装依赖的Ubuntu离线包安装工具 gdebi
  • 2014-04-23 解决搜狗输入法Ubuntu 14.04下黑块状态条
  • 2014-04-08 Setup Ghost blog system on Ubuntu
  • 2014-03-28 Trace a process
  • 2014-03-25 Forecast::IO 599 Internal Exception
  • 2014-03-23 Mac Tips
  • 2014-01-17 LD_LIBRARY_PATH shouldn't contain the current directory
  • 2014-01-10 Python on vim
  • 2013-11-12 ibus-pinyin doesn't work in KUbuntu 13.10
  • 2013-11-11 phpMyAdmin login error to remote server
  • 2013-11-04 Get SNMP(v3) working on Ubuntu 12.04
  • 2013-10-30 Accessing Facebook by LWP
  • 2013-10-17 Log4perl多个Appender重复输出日志的问题解决办法
  • 2013-04-19 How to enable ORMLite internal log on Android
  • 2013-02-20 Bash Shortcuts
  • 2013-02-20 '"adb shell dumpsys" parameter list'
  • 2013-01-27 循环有序数组的二分查找
  • 2013-01-23 为Java运行环境导入根证书解决Eclipse的TFS插件的"PKIX path building failed"错误
  • 2013-01-21 ant 中通过重新定义 project.all.jars.path 在 classpath 中引入外部 jar 文件
  • 2012-09-22 Android的滚动条实现细节
  • 2012-02-05 hostname自动变成bogon的问题
  • 2011-07-09 代码段速记 gist.github.com
  • 2011-07-08 A perl Data.Dumper clone for Python
  • 2011-06-22 魔兽世界私服Trinity,从源码开始
  • 2011-06-13 魔兽世界3.3.5 13930登录数据包分析
  • 2011-06-13 魔兽世界 3.3.5 13930 Trinity 认证补丁
  • 2011-05-20 统一业务模型(UBM) in ERP5
  • 2011-03-08 制作ASCII字符动画
  • 2011-01-14 Ubuntu升级导致的udevd错误修复
  • 2011-01-09 行列有序矩阵求第K个数
  • 2011-01-09 字节按位逆序
  • 2011-01-01 编程之美 1.2 中国相帅问题的一个简洁解法
  • 2010-11-26 为Windows 7/Windows Server 2008添加IPX协议
  • 2010-11-12 How to debug with Android Logging
  • 2010-09-16 利用google-code-prettify做网页内源码的语法高亮
  • 2010-09-05 10 Ways We Hurt Our Romantic Relationships
  • 2010-08-30 利用ipkall+xlite+iptel.org开通google voice
  • 2010-08-27 避免Android开发中的ANR
  • 2010-08-27 在Eclipse中查看Android SDK的源代码
  • 2010-08-18 Git中判断一个commit是否在某个branch中
  • 2010-08-04 修正auto-excerpt产生带格式的摘要
  • 2010-03-31 Go 编程语言入门教程
  • 2010-03-13 利用外部VPS主机通过ssh隧道穿透防火墙连接内网
  • 2009-12-30 旋转矩阵
  • 2009-02-10 为什么cpio要比tar好
  • 2008-12-11 ThoughtWorks 的一道笔试题
  • 2008-11-18 长距离打车如何省钱?
  • 2007-02-08 ftp后台自动上传下载
  • 2006-06-12 vi cheatsheet
  • 2006-04-19 修正mysqlcc在MySQL 5.0上常报的 Table 'xxx' doesn't exist 错误
  • 2006-04-01 'Perl中不寻常的 ?: 运算符'
  • 2005-09-13 关于IoC、低耦合、配置文件及其本质意义的思考
  • 2005-09-13 Perl与数据库DBI快速入门
  • 2005-09-12 Perl无废话入门指南
  • 2005-07-16 Solaris 下安装Perl的DBD-mysql模块失败的原因以及解决办法
  • 2004-10-15 SharpDevelop的AddInTree View 插件
  • 2004-10-14 SharpDevelop源码分析 (完整版)
由 GitBook 提供支持
在本页

这有帮助吗?

2014-09-02 Facebook的Dalvik运行期补丁

上一页2016-12-13 JavaScript 中几种不同的基于 prototype 继承方式的区别下一页2014-08-19 理解AnguarJS中的模板编译

最后更新于5年前

这有帮助吗?

对于一个功能丰富的Android应用来说,要面对的挑战很多,其中很多问题绝大多数开发者都可能意识不到。Android应用的方法数量限制可能就是一个。

在一些传统的企业应用中,当处理跟业务数据相关的时候,通常不会将数据变量设置为公开访问 public,而是会用getter/setter这样的存取方法来封装访问数据模型中的私有变量。在一般的编程实践中,这是一个被推荐和鼓励的做法。但是其副作用是类文件中会产生大量的方法(每一个私有变量都对应着2个方法)。

这个副作用带来的问题是,当应用安装在一些老机型上的时候,可能导致Android的一个Bug。原因在于,在应用的安装过程中,会运行一个程序(dexopt)去根据当前机型为应用做一些特定的准备和优化。dexopt中使用了一个固定大小的缓冲区(名为LinearAlloc)来存储应用中方法的信息。最新的几个Android版本中,该缓冲的大小是8MB或16MB。但是Froyo(2.2)和Gingerbread(2.3)只有5MB。因为老版Android的这个限制,当方法数量超过这个缓冲大小的时候,会导致应用崩溃。

解决办法之一是可以利用提到的技术,将dex分成多个dex文件分别加载,首先加载核心dex,其他的模块和扩展功能放在其他的dex文件中。

但是某些时候,如果第二个dex包中的类需要被Android Framework直接访问到的话,上述方案就是不可行的。此时必须要将第二个dex文件注入到Android的系统class loader中。这个一般情况下是无法做到的,但好在Android是开源的系统,可以利用Java的反射去修改Android内部的一些数据结构来解决这个问题。

这个方案在实际运行中,你会发现其实LinearAlloc不仅仅只是在dexopt中存在,而是在每个运行中的Android程序中都有它的身影。dexopt使用LinearAlloc储存dex文件的方法信息的时候,运行期的Android应用使用它访问实际用到的方法。如果在运行期将所有的dex文件都加载到一个进程中的话,实际的方法数量还是会超过限制。应用虽然可以启动但是很快就会崩溃。

看起来似乎要么只能精简应用的功能,要么就只能放弃支持Android部分版本而只支持最新的ICS以上的版本。

再次回头去看Android的源码,找到LinearAlloc缓冲的定义,也许你能意识到:其实只要能将缓冲从5MB增加到8MB就可以安全运转了。

也许,可以使用JNI把当前的缓冲替换成一个更大的缓冲区。这个方案看起来太疯狂了。修改Java的Class Loader的内部是一回事儿,修改运行中的Davlik虚拟机的内部可是另外一回事儿--运行中的代码可能会很危险。但是仔细查看过代码、分析所有使用LinearAlloc的地方后发现,只要在应用启动的时候去做,应该没什么危险。所需要做的就是,找到LinearAlloc对象,锁定,然后替换缓冲。

事实上,只要找到它,后面的事情就很顺利了。在这里,DvmGlobals对象中保存了LinearAlloc缓冲区。大概距离对象的开始地址700个字节的位置。从对象的开头开始扫描到这里风险很大,但是幸运的是,可以用距离一步之遥的vmList对象作为起点。这里包含了一个值,可以通过JNI和JavaVM的指针做比较。

最终方案是,找到vmList的值,扫描DvmGlobals对象找到匹配的位置,往后跳几个字节找到LinearAlloc的头,然后替换缓冲区。编写JNI扩展,嵌入到应用中,启动,然后应用应该可以正常运行在Gingerbread上了。

但是又有一个问题是,这个方案在Sumsung Galaxy S II上会失败,这可是最流行的运行Gingerbread的手机。

似乎三星对Android做了一些改动,导致这个方案的失败。其它的厂商可能会做同样的事情。所以这个方案的代码应该得更加健壮才行。

经过观察,在GS II上,LinearAlloc的缓冲区地址距离实际要找的地址只有4个字节。于是调整代码,如果在期望的地址没有找到LinearAlloc,那么就在附近几个字节的范围内查找。做这件事需要获取当前线程的内存映射表,确保没有在附近的查找过程中访问到无效地址(否则会立刻导致应用崩溃)。

(以上就是Facebook的故事,

https://code.google.com/p/android/issues/detail?id=22586
https://android-developers.blogspot.hk/2011/07/custom-class-loading-in-dalvik.html
https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/LinearAlloc.h#L33
https://github.com/android/platform_dalvik/blob/android-2.3.7_r1/vm/Globals.h#L519
Via