标签归档: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

In July 2005, Google acquired Android Inc., a small startup company based in Palo Alto, California, USA.[20] Android’s co-founders who went to work at Google included Andy Rubin (co-founder of Danger),[21] Rich Miner (co-founder of Wildfire Communications, Inc.),[22] Nick Sears (once VP at T-Mobile),[23] and Chris White (headed design and interface development at WebTV).[24] At the time, little was known about the functions of Android, Inc. other than that they made software for mobile phones.[20] This began rumors that Google was planning to enter the mobile phone market.

从这两个简介中看来 Andy Rubin 的经历不能不说是很神奇啊,曾经供职过 Apple, Philips,然后是 WebTV,Danger,现在是 Google,Andy Rubin 好像一直都是在做移动设备,在该领域他的经验非常的丰富和恐怖。从 Danger 和 Android 的两个简介看来,Andy Rubin 在这两个公司之间是有交集的,不只是职务上,在时间上也是有的,Android 05 年被 Google 收购,08 年 Danger 被 Microsoft 收购。当然我们不管这个了,这只是一个八卦而已。Android 存在的问题很多,昨天看了一下在 Google Code 上的 Android Issues 列表,目前 Open Issues 的数量是 9257,总共 Issues 是 13851,也就是说 Fixed 的 Issued 最多就是 4594 个,比例是 0.331672803,百分比 33%,刚刚 1/3。当然这跟 Android Open 是有一定关系的,任何人都可以提交 Issue,还有很多的 Issue 是重复的。不过我还是认为这个是能说明一些问题的,那就是开发者对于 Android Framework 还不是很满意和放心,当然这其中包括我自己,因为 Android 中有太多 @hide 标签的东西以及需要自己去 Hack 的东西,一个 AlertDialog 你想自定义其风格,非常的困难,而系统提供的默认 UI 风格真的非常的一般,从我自己的心眼里来说我非常的不感冒 Google 原生的 UI,我使用的 Legend 自带的 HTC Scense 我就认为非常出色,是一个不可多得的 UI 套件,当然现在市面上已经有了很多很多的 UI 套件,著名一些的国内 UI 套件,有点心科技、MIUI 等等。这确实说明了大家对 Google 原生的 UI 不是很感冒,而且既然你开放,别人能做得比你好干嘛不做啊。

开放带来的问题不只是 UI 的分裂和多样化,还带来了设备的极大丰富和多样,分辨率的多样和分裂更是成为诸多开发者的噩梦。不过其实也不是那么夸张啦,只是需要很大的精力来做好适配,开发者需要更多的时间来调试,设计者需要为多种设备做考虑。这样的情况下,粗看下来,其实设备更多市场越大,用户越多,开发出来的游戏和应用能更大程度地体现价值,更有可能得到不错的回报。这是一个毫无疑问的肯定句,不过能做到适配多机型的软件并不是非常多,因为这个工作量确实不小,关键是设计的工作量不小。Android 开发的难度大不大我想这个因人而异吧,小米团队号称 80 多天弄出来一个 MIUI,那可是非常强大的呢,而我折腾了半年有余,至今未见什么动静。不过我想我碰到的问题每个开发者难免多少都会碰到一些,所谓都在河边走,谁能不湿鞋啊,不湿鞋的那是光着脚走路。

  1. Android 开发中没有良好的可视化控件开发工具,虽然 ADT 中有一个简单的 XML 预览功能,但是确实非常的简陋,不过 ADT 8.0.1 已经好很多了。
  2. Android Framework 对于内存有限制,默认一个 Activity 分配的非 Native 内存由生产商在 VM 初始化确定其值为多大,在 MDPI 的模拟器上是 16M,在 HDPI 的模拟器上是 24M。很多开发者都曾碰到过让人头疼讨厌的 OOM 错误。
  3. 多设备和多分辨率带来的问题,虽然说这可以是一个正面的数据,大不了不支持那么多就行了,但是随便支持几个,你都会或多或少地碰到一些问题,这些问题就是因为分辨率的适配,你的 Layout 甚至可能需要依照分辨率来编写,你的图像资源文件几乎是完全按着分辨率来重新制作的。
  4. UI 的不统一型带来的问题,由于某些 ROM 直接修改了系统默认控件的效果,在很多时候你无法预知你的程序发布出去用户安装之后是一个什么样的运行效果,因为你无法充分测试所有的设备和 ROM,如果非得从根本上让应用在所有设备上效果一致,控件需要自己完全重新实现,这个非常的不现实。
  5. 用户免费心理对开发者的打击甚大,目前在 Android 上收费的软件成功的很少,虽然 Android 号称已经在全球市场上超过了 iPhone,但是目前还仍未出现一个公司能通过在 Android Market 上出售软件实现盈利。遍地开花的破解软件商店,粗制滥造的免费应用充斥着市场等等,无疑让这趟浑水越来越浑了。
  6. 版本的错乱导致的特别版本,针对 NDK 的程序必须在 1.6 以上的机器上运行,Android 几乎是每半年一个大版本,非常有开源社区的风格。每次更新都会带来一些新的特性,那么你能在你的应用中使用吗?我想大多数时候,回答是否定的。市场上的设备很少能及时跟上 Android 版本更新的速度,为了向下兼容,很多时候我们经常是看着已有的轮子不能用,非得自己重新造轮子。
  7. Native 开发环境的恶劣,几乎无法调试的开发,让开发者几近发狂。JNI 开发的特殊性本来就让挺多人挺头疼的了,目前还没有什么好的开发环境,更是让开发者有苦难言啊。
  8. 胜利就在眼前,而你却看不见胜利,多么地令人遗憾啊!
  9. 没有第九点。

