Android 从一次apk迁移窥看Android JellyBean(4.1)的变化

来源:互联网 发布:麒麟臂数据 编辑:程序博客网 时间:2024/06/07 02:05

平台的版本的变化会引入新的特性,和对现有API的优化。

对于Android 4.1 (JellyBean)的新特性请参考官方文档android-4.1,由于本文的重点不是对4.1新特性进行介绍,所以暂时省略这部分内容。

众所周知对于已有项目的维护,有一项必不可少的工作就是对app进行平台版本的迁移工作,对于身处移动平台的Android更是如此。这期间会碰见许多"莫名其妙"的奇怪问题,下文会结合一次实际的平台迁移工作小结下一部分4.1下API的变化以及可能引起的问题。


案例1. 莫名其妙的 "java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics......"

相信各位看官对这个exception一定不会陌生,正如exception 本身所描述的那样,当我们试图在Canvas上Draw一个回收过的bitmap时就会抛出这样的异常。

同样的apk在4.1版本以下运行都是没有问题的,但一旦运行到4.1版本的操作系统下就会抛出这个异常,这应该是一个由版本迁移引起的API变化的问题,那么到底是哪个API引起的问题呢?怎样定位到这个有问题的API呢?

bitmap.recycle()!既然是回收bitmap引起的问题,那就从所有调用bitmap.recycle()方法的地方入手,根据重现步骤反相定位有问题的代码。

按照这样的解决思路我很快的揪出了这个引起问题的API

Bitmap result = Bitmap.createScaledBitmap(bitmap, dstWidth, dstHeight, true);

首先说说这个API在JellyBean下发生了什么样的变化?

这个Bitmap的静态方法createScaledBitmap(......)是用来根据一个源bitmap,和缩放的大小来创建一个缩放的bitmap。

那么请考虑这样的一种特殊情况,当作为参数传进来的源bitmap的宽度高度与同样是参数的缩放的宽度高度一致的时候(换句话说就是没有对源bitmap进行缩放的时候),这个API会返回什么样的结果呢? 

 Source bitmap hash codeReturned bitmap hash codeJellyBean42041a0042041a00ICS41cd888841dfa4c8Gingerbread41d3775041c2a3d1


各位看官看出有什么问题么?

对。那就是在JellyBean之前的版本返回的这个实际上没有缩放的bitmap与源bitmap实际上并不是同一个对象实例,但JellyBean对于这样的特殊用例给出了优化方案,那就是将这个源bitmap实例返回,减少创建不必要的内存。

那么为什么会引起draw 回收bitmap的问题呢?

请各位再次看看这个出问题的API,实际上当调用这个API的时候是想获得一个新的bitmap对象,这个新的bitmap对象可能是以缓存的形式存在,在某个时点会为了对bitmap资源进行管理,会调用bitmap。recycle() 方法进行释放。

注意:而实际上并不希望回收源bitmap,如果回收了这个源bitmap很有可能就会造成上面提到的那个Exception。

说到现在,那么这个问题就很好解释了,在JellyBean下如果调用方法,并将这个返回的bitmap缓存起来,在某个时点调用bitmap.recycle()方法进行手动回收,回收这个缓存的同时也会将源bitmap回收掉了,悲剧就发生了。 

怎样解决?

两种解决方案;

1. 保持原先代码逻辑不变,只是对变化了实现的API进行一层方法封装

public Bitmap createScaledBitmap(Bitmap sourceBitmap,  int scaledWidth, int scaledHeight, boolean filter){    Bitmap scaledBitmap = null;    if (null != sourceBitmap){        if(sourceBitmap.getWidth() == scaledWidth && sourceBitmap.getHeight() == scaledHeight){            scaledBitmap = Bitmap.createBitmap(sourceBitmap);        }else{            scaledBitmap = Bitmap.createScaledBitmap(sourceBitmap, scaledWidth, scaledHeight, filter);        }    }    return scaledBitmap;}

2. 设计好bitmap资源缓存回收机制,依赖GC和缓存回收机制回收资源;不再调用bitmap。recycle()方法回收bitmap。 


案例2. ListView 中的RadioButton 点击后选中状态下UI没有更新


如同上个案例,这个问题同样也只会在JellyBean 下发生,在排除了UI展示所需的数据错误后,这个问题最终定位在了UI层中ListView中的UI未被更新。

为什么会在JellyBean中出现这个问题呢?

有两方面的原因:

首先,ListView中在处理点击事件的部分,framework层的实现有了变化,在JellyBean之前ListView在触发了Click事件后会调用Listview的requestLayout()方法,从而可能会导致ListView item的UI更新,而JellyBean出于优化的考虑,不再主动调用requestLayout()方法更新UI,也就是是说需要开发者自己负责去在数据变化的时候更新UI。

其次,基于第一个原因,当用户点击ListView中的item导致数据变化的时候没有调用adapter的notifyDatasetChanged()方法通知UI改变,这个原因是自身代码的问题,今后要引以为戒。


案例3. One table, many user


多可用户可以共享一个平板,这就像Windows下面的多个用户一样,不同的用户可以有不同的桌面,不同的widgets,不同的界面设置等信息,不同用户之间的信息是分隔开的,也就是说,这个用户的配置不会影响到其他用户。

每一个用户会共享应用程序,A用户装了一个程序,B用户也要可以装,但实现在平板上面,只会有一个程序,所有用户都共享这个程序。

对于开发者来说,一般我们不需要针对多用户做特别的操作,这里要说一点,特别重要:

在4.2上面,应用程序的数据存储路径再也不是/data/data/package-name了,而是/data/user/user-id/package-name。

因此,我们程序中如果需要存储一些配置文件到/data目录,一定不能直接是写死的字符串,而是始终都应该从程序中去动态获取,获取方法如下:

String dataDir = context.getApplicationInfo.dataDir

记住:

1,不要把存储路径写死。

2,不要把存储路径写成绝对路径。

3,动态根据程序来取存储路径,这样可以应对不同的系统版本。


总结

1,在Jelly Bean下,Bitmap.createScaledBitmap并不会总是返回一个新的Bitmap,如果指定的大小与源图片一样,那么它直接返回源图片。

2,在Android开发过程中,尽量不要自己去调用Bitmap.recycle()方法来回收bitmap所占的内在,因为,你很有可能不清楚这个bitmap是否还有对象在使用,比如如果这个bitmap设置为View的背景图片时。

3,在AdapterView(ListView, GridView, Gallery)在,如果当我们更改了数据后,一定要调用BaseAdapter#notifyDatasetChange()方法来更新UI,这一点务必明确。 




原创粉丝点击