一、分析一个Hello World App中bitmap对象的分布
使用BitmapAnalyzer分析一个Android Hello World App时你会dump出400+张图片,什么是BitmapAnalyzer?BitmapAnalyzer是一个自动分析Android dump heap中bitmap对象的工具,详细请看《Android Bitmap的内存大小是如何计算的?》这篇文章的介绍。
分析仔细看一下可以发现,上图中sPreloadedDrawables数组所引用的预加载Drawable就会占用10M以上的内存空间。这些都是Android系统的主题资源图片,为什么会有这么多主题资源被加载?不需要的无用的系统资源能否避免被加载?那么要如何做到?
二、为什么有这么多系统资源被加载?
使用Android Studio(简称AS)的monitor工具dump内存如上图所示,hprof中的内存信息分为三类,分别是App heap,Image heap,Zygote heap。App heap很好理解,就是应用进程独占的内存空间,在应用中创建的对象都会在App heap上。而在Dalvik虚拟机上运行的app,只有App heap和Zygote heap,并没有Image heap。Android官网文档的三者的解释是:
1 | App heap - The heap used by the current app. |
Zygote机制总的来说是app_process
启动Zygote并创建第一个虚拟机进程,Zygote启动时会预加载所有需要的Java classes和”必要的”资源文件,这些资源文件就包括我们在hprof文件中看到的被sPreloadedDrawables所直接引用的Bitmap资源。应用进程都是从Zygote中fork出来的,所以所有的应用进程都会包含上面提到的资源文件。这些资源的拷贝可以看成浅拷贝,并不是真正的内存copy,而是作为进程间的共享内存使用。详细的可以查看《Zygote》这篇文章。
三、强制清除未使用的系统资源图片会怎样?
1 | private void clearPreloadedDrawables() { |
当通过上述clearPreloadedDrawables函数清除sPreloadDrawable对这些资源的强引用,然后使用Android Studio的monitor工具发现,gc以后App heap发生了断崖式的内存回收,并且使用adb shell dumpsys meminfo
命令可以发现Heap Alloc和Heap Size都发生了显著的减小,但Pss total并没有显著变化。这里的理解是PSS = Private RAM + 按比例计算的Shared RAM,这两部分都没有发生显著变化,所以PSS Total没有变化。
那请问,上面👆的内存清理有意义吗?Heap allocated较少了10M,但PSS并没有明显的变化,给我的感觉就像大家在吃大锅饭,我只吃了1碗,别人吃了10碗,付钱的时候却是AA,甚至可能我付的还更多一些。你说你是不是萨?
四、Copy-On-Write(COW)
Zygote heap是系统共享内存heap,这里我尝试修改sPreloadDrawable
对应数据时,如下图所示,系统默认预加载399张资源图片,当手动插入一张新的图片到缓存后,缓存大小变成了400。但让我觉得诡异的是,不是说Android系统使用COW机制吗?为啥插入前后sPreloadDrawable相关的内存地址根本没有变化?
我的理解是在fork子进程的时候并不会真正拷贝父进程内存数据,而是子进程的虚拟内存空间指向父进程的物理内存空间。子进程和父进程的虚拟空间不同,但物理空间是同一个。当子进程需要重写共享内存数据时,系统才会为子进程分配响应的物理内存。这里的虚拟内存空间对应用程序来说就是逻辑地址,对于CPU来说就是线性地址。
五、总结
回到之前思考的几个问题:
- 为什么会有这么多主题资源被加载?
这些主题资源是系统启动时Zygote预加载的系统资源,Zygote认为这些资源每个App都会用到,特别是5.0+的系统会达到400+的默认theme图片,占用10M+的内存空间,不过这些内存是系统共享的你用或不用他们都在那里。改变应用的主题样式并不会改变sPreloadDrawable的加载内容。 - 不需要的无用资源能否避免被加载?
这些默认系统资源无法避免被系统预加载,但是不同ROM厂商针对这些资源的看法差别还是很大的,根据测试的情况看,三星的预加载资源比源码的还要多,清空sPreloadDrawable也不能减少Heap allocte的大小。相反锤子手机对这些预加载资源的作用倒是不太看好,在锤子坚果上看到的预加载资源就很少,几乎可以忽略不计。而在Android 5.0以下的系统版本,这块预加载的资源也不是很夸张。 - 清空预加载资源对应用内存有无优化作用?
说下个人看法,如果你的应用分为UI进程和后台服务进程。在UI进程清除系统预加载资源可能并不是明智的选择,为啥?因为这是一件吃力未必讨好的事情,虽然这10M空间你并没有使用,但系统并没有领情,反而当你想要在使用系统资源的时候,那这些资源就会完全算到你的头上。那样反而会造成应用进程内存的上升。为啥?清除系统的共享内存后,再加载系统资源时分配的就是应用的私有内存了。
如果是后台服务进程,打死也不会用到系统主题资源的情况下,清除预加载资源为什么不是对自己有利的事情呢?因为这样做确实让Heap变小了呀!
当然以上都是个人的理解,如有理解错误,恳请能够指出。对于如何获取Java对象的物理地址,我还没有找到更好的办法,所以暂时还没有实践验证COW。
临帖顿首,不知所言,2017.4.26日夜。
参考
作者:乐蛙科技高级研发经理 易宁
Android应用普遍没有iOS应用流畅,这与Android应用是运行在Java虚拟机上有很大的关系,而关系最大的恐怕就是Java虚拟机上的Garbage Collection(GC)了。Java语言提供了自动内存管理,垃圾对象会在GC过程中自动被释放。这提供了开发效率,使开发者能集中精力在业务逻辑上;但天下没有免费的午餐,你写的Android应用的某些性能上的问题很可能就是GC导致的。
下面是一段非常简单的Java语句:
1 | byte[] data = new byte[512 * 512 * 4]; |
这条语句申请了一块大小相等于512*512大小图片的内存。执行这条语句会消耗多少时间呢?0+ms吗?基本没有性能影响吗?
原文地址:Zygote https://anatomyofandroid.com/2013/10/15/zygote/
Zygote(受精卵)是一个启动应用的守护进程,在Server Manager之后被init.rc触发,但其实他是真正被app_process启动的,下面是启动这个特殊进程的流程。
1 | service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server |
你可以发现Zygote是如何当做系统服务启动的,你也可以看到app_process为什么说app_process是启动Zygote的真正命令。就像之前说的,Zygote是一个以专门启动应用为的守护进程。这意味着Zygote是所有App进程的父进程。当app_process启动Zygote时,它通过调用Zygote的main()方法创建了第一个Dalvik虚拟机。当Zygote启动后,zygote会预加载所有必须的Java类和资源,启动System Server并打开一个名为/dev/socket/zygote的socket服务,去监听所有启动应用的请求。 想之前的文章所说,System Server 是一个和它的父进程完全分离的进程。一旦System Server创建完成,它会初始化一系列不同的System Services并启动Activity Manager。那Zygote是如何启动新的应用的呢?
一般情况下如果我想要较详细的分析内存状况,会使用android monitor(下简称monitor)和Memory Analyzer工具(下简称MAT),但步骤有些麻烦。
- 使用monitor dump堆文件
- 使用hprof-conv工具,将hprof转换成MAT能够识别的格式
- 使用MAT分析
需要手动dump hprof再使用hprof-conv工具手动转码,上篇Android Bitmap的内存大小是如何计算的?说到使用square的haha来做内存堆中Bitmap的分析。所以在思考如何一键完成内存dump和analyze的过程,当然也可以任意其他Java对象做分析。
如何完成dump和转码,思路还是Read the fucking source code,看下monitor是如何dump hprof文件的。打开Android sdk目录,monitor和其依赖的jar分别在/tools
和’/tools/lib’目录,核心代码在ddmlib.jar
中,Java swring代码在ddms.jar
和ddmuilib.jar
中,ddmlib需要依赖common.jar
和guava.jar
。
一、前言
本来只想说下Bitmap和内存的基本关系,但发现如果真的想把这看似简单的事情说清楚,实际上未必那么简单,你不信?不妨先尝试下回答下面几个问题!
- 问1:什么是dpi?什么是dp?答:你在侮辱我?我拒绝回答!(:无辜脸
- 问2:以Nexus6为例,分辨率1440*2560,5.96英寸,ppi是什么?dpi是什么?1dp是多少像素?
- 问3:还以Nexus6为例,一张180*180的图片,放置在设置了
wrap_content
属性的ImageView中,当这张图片,分别放在drawable-nodpi
,drawable-mdpi
,drawable-hdpi
,drawable-xxhdpi
,drawable-xxxhdpi
这几个资源目录中,在屏幕上分别显示多大(像素)的图片? - 问4:这张图片占用的内存大小分别为多少?
- 问5:如果设置ImageView的宽和高为固定的值,如50px,那么上述情况下,加载的Bitmap占用内存大小分别为多大?
本文主要围绕上述问题,介绍Android图片资源加载的基本机制,图片的内存大小如何计算,以及内存图片分析工具。
最近在看某外卖平台的代码,发现某外卖平台最新版本版本无法正常的通过dex2jar工具将dex转换出Java源代码,在转换过程中会提示出错,如图:
一、工具介绍
- apktool:google提供Android apk编译与反编译工具,https://ibotpeaches.github.io/Apktool/
- dex2jar:dex文件转jar文件工具,https://github.com/pxb1988/dex2jar
- jd-gui:查看jar文件,http://jd.benow.ca/
- keytool:制作和管理数字证书
- jarsigner:使用数字证书对jar和apk进行签名
- Android killer:Window系统中,上述工具的集成工具
更多反编译工具可查看:Uncle Chen—Android反编译技术总结
二、去除应用开发助手的广告
CodeKK公众号在17年初推出了一个应用开发助手。V1.0版本是有广告版本的,下面以这个版本为例,简单看下去广告过程。它的后续的V1.1.0和V1.2.0已经去除了广告功能。
2.1、反编译
1 | apktool.sh d -f ./dev-tools.apk |
2.2、注释广告代码
这里可以先用dex2jar转jar后查看下源码,会发现使用的是Google AdMob作为广告平台,简单看下代码可以发现在主页面activty_main.xml中包含广告视图的布局文件。所以只需要找到ad_layout.xml
将其 android:visibility="gone"
属性设置成gone
就可以了。
2.3、重打包
这里重打包的过程可能会遇到一些错误,不过认真查看日志并结合Google都是可以找到解决方案的,这里暂且不表。使用apktool b命令,生成的apk默认路径为/dist
路径下
1 | apktool.sh b ./dev-tools |
2.4、制作一个名为hackapk.keystore的证书,并重新签名应用
1 | 制作证书: |
详细的参数可以参考:
APK签名之keytool生成keystore和jarsigner签名apk
2.5、安装
1 | adb install -r dev-tools-no-ads.apk |
2.6、下载去广告版本的应用开发助手
三、反编译脚本
1 | #!/bin/sh |
将上述内容保存到本地hack.sh
文件中,使用下面的命令就会把apktool和dex2jar的结果输出到meipu
这个目录中,这样就不需要每次收到敲命令喽,只要知道需要反编译的apk和输出的文件夹名称就可以~
./hack.sh ./美铺_1487845975713.apk meipu