相对而言,iPhone 成功的 App Store 和优秀的开发环境和稳定的 SDK,版本相对稳定,机型更是全球唯一确定的那么几种。Symbian 呢,我也不知道说什么,我挺喜欢 Nokia 的,能做这么好的手机的厂商也就是 Nokia 了。WP 7 会是 2011 年微软最成功的产品吗?Visual Studio 的易用性世人皆知,Windows 系统的操作习惯早日深入全世界人民心中,Microsoft 又舍得在这上面砸钱。

我想说的是 WTF,真混乱,趟浑水究竟要到什么时候啊…

I have a dream, One day there is no mobile device in this world.

Android 中自定义 ListView 无法响应 OnItemClickListener 中的 onItemClick 方法问题解决方案

在 Android 软件设计与实现中我们通常都会使用到 ListView 这个控件,系统有一些预置的 Adapter 可以使用,例如 SimpleAdapter 和 ArrayAdapter,但是总是会有一些情况我们需要通过自定义 ListView 来实现一些效果,那么在这个时候,我们通常会碰到自定义 ListView 无法选中整个 ListViewItem 的情况,也就是无法响应 ListView 的 onItemClickListener 中的 onItemClick() 方法,究竟是为什么呢?我之前也在网上查过不少的资料,但是没有发现什么有价值的文章,有一些是建议在 Adapter 的 getView 方法中对自己需要响应单击事件的控件进行设置。但是最终的效果并不是特别理想,而且我认为这是一种取巧的方式,并不推荐。之后自己查看了一下 ViewGroup 的源码,发现了以下的一段常量声明:

/**
* This view will get focus before any of its descendants.
*/
public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000;
/**
* This view will get focus only if none of its descendants want it.
*/
public static final int FOCUS_AFTER_DESCENDANTS = 0x40000;
/**
* This view will block any of its descendants from getting focus, even
* if they are focusable.
*/
public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000;

/**     * This view will get focus before any of its descendants.     */

public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000;
/**     * This view will get focus only if none of its descendants want it.     */

public static final int FOCUS_AFTER_DESCENDANTS = 0x40000;
/**     * This view will block any of its descendants from getting focus, even     * if they are focusable.     */

public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000;

