准备工作
3.图片占内存公式:分辨率 * 每个像素大小,严谨吗?
https://mp.weixin.qq.com/s/ufOjtKURP8QERWw1pn_m1Q
------------ 疑问----------------
实际使用AT抓取结果显示
测试机屏幕分辨率420dpi/xxh-dpi/2.625x
同一张1500*1004尺寸的png图,放在xxh-dpi文件夹下
8bit-color和24bit-color申请的内存均为4.62Mb左右。
也就说,位图深度是不影响的。
按照2里的公式
缩放尺寸 = 图片尺寸 / 当前图片文件夹代表的dpi * 目标机器屏幕的dpi + 0.5f 结果取整
scaledWidth = int( 1500 / 480 * 420 + 0.5f) = int(1313) = 1313
scaledHeight = int( 1004 / 480* 420 + 0.5f) = int(879) = 879
分配的内存理论值应当是
1313 * 879 * 4bytes /1024/1024= 4.403Mb
和实验值4.62Mb还是有一定误差的
误差问题暂且搁置。
---------------------分割线----------------------
正文
其实这个标题有点夸大,处理图片有很多的方式啦,
准确里说,总结的是:放一张图做background的时候,如何让内存分配最合理。
1.先放图
在之前的总结
AllocationTracker实践
以设置图片背景为例做了实践,
当时我们使用的方式就是
最偷懒最省事的 在某个layout.xml里background:@drawable
该方式最终调用
BitmapFactory.decodeStream()
这个时候,我们的dalvik虚拟机就会为此分配一块内存
newNonMovableArray,
这些都是AT给我们的信息。
乍一看,这都没有问题。
我准备了一张分辨率为7874*5315 的jpeg图,放在h-dpi里
/**测试机是6G的RAM,可用4G,largeHeap=false,所以这张大分辨率图放xh,xxh都能hold住。
理论值 int(7874 / 240 * 420+0.5) *(5315 / 240 * 420+0.5)=13780 * 9302 * 4 byte = 512891600 byte
实际信息显示申请分配 512671132 byte
*/
2.找调用
回归我们的重点,在xml中直接设置背景属性的做法应该是很普遍,但是这样面对不同配置的机器时,内存分配不合理,而且极易造成OOM,这是非常严重的问题。
通过AT和源码可知,xml配置background实质是在view init方法的中调用setBackground(Drawable)方法 or setBackgroundResource(Drawable)
他们最终都是调用到setBackgroundDrawable
依次调用setBackground方法
通过我自己的代码实践,无论background是放图片drawable还是xml drawable,还是在drawable.xml里做很别扭的操作
AT显示getDrawable占比非常大,依次跟踪追踪到ResourcesImpl::loadDrawable方法。
3.猜测
1.加载图片最终都会调用BitmapFactory.decodeStream,直接走jni解析该图,分配的内存主要在工厂类初始化这里。
2.普通的方法setBackground,在除了decodeStream操作外,还会申请一部分内存块缓存这个Drawable,之后要用这个图,就直接从缓存里拿。
而这个就是内存爆炸的原因。
4.解决方案
知道原因后,我们的解决方法就是,直接调用工厂类解析目标图片供使用。
绕过分配内存缓存Drawble,从而减少OOM的风险,
在onDestroy的时候setBackground(null)显式解除引用,告诉gc可以回收。
参考:
以最省内存的方式读取本地资源的图片,存在bug:因机器分辨率不同而无法自动适配
* http://blog.csdn.net/woshicaixianfeng/article/details/6825295
* http://bbs.csdn.net/topics/390919272
图片的质量压缩和尺寸压缩
* http://blog.csdn.net/jdsjlzx/article/details/44228935
用decodeFileDescriptor替代decodeStream或者decodeFile
* http://www.th7.cn/Program/java/201606/888679.shtml
如何回收这些资源
* http://blog.csdn.net/springsky_/article/details/25212419
Drawable和Bitmap在内存中谁更省内存
* http://blog.csdn.net/zhu071011/article/details/48310597
其他的加载图片方案
1.Fresco
超大图和网络图推荐。小图不推荐,imagePepline比小图内存还多。
Fresco的内存管理
http://blog.csdn.net/sgwhp/article/details/49640611
http://www.cnblogs.com/wytiger/p/5690039.html
2.Glide
待续。。。
其余回收方案
回收TextView的Drawable资源
常常会出现一个textView上下左右某个方向接一张ImageView图
官方建议精简布局,推荐在textView的xml配置drawableLeft (someelse)
为了追求极致的资源回收,我们也应当在onDestroy对其进行回收
查看源码可知该attrribute
实际调用的是
往下跟
也可以运用省内存方案加载图片。
解决方法
textView.setCompoundDrawablesWithIntrinsicBounds(null,null,null,null);
或者
textView.setCompoundDrawables(null,null,null,null);
理由见源码说明
回收theme里的Background:Drawble资源
做过启动加速的朋友知道,在调起app时,会出现初始化加载的空白期,体现出来就是一块白屏。这个用户体验很不好。所以我们会在启动Activitiy的theme里
设置background,给用户一点击就打开的感觉。
我做了一个对照
avtivity的style放一张图,这个activity里的textView控件我也放一个style
AT抓取分配情况(点击查看大图)
对activity的就是对ViewGroup里所有的view遍历set
下方textView的更简单,
以上的分配套路和之前的分析一样。
那么问题来了,启动占位图,我们根本不想开辟一部分内存缓存这张图,
那如何省内存的去加载图片呢?
好的,为了更好的测试效果,我弄了一个OOM
题外话:
catch 住 OOM,行吗?
https://mp.weixin.qq.com/s/83jlnS0-payhZoykr21Ohg
目前:
因为对activity启动了解不够,没懂整个view的绘制过程,暂未找到用省内存方案加载带图片的style/theme。
所以建议不加载带图片的style/theme,启动占位图也要及时回收。
回收方法,在super.onCreate之前,进行
getWindow.setBackgroundDrawble(null);
亲测生效的回收方案:
1.因为app启动的关系,在splashActivity的oncreate方法里去处理backgroud,不生效。即使是在super.onCreate()方法前调用getWindow.setBackgroundDrawble(null);
2.在super.onCreate()前调用setTheme(R.style.splash_no_bg)
不要直接setTheme(0),这样不生效,原因查看源码就知道了。
自定义的style_splash_no_bg的background属性用@null
再使用getWindow.setBackgroundDrawble(your drawble);
onDestroy里解除引用getWindow.setBackgroundDrawble(null);
踩坑记录:
因为使用了某第三方sdk,该sdk的初始化引用了splashActivity的context,
导致context一直被sdk的静态量一直引用,造成了内存泄漏10Mb--也就是这张bg_splash.jpg
简单来说就是
sdk引用context,context使用了theme,theme里有这张splash.jpg
最优解就是sdk解除context的引用,但是因为sdk的bug,目前无法做到。
那么我只能尽可能的减少泄漏值。
通过setTheme(R.style.splash_no_bg)方法解除大图片的引用。
可是因为业务的需要,splashActivity需要延迟1s才能到mainActivity(别问为什么,就是业务需求)
这就造成了1s的短暂白屏。
解决方案:
在onCreate()方法里依次调用
setTheme(R.style.splash_no_bg);
super.onCreate();
if(getWindow!=null)getWindow.setBackgroundResource(R.drawble.bg_splash);
在onDestroy()方法里
if(getWindow()!=null)getWindow.setBackgroudDrawable(null);解除引用