在上一篇博文Android Bitmap内存限制中我们详细的了解并分析了Android为什么会在Decode Bitmap的时候出现OOM错误,简单的讲就是Android在解码图片的时候使用了本地代码来完成解码的操作,但是使用的内存是堆里面的内存,而堆内存的大小是收VM实例可用内存大小的限制的,所以当应用程序可用内存已经无法再满足解码的需要时,Android将抛出OOM错误。 这里讲一个题外话,也就是为何Android要限制每个应用程序的可用内存大小呢?其实这个问题可能有多方面的解答,目前我自己考虑到的有两点: 使得内存的使用更为合理,限制每个应用的可用内存上限,可以防止某些应用程序恶意或者无意使用过多的内存,而导致其他应用无法正常运行,我们众所周知的Android是有多进程的,如果一个进程(也就是一个应用)耗费过多的内存,其他的应用还搞毛呢?当然在这里其实是有一个例外,那就是如果你的应用使用了很多本地代码,在本地代码中创建对象解码图像是不会被计算到的,这是因为你使用本地方法创建的对象或者解码的图像使用的是本地堆的内存,跟系统是平级的,而我们通过Framework调用BitmapFactory.decodeFile()方法解码时,系统虽然也是调用本地代码来进行解码的,但是Android Framework在实现的时候,刻意地将这部分解码使用的内存从堆里面分配了而不是从本地堆里分配的内存,所以才会出现OOM,当然并不是说从本地堆里分配就不会出现OOM,本地堆分配内存超过系统可用内存限制的话,通常都是直接崩溃,什么错误可能都看不到,也许会有一些崩溃的错误字节码之类的。 省电的考虑,呃…,原因我好像也不能很明白地说出来。 回到正题来,我们在应用的设计和开发中可能会经常碰到需要在一个界面上显示数十张图片乃至上百张,当然限于手机屏幕的大小我们通常在设计中会使用类似于列表或者网格的控件来展示,也就是说通常一次需要显示出来图片数还是一个相对确定的数字,通常也不会太大。如果数目比较大的画,通常显示的控件自身尺寸就会比较小,这个时候可以采用缩略图策略。下面我们来看看如果避免出现OOM的错误,这个解决方案参考了Android示范程序XML Adapters中的ImageDownloader.java中的实现,主要是使用了一个二级缓存类似的机制,就是有一个数据结构中直接持有解码成功的Bitmap对象引用,同时使用一个二级缓存数据结构持有解码成功的Bitmap对象的SoftReference对象,由于SoftReference对象的特殊性,系统会在需要内存的时候首先将SoftReference对象持有的对象释放掉,也就是说当VM发现可用内存比较少了需要触发GC的时候,就会优先将二级缓存中的Bitmap回收,而保有一级缓存中的Bitmap对象用于显示。 其实这个解决方案最为关键的一点是使用了一个比较合适的数据结构,那就是LinkedHashMap类型来进行一级缓存Bitmap的容器,由于LinkedHashMap的特殊性,我们可以控制其内部存储对象的个数并且将不再使用的对象从容器中移除,这就给二级缓存提供了可能性,我们可以在一级缓存中一直保存最近被访问到的Bitmap对象,而已经被访问过的图片在LinkedHashMap的容量超过我们预设值时将会把容器中存在时间最长的对象移除,这个时候我们可以将被移除出LinkedHashMap中的对象存放至二级缓存容器中,而二级缓存中对象的管理就交给系统来做了,当系统需要GC时就会首先回收二级缓存容器中的Bitmap对象了。在获取对象的时候先从一级缓存容器中查找,如果有对应对象并可用直接返回,如果没有的话从二级缓存中查找对应的SoftReference对象,判断SoftReference对象持有的Bitmap是否可用,可用直接返回,否则返回空。 主要的代码段如下: private static final int HARD_CACHE_CAPACITY = 16; // Hard cache, with a fixed maximum capacity and a life duration private static final HashMap<String, Bitmap> sHardBitmapCache = new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) { private static final long serialVersionUID = -57738079457331894L; @Override [...]
Category Archives: Android
也扯Android开发
首先声明一下,我做移动平台开发的时间很短,短到还不足一年,其次再强调一下,我涉及的移动开发平台只是Android。这就意味着我看到的东西非常非常的片面,甚至都没有参考价值和意义。只是自己周末觉得应该写个博客,而暂时也没有整理好的技术博客,所以索性就扯一下。 在做Android之前,我做过一年时间的JNI开发,一门原本非常非常偏门的中间件开发技术,在进入之前的公司(SuperMap)之前,我从未接触过JNI开发,虽然在零星地做着一些Java方面的工作,但是均只限于Demo的学习与演练,绝对没有半分实践的经验。进入超图之后,开始学习JNI开发技术,半年之后渐入佳境,日常的开发工作之余开始接触一些业务的东西,也就是说开始接触了一些技术支持方面的工作,偶尔需要到客户的现场处理缺陷或者针对用户的使用场景提出解决方案。当然这么说好像有点大,好吧,我承认有点虚。不过当时山中无老虎,猴子尚且称霸王呢,自己当时也算是团队中的核心开发成员了,所以当时有幸出了一次差,而且有幸坐了第一次飞机,体验了一下空中交通工具的便捷性。还是回到正题吧,在超图工作的重点有两个,一个是编码一个是支持,编码的过程非常简单,流程化很高。接口设计由架构团队讨论决定之后,底层开发团队根据功能需求完成基础接口,然后由Java层面通过JNI技术将底层团队提供的C/C++接口封装起来,当然同时还有.NET团队在做CLR的封装。 JNI封装的工作性质很简单,主要就是与C/C++层面的通信,把一堆基础接口攒成一个可用性高的接口,当然这个工作主要有架构师团队完成设计。编码工作中主要注意的东西有一些,异常判断,数组搬移,野指针等等。SDK作为基础开发平台主要就是模块化,稳定压倒一切。在超图的工作经历,让我对与JNI整个的工作机制以及编码方法有了一个大体的了解,来到喜讯之后,做了第一个游戏——《黄金矿工喜讯特别版》Android版本。编码的过程中逐渐开始熟悉Android的开发方式以及框架结构,在开发过程中碰到了很多的问题,不过所幸最后产品还是成功的出炉了。据说市场反应还不错,因为这作为公司一个磨练团队的产品,目前还没有后续的开发和维护,只是出了一个版本,对于市场反馈也没有做太多的统计,甚至都没有放到Google Market上去,只是在一些国内论坛和第三方市场中做过分享。不过大体了解一些Android中开发的猫腻和技巧,Android由于其开源的特性,有很多东西可以窥探到,在Android Framework中有很多带有// TODO标签的代码段,很多的注释中直接标明“This is a bug”类似的字眼。不过正因为它的开源,很多Framework的机制可以自己一一剥开来慢慢瞧,例如对于图像解码中非常容易出现的OOM错误,顺着代码链,我们可以看到所有的代码,从Java=>JNI=>C,最终可以跟踪到VM创建时堆大小的初始化,详情可以参见我这篇博客。 虽然最终我们看似已经找到了问题,但是究竟如何解决这个问题呢,目前我还没有找到非常好的方法,因为这个受到了Framework的限制,而Framework也没有提供非常好的机制来防止该问题的出现,不过也许可以借助异常的捕获来解决该问题,不过本人自己还未做过该方面的尝试。在做完这个游戏之后,又经历了两个软件项目,项目中自己主要负责的是UI层面的东西,不过主要的设计和UE控制并不由我主要负责,本人主要负责代码实现,主要设计与UE控制由设计师来完成。Android原生的开发环境是Java,按说开发效率是比较高的,当然这个并不是我自己说的,也是从网络上诸多的报告和大牛们的分析中看到的,至于他们为什么得出这个结论我自己并不是非常明了,我自己比较有感触的就是Java相对于C/C++来说,自由度相对要小,不过基础库非常完备,很多的特性均有平台完成,程序员主要负责的就是逻辑和界面的问题,相对来说会较C/C++效率高一些(当然这还是因人而异的)。既然Java开发效率要高一些,那么Android中的开发效率是否会高一些呢?我看不尽然,Android虽然提供Java的开发环境,但是其框架环境非常庞大和复杂,个人认为比Java SE复杂多了很多。Java只是Android开发中的一个敲门砖而已,不过既然已经没有了语言层面上的问题,那么就剩下框架的问题。 然而框架的问题并不是那么的简单,Android是一个在工程实验室中就被推到市场上的项目,当年Andy Rubin创建Android具体是什么时候不是很清楚,不过Andy Rubin还是Danger, Inc.的联合创始人,而Danger在2008年2月份才被Microsoft收购。 The company was originally started by former Apple Inc., WebTV and Philips employees Andy Rubin, Joe Britt, and Matt Hershenson. Danger was acquired by Microsoft on 11 February 2008, for a price rumored to be around $500 million (USD). 由此看来这家公司参与者还有Apple的身影。再来看看Android [...]
Android TabActivity无法正常bindService解决方法
在Android开发的过程中,我们很有可能会使用到TabActivity来进行开发,而如果你的程序中需要针对TabActivity的TabHost中的每一个Activity绑定一个Service,通常我们的做法是在对应Tab页的Activity的onCreate()方法中进行bind操作,但是通过实践表明这个方法是无法达到绑定效果,在网上查了一下,发现在Google Android Issue中有这个缺陷,缺陷详细信息在这里(Google Android Issue 2483),三楼的oliver给出了正解 Using getApplicationContext().bindService instead of just bindService on your activity solves the problem as it is using the higher level application context. 也就是在TabActivy的TabHost中的Activity如果需要bindService的话,需要先调用getApplicationContext()获取其所属的Activity的上下文环境才能正常bindService,也就是在onCreate()方法中使用this.getApplicationContext().bindService([args…])就可以了,否则bindService将永远失败返回false。具体的代码并没有去查看,先记录一下吧。 ====================明天是个好天气=======================