我们看到了一行代码定义的变量的意思是 “当前 View 将屏蔽他所有子控件的 Focus 状态,即便这些子控件是可以 Focus 的”,其实这段话的意思就是这个变量代表着当前的 View 将不顾其子控件是否可以 Focus 自身接管了所有的 Focus,通常默认能获得 focus 的控件有 Button,Checkable 继承来的所有控件,这就意味着如果你的自定义 ListViewItem 中有 Button 或者 Checkable 的子类控件的话,那么默认 focus 是交给了子控件,而 ListView 的 Item 能被选中的基础是它能获取 Focus,也就是说我们可以通过将 ListView 中 Item 中包含的所有控件的 focusable 属性设置为 false,这样的话 ListView 的 Item 自动获得了 Focus 的权限,也就可以被选中了,也就会响应 onItemClickListener 中的 onItemClick() 方法,然而将 ListView 的 Item Layout 的子控件 focusable 属性设置为 false 有点繁琐,我们可以通过对 Item Layout 的根控件设置其 android:descendantFocusability=”blocksDescendant” 即可,这样 Item Layout 就屏蔽了所有子控件获取 Focus 的权限,不需要针对 Item Layout 中的每一个控件重新设置 focusable 属性了,如此就可以顺利的响应 onItemClickListener 中的 onItenClick() 方法了。

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。具体的代码并没有去查看,先记录一下吧。

====================明天是个好天气=======================

Android Bitmap 内存限制

在编写 Android 程序的时候,我们总是难免会碰到 OOM 的错误,那么这个错误究竟是怎么来的呢?我们先来看一下这段异常信息:

08-14 05:15:04.764: ERROR/dalvikvm-heap(264): 3528000-byte external allocation too large for this process.
08-14 05:15:04.764: ERROR/(264): VM won’t let us allocate 3528000 bytes
08-14 05:15:04.764: DEBUG/skia(264): — decoder->decode returned false
08-14 05:15:04.774: DEBUG/AndroidRuntime(264): Shutting down VM
08-14 05:15:04.774: WARN/dalvikvm(264): threadid=3: thread exiting with uncaught exception (group=0x4001b188)
08-14 05:15:04.774: ERROR/AndroidRuntime(264): Uncaught handler: thread main exiting due to uncaught exception
08-14 05:15:04.794: ERROR/AndroidRuntime(264): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:447)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:346)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:372)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at com.xixun.test.HelloListView.onCreate(HelloListView.java:33)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2459)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2512)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.app.ActivityThread.access$2200(ActivityThread.java:119)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1863)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.os.Handler.dispatchMessage(Handler.java:99)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.os.Looper.loop(Looper.java:123)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at android.app.ActivityThread.main(ActivityThread.java:4363)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at java.lang.reflect.Method.invokeNative(Native Method)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at java.lang.reflect.Method.invoke(Method.java:521)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
08-14 05:15:04.794: ERROR/AndroidRuntime(264):     at dalvik.system.NativeStart.main(Native Method)

从上面这段异常信息中,我们看到了一个 OOM(OutOfMemory) 错误,我称其为 (OMG 错误)。出现这个错误的原因是什么呢?为什么解码图像会出现这样的问题呢?关于这个问题,我纠结了一段时间,在网上查询了很多资料,甚至查看了 Android Issues,确实看到了相关的问题例如 Issue 3405Issue 8488,尤其 Issue 8488 下面一楼的回复,让我觉得很雷人啊:

Comment 1 by romain…@android.com, May 23, 2010

Your app needs to use less memory.

当然我们承认不好的程序总是程序员自己错误的写法导致的 ,不过我们倒是非常想知道如何来规避这个问题,那么接下来就是解答这个问题的关键。

我们从上面的异常堆栈信息中,可以看出是在 BitmapFactory.nativeDecodeAsset(),对应该方法的 native 方法是在 BitmapFactory.cpp 中的 doDecode() 方法,在该方法中申请 JavaPixelAllocator 对象时,会调用到 Graphics.cpp 中的 setJavaPixelRef() 方法,在 setJavaPixelRef() 中会对解码需要申请的内存空间进行一个判断,代码如下:

bool r = env->CallBooleanMethod(gVMRuntime_singleton,

                                   gVMRuntime_trackExternalAllocationMethodID,

                                   jsize);

而 JNI 方法 ID — gVMRuntime_trackExternalAllocationMethodID 对应的方法实际上是 dalvik_system_VMRuntime.c 中的 Dalvik_dalvik_system_VMRuntime_trackExternalAllocation(),而在该方法中又会调用大 HeapSource.c 中的 dvmTrackExternalAllocation() 方法,继而调用到 externalAllocPossible() 方法,在该方法中这句代码是最关键的

