收集Android实际开发中的bug总结与解决方法(第三节)

来源:互联网 发布:linux打tar包 编辑:程序博客网 时间:2024/05/22 11:48
解决bug中的总结:Bitmap 内存优化相关

 XXXXX项目中相关的bug有2个:
1) 在生成圆角图片的RoundImageView的onDraw()方法中 :bug: bitmap size exceeds VM budget .
2) 在SSQSplashActivity的onCreate()方法中加载欢迎界面的图片时 bug: OutOfMemoryError.
 
Bitmap 内存优化:
 
1)   要及时回收Bitmap的内存
Bitmap类的构造方法都是私有的,所以不能直接new出一个Bitmap对象,只能通过BitmapFactory类的各种静态方法来实例化一个Bitmap。
查看BitmapFactory的源代码可以看到,生成Bitmap对象最终都是通过JNI调用方式实现的。所以,加载 Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。
这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用 recycle()方法来释放C部分的内存。
从Bitmap类的源代码也可以看到,recycle()方法里也的确是调用了JNI方法了的。

对于第一个bug:
RoundImageView控件中采用的生成圆角图片的方法是setXfermode(Mode.SRC_IN)+canvas来实现的,其中在onDraw()方法中共在创建了3个bitmap对象,其中一个是bitmap = b.copy(Bitmap.Config.ARGB_8888, true)的拷贝其实现实是调用的JNI的方法在C底层实现,但是在最后仅仅是将bitmap赋值为null了,如bmp = null;这样的话,可能存在Android系统对java层的bitmap做了回收,而没有用 recycle()方法调用JNI的来彻底回收C部分的内存。

解决办法生成圆角图片有一个更好的实现方法是:BitmapShader(Bitmap bitmap,Shader.TileMode tileX,Shader.TileMode tileY)。调用这个方法来产生一个画有一个位图的渲染器(Shader)。该方法实现简单高效,节约内存开销。查看球神的源码中,就是用的这种方法。于是我们将球神中的CircleImageView控件,替换了之前的RoundImageView控件。

2)捕获异常
因为Bitmap是内存消耗大户,为了避免应用在分配Bitmap内存的时候出现OutOfMemory异常以后Crash掉,需要特别注意实例化Bitmap部分的代码。
通常,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获。

3) 对ImageView等图片的资源的操作

尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,
因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。

然而,可以改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,
decodeStream最大的不同之处在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。
但是,decodeStream有这么一个缺点:
decodeStream直接拿的图片来读取字节码了, 不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源, 
否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。
 
Bitmap,占用内存的算法如下:
图片的width*height*Config。如果Config设置为ARGB_8888,那么上面的Config就是4。一张480*320的图片占用的内存就是480*320*4 byte。

对于第二个bug:
在欢迎界面加载的图片是720*1280,位深度是24,大小189kb. 在Android中图片占用的内存就是720*1280*4byte. 所以还是比较大的。建议在保证高清线度的前提下,尽量减小的图片资源。
 
4) 压缩图片

使用BitmapFactory.Options设置inSampleSize就可以缩小图片。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一。如果知道图片的像素过大,就可以对其进行缩小。

那么如何才知道图片过大呢?

方法是:使用BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,但是可计算出原始图片的宽度和高度,即options.outWidth和 options.outHeight。通过这两个值,就可以知道图片是否过大了。在实际项目中,先获取图片真实的宽度和高度,然后判断是否需要跑缩小。如果不需要缩小,设置inSampleSize的值为 1。如果需要缩小,则动态计算并设置inSampleSize的值,对图片进行缩小。需要注意的是,在下次使用BitmapFactory的 decodeFile()等方法实例化Bitmap对象前,别忘记将opts.inJustDecodeBound设置回false。否则获取的 bitmap对象还是null。


BUG 、使用actionProvider时出现的问题

bug复现:

解决方案:

1
2
//import android.support.v4.view.ActionProvider;
import android.view.ActionProvider;

换一种import的方式即可。tmd,这就是一个坑。  

 

BUG : 背景墙设置失效

采用XUTILS的图片缓存技术做了个小米电视的app,加了一个配置图片仓库和图片数量的对话框。如果配置完,程序重启什么都ok,但是一旦关机就恢复初始状态,原因是自己

在写程序的时候大意了。

复制代码
复制代码
 1    String tmpBucketName = LocalDataDeal.readBucketNameFromLocalData(); 2   String tmpBucketNum = LocalDataDeal.readBucketNumFromLocalData(); 3    String tmpBucketWaterMark = LocalDataDeal.readBucketWaterMarkFromLocalData(); 4    
5 if(tmpBucketName != null && tmpBucketName != "" && tmpBucketNum != "" && tmpBucketNum != null && tmpBucketWaterMark != null && tmpBuckeWaterMark != "" ) 6 { 7 if(Integer.parseInt(tmpBucketNum) > 1) 8 { 9 QiNiuBucketName = tmpBucketName;10 QiNiuBucketNumber = Integer.parseInt(tmpBucketNum);11 QiNiuBucketWaterMark = tmpBucketWaterMark;12 }13 QiNiuBucketName = LocalDataDeal.readBucketNameFromLocalData();14 }
复制代码
复制代码

问题出在了对第五行对waterMark的处理,因为允许设置是否显示水印,而水印不存在的时候就是tmpBuckerWaterMark为null的时候,所以对于没有设置水印的仓库配置,是永远不会显示的。

还有一点,就是在对字符串比较的时候,除了和null对比可以直接用==符号,其余比较都得用equal方法进行对比。



原创粉丝点击