分类目录归档: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;
    }

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