heap = hs2heap(hs);

   currentHeapSize = mspace_max_allowed_footprint(heap->msp);

   if (currentHeapSize + hs->externalBytesAllocated + n <=

           heap->absoluteMaxSize)

   {

       return true;

   }

这段代码的意思应该就是当前堆已使用的大小 (由 currentHeapSize 和 hs->externalBytesAllocated 构成) 加上我们需要再次分配的内存大小不能超过堆的最大内存值。那么一个堆的最大内存值究竟是多大呢。通过下面这张图,我们也许可以看到一些线索 (自己画的,比较粗糙)

image

最终的决定权其实是在 Init.c 中,因为 Android 在启动系统的时候会去优先执行这个里面的函数,通过调用 dvmStartup() 方法来初始化虚拟机,最终调用到会调用到 HeapSource.c 中的 dvmHeapSourceStartup() 方法,而在 Init.c 中有这么两句代码:

gDvm.heapSizeStart = 2 * 1024 * 1024;   // Spec says 16MB; too big for us.

gDvm.heapSizeMax = 16 * 1024 * 1024;    // Spec says 75% physical mem

在另外一个地方也有类似的代码,那就是 AndroidRuntime.cpp 中的 startVM() 方法中:

strcpy(heapsizeOptsBuf, "-Xmx");

property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");

//LOGI("Heap size: %s", heapsizeOptsBuf);

opt.optionString = heapsizeOptsBuf;

同样也是默认值为 16M,虽然目前我看到了两个可以启动 VM 的方法,具体 Android 何时会调用这两个初始化 VM 的方法,还不是很清楚。不过可以肯定的一点就是,如果启动 DVM 时未指定参数,那么其初始化堆最大大小应该就是 16M,那么我们在网上查到了诸多关于解码图像超过 8M 就会出错的论断是如何得出来的呢?

我们来看看 HeapSource.c 中的这个方法的注释

/*

* External allocation tracking

*

* In some situations, memory outside of the heap is tied to the

* lifetime of objects in the heap.  Since that memory is kept alive

* by heap objects, it should provide memory pressure that can influence

* GCs.

*/

static bool

externalAllocPossible(const HeapSource *hs, size_t n)

{

    const Heap *heap;

    size_t currentHeapSize;

   /* Make sure that this allocation is even possible.

     * Don’t let the external size plus the actual heap size

     * go over the absolute max.  This essentially treats

     * external allocations as part of the active heap.

     *

     * Note that this will fail "mysteriously" if there’s

     * a small softLimit but a large heap footprint.

     */

    heap = hs2heap(hs);

    currentHeapSize = mspace_max_allowed_footprint(heap->msp);

    if (currentHeapSize + hs->externalBytesAllocated + n <=

            heap->absoluteMaxSize)

    {

        return true;

    }

    HSTRACE("externalAllocPossible(): "

            "footprint %zu + extAlloc %zu + n %zu >= max %zu (space for %zu)\n",

            currentHeapSize, hs->externalBytesAllocated, n,

            heap->absoluteMaxSize,

            heap->absoluteMaxSize –

                    (currentHeapSize + hs->externalBytesAllocated));

    return false;

}

标为红色的注释的意思应该是说,为了确保我们外部分配内存成功,我们应该保证当前已分配的内存加上当前需要分配的内存值,大小不能超过当前堆的最大内存值,而且内存管理上将外部内存完全当成了当前堆的一部分。也许我们可以这样理解,Bitmap 对象通过栈上的引用来指向堆上的 Bitmap 对象,而 Bitmap 对象又对应了一个使用了外部存储的 native 图像,实际上使用的是 byte[] 来存储的内存空间,如下图:

image

我想到现在大家应该已经对于 Bitmap 内存大小限制有了一个比较清楚的认识了。至于前几天从 Android123 上看到 “Android 的 Btimap 处理大图片解决方法”一文中提到的使用 BitmapFactory.Options 来设置 inTempStorage 大小,我当时看完之后就尝试了一下,这个设置并不能解决问题,而且很有可能会给你带来不必要的问题。从 BitmapFactory.cpp 中的代码来看,如果 option 不为 null 的话,那么会优先处理 option 中设置的各个参数,假设当前你设置 option 的 inTempStorage 为 1024*1024*4(4M) 大小的话,而且每次解码图像时均使用该 option 对象作为参数,那么你的程序极有可能会提前失败,在我的测试中,我使用了一张大小为 1.03M 的图片来进行解码,如果不使用 option 参数来解码,可以正常解码四次,也就是分配了四次内存,而如果我使用 option 的话,就会出现 OOM 错误,只能正常解码两次不出现 OOM 错误。那么这又是为什么呢?我想是因为这样的,Options 类似与一个预处理参数,当你传入 options 时,并且指定临时使用内存大小的话,Android 将默认先申请你所指定的内存大小,如果申请失败,就抛出 OOM 错误。而如果不指定内存大小,系统将会自动计算,如果当前还剩 3M 空间大小,而我解码只需要 2M 大小,那么在缺省情况下将能解码成功,而在设置 inTempStorage 大小为 4M 的情况下就将出现 OOM 错误。所以,我个人认为通过设置 Options 的 inTempStorage 大小根本不能作为解决大图像解码的方法,而且可能带来不必要的问题,因为 OOM 错误在某些情况是必然出现的,也就是上面我解释的那么多关于堆内存最大值的问题,只要解码需要的内存超过系统可分配的最大内存值,那么 OOM 错误必然会出现。当然对于 Android 开发网为何发布了这么一篇文章,个人觉得很奇怪,我想作为一个技术人员发布一篇文章,至少应该自己尝试着去测试一下自己的程序吧,如果只是翻翻 SDK 文档,然后就出来一两篇文章声称是解决某问题的方案,恐怕并不是一种负责任的行为吧。

=================================

还是点到为止吧,希望大家都自己去测试一下,验证一下,毕竟自己做过验证的才能算是放心的。

Android 中对图像进行 Base64 编码

首先我们来看看维基百科是怎么定义的这个概念的。

Base64 is a generic term for a number of similar encoding schemes that encode binary data by treating it numerically and translating it into a base 64 representation. The Base64 term originates from a specificMIME content transfer encoding.

Base64 encoding schemes are commonly used when there is a need to encode binary data that needs be stored and transferred over media that are designed to deal with textual data. This is to ensure that the data remains intact without modification during transport. Base64 is used commonly in a number of applications including email via MIME, and storing complex data in XML.

当然我们对于概念可以不做过多的理会,只要知道这是一种编码方式,设计用来进行数据传输,而且要不易为人读懂,也就是为了加密用的设计的目的是用文本字符串来传输二进制数据 (经麦壳童鞋和 KongQue 童鞋二人提醒后,更正)。至于他的算法实现,我们可以 Google 出来很多中成熟的算法。

我选用了这个网站上提供的源码,测试之后暂时还没有发现问题,并且该源码的作者将版权完全放弃了,无需任何的 License 授权,也不怕 License 感染,拿过来用就是了,作者只是希望使用的人如果发现问题可以反馈给他,如果能参与进来一起解决那是更好。

Android SDK2.2 之后提供了 Base64 编码相关的 API 类 Base64, 不过鉴于开发的程序需要向下兼容,我想大部分的程序还是需要自己实现或者寻求第三方的实现来解决该问题。下面我们来一步步看看如何将图片编码成一个 Base64 编码的字符串进行传输。

任何图像到了程序中都需要解码成为 Bitmap 来进行绘制 (不论是显示的解码还是系统在 API 中帮的忙),解码之后的 Bitmap 就是一张位图也就是一个 byte 数组,在 Android 中 Bitmap 有 compress(Bitmap.CompressFormat format, int quality, OutputStream stream) 这个方法,该方法可以将 Bitmap 重新压缩存储为别的格式,可以是 PNG/JPG 文件,或者是 ByteArrayOutputStream 输出。

public static String getBitmapStrBase64(Bitmap bitmap){
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    bitmap.compress(CompressFormat.PNG, 100, baos);
    byte[] bytes  = baos.toByteArray();
    return Base64.encodeBytes(bytes);
}

这就是获取位图 Base64 编码的代码,同理也可以将 Base64 编码字符串转化为 Bitmap 对象

public Bitmap getBitmap(){
        try {
            byte[] bitmapArray;
            bitmapArray = Base64.decode(iconBase64);
            return BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

以上就是所有的代码实现了,比较简单,效率还不错。