0%

翻自:http://developer.android.com/training/improving-layouts/index.html 布局是Android程序中直接影响用户体验的关键部分,如果写得不好,会导致程序内存饥饿,UI卡顿。Android SDK里包含了帮助你确定布局性能问题的工具,结合下面的课程,你可以用少量的内存实现流畅地滑动。

课程

优化布局层次

跟一个复杂的网页会令加载时间变长一样,如果布局层次太复杂,同样会引起性能问题。本节向你展示了如何用SDK里的工具检查布局,并且发现性能瓶颈。

标签复用布局

如果你的应用UI在多个部分有重复的确定的布局,本课向你展示了如果创建高效的,可重用的布局设计,然后在合适的地方插入它们。

按需加载视图

除了简单地在一个布局里插入另一个布局,你可能想要仅仅让布局在需要的时候显示,有时在Activity运行以后。本课向你展示如何通过按需载入部分布局,提高布局的初始化性能。

让ListView流畅滑动

如果你构建了一个在每个item里包含复杂或者大数据量ListView,滚动性能可能会有影响。本节提供了如果让滚动更加流畅的一些建议。

翻自:http://developer.android.com/training/articles/memory.html 在任何软件开发环境中,RAM都是宝贵的资源,但在移动操作系统中更加珍贵。尽管Dalvik虚拟机有垃圾回收机制,也不要忽略分配和释放内存。 为了让GC回收内存,你要避免内存泄漏(通常因为全局成员变量引用对象引起),并且在适当的时候释放对象引用。对大多数app来说,垃圾回收负责剩下的:当相应的对象离开app活动线程范围时,系统回收内存分配。为了让GC回收内存,你要避免内存泄漏(通常因为全局成员变量引用对象引起),并且在适当的时候释放对象引用。对大多数app来说,垃圾回收负责剩下的:当相应的对象离开app活动线程范围时,系统回收内存分配。 这篇文档解释了Android如何管理app处理内存分配,你如何主动减少内存占用。更多用java编程时清理资源的信息,可以参考关于管理资源引用的文档和书籍。如果你在找分析应用的内存占用的信息,阅读:Investigating Your RAM Usage 以后会翻译

Android如何管理内存

Android不提供交换空间,而是使用页和内存映射来管理内存。这意味着任何内存的修改,不论是分配给新对象还是映射内存页,都在内存中,无法被移出.所以,完成释放内存的唯一方法是释放对象引用,把这块内存交给GC。这里有个例外,任何映射过来过来没有修改的,例如代码,如果系统要使用内存可以调用。

共享内存

为了适应内存中的所有需要,Android会跨进程共享内存页.可以用下面的方式来实现:

  • 每个app进程都是从Zygote进程fork出来的。Zygote进程在系统启动并且载入通用框架代码和资源时启动(像activity主题),为了启动一个app进程,系统从Zygote进程里fork出一个进程来载入运行app的代码。这使得大多数为Android 框架代码和资源的内存分配可以跨所有app进程共享
  • 大多数静态的数据是被映射到一个进程里.这不仅允许相同的数据在进程间被共享,同时也允许在需要的时候移出。静态数据例子:Dalvik代码(在直接映射的.odex文件中),app资源(通过设计一个资源表的结构,使得可以被映射和调整),native代码的so文件
  • 在很多地方,android通过显示地分配共享内存区域,来跨进程共享动态内存。举个例子,窗口surface在app和screen compositor间共享内存,cursor在Content Provider和客户端间共享内存

由于大量共享内存,确定你的应用用了多少内存需要当心。http://developer.android.com/tools/debugging/debugging-memory.html 从技术上讨论了判断app内存使用。

分配和回收应用内存

下面是Android如何分配和回收内存的几个事实:

  • 每个进程的Dalvik堆局限于一个单一的虚拟内存区域。这里定义了可以根据需要自动增长的逻辑堆大小(但只有达到系统给每个app定义的上限时)
  • 堆的逻辑大小,跟堆使用的物理内存大小不同。当检查应用的堆时,Android计算一个叫PPS(比例设置大小)的值,该值记录了与其他进程共享的dirty和clean页,但只有多少应用共享内存的一个比例 。这个值就是系统认为了你的物理内存足迹,更多关于pps的信息,参考Investigating Your RAM Usage guide
  • Dalvik堆并不会整理碎片,Android只会在无用的空间处理堆的结尾时收缩堆的size.但这并不意味着堆用的物理内存不能被收缩。在GC后,Dalvik找到堆里无用的页,并且用madvise返回给内核.所以,分配和释放大块内存成对出现,就会释放几乎所有的物理内存。然而,小块分配内存的释放没有那么明显的效果,因为这块内存可能仍然被其他没有释放的模块共享

限制app内存

为了支持多任务,Android给每个app限制了堆大小。具体限制大小根据设备不同以及内存大小不同而变动。如果你的app达到了这个限制还是尝试分配更多内存,就会报OutOfMemoryError. 因为某些原因,你可能需要通过查询系统判断当前设备上堆限制的大小,比如,判断缓存里放多少数据比较安全。你可以通过调用getMemoryClass()来得到这个数字。它返回一个整数表示你的app的堆可用大小(M).这将在下面作进一步讨论,参见 判断你应该使用多少内存。

切换App

当用户切换App时,Android并没有把之前的App放到交换空间里,而是把所有非前台的应用组件放到一个LRU缓存里。举例来说,用户第一次运行app时,为它创建了一个进程,但当用户离开这个app时,进程并没有退出。系统缓存了这个进程,所以当用户回到这个应用时,进程可以重用以达到快速切回。 当你的应用缓存了进程,并且保留当前没有用的内存时,即使用户没有在用,也限制了系统的整体性能。所以,当系统内存较低时,它会清理LRU缓存里最近最少用的那一部分进程,但也会考虑哪个进程最需要内存。为了让你的进程缓存得尽可能久,遵从下面几条关于何时释放引用的建议。 更多关于进程在没有运行在前台时如何被缓存以及Android决定杀死哪个进程的信息,参考 Processes and Threads guide

你的App该如何管理内存

在整个开发阶段,你都应该考虑到内存的限制,包括app设计(开发前).这里有几条让你的设计和代码更高效的建议,通过再三应用聚合同样的技术。 你在设计和实现app时应该遵循以下技巧以实现高效的内存利用。

谨慎使用Service

如果你的应用需要一个Service在后台执行任务,在没有任何执行时,不要让它运行。同样不要忘记在它结束工作时关闭Service. 当你启动一个Service时,系统更倾向于保持这个进程以让Service继续运行。这让进程变得非常昂贵,因为被它使用的内存无法被系统回收。这减少了系统可以缓存在LRU里进程的数量。让App切换效能差一些。它甚至可能会导致系统在可用内存紧张,无法维持足够的进程供所有当前运行的Service时超负荷运转。 限制Service生命期限的最佳方法是使用IntentService.它会在处理完启动它的Intent后,自动杀死自己。更多信息,阅读 Running in a Background Service 在不需要的时候还让Service运行是最糟糕的内存管理之一。所以,不要贪婪地想用保持Service运行来保持App运行。这不仅会增加在达到内存限制时风险,用户也会因为这样的作弊行为而卸载它。

当用户界面隐藏时,释放内存

当用户导航到其他app,你的ui不再可见时,你应该释放那些只有UI使用的资源。在这个时候释放资源可以大大增加系统缓存进程的能力,这跟用户体验直接相关。 当用户退出ui时,在Activity里实现onTrimMemory()回调,你应该在这个方法里监听TRIM_MEMORY_UI_HIDDEN,它表示你的UI从视图中隐藏了,你需要释放只有UI使用的资源。 注意,只有当应用的所有ui都隐藏时,才会收到onTrimMemory()回调。这跟onStop()回调不同,它在Activity隐藏时就会回调,包括切换到同一个app的不同Activity.所以,尽管你应该实现onStop()释放activity的资源,像网络连接,或者解注册广播接收器,你通常不应该释放你的UI资源,直到收到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)回调。这确保了当用户导致到应用内的其他activity再回来时,ui资源仍旧可用并能快速恢复.

当内存紧张时释放资源

在App生命周期的任何一个阶段,onTrimMemory()同样告诉你什么时候整个设备的可用内存变低。此时,你应该根据onTrimMemory传入的几个内存级别释放内存:

  • TRIM_MEMORY_RUNNING_MODERATE 你的应用正在运行,并且不会被杀死,但设备已经处于低内存状态,并且开始杀死LRU缓存里的内存。
  • TRIM_MEMORY_RUNNING_LOW 你的应用正在运行,并且不会被杀死,但设备处于内存更低的状态,所以你应该释放无用资源以提高系统性能(直接影响app性能)
  • TRIM_MEMORY_RUNNING_CRITICAL 你的应用还在运行,但系统已经杀死了LRU缓存里的大多数进程,所以你应该在此时释放所有非关键的资源。如果系统无法回收足够的内存,它会清理掉所有LRU缓存,并且开始杀死之前优先保持的进程,像那些运行着service的。同时,当你的app进程当前被缓存,你可能会从onTrimMemory()收到下面的几种level.
  • TRIM_MEMORY_BACKGROUND 系统运行在低内存状态,并且你的进程已经接近LRU列表的顶端(即将被清理).虽然你的app进程还没有很高的被杀死风险,系统可能已经清理LRU里的进程,你应该释放那些容易被恢复的资源,如此可以让你的进程留在缓存里,并且当用户回到app时快速恢复.
  • TRIM_MEMORY_MODERATE 系统运行在低内存状态,你的进程在LRU列表中间附近。如果系统变得内存紧张,可能会导致你的进程被杀死。
  • TRIM_MEMORY_COMPLETE 系统运行在低内存状态,如果系统没有恢复内存,你的进程是首先被杀死的进程之一。你应该释放所有不重要的资源来恢复你的app状态。 因为onTrimMemory()是在API 14里添加的,你可以在老版本里使用onLowMemory()回调,大致跟TRIM_MEMORY_COMPLETE事件相同。

提示:当系统开始杀死LRU缓存里的进程时,尽管它主要从下往上工作,它同时也考虑了哪些进程消耗更多的内存,如果杀死它们,系统会得到更多的可用内存。所以,在LRU整个列表中,你消耗越少的内存,留在列表里的机会就更大。

确定你可以用多少内存

之前提过,每个Android设备有不同的可用内存,因此给每个app提供不同的堆限制。你可以调用getMemoryClass()来确定你的app能用多少M堆大小。如果你的app尝试分配更多的内存,会收到OutOfMemoryError 在非常特殊的情况下,你可以请求一个较大的堆,通过在Androidmanifest.xml的application标签里设置largeHeap属性为true。如果你这么干,你可以调用 getLargeMemoryClass()来得到较大堆的大小. 然而,这个特性只是给一小部分需要消耗大量内存的app(像大图片编辑app)准备的。不要仅仅因为你的应用报了OOM,需要快速修复而设置large heap,你应该在知道所有的内存用在哪,为什么需要保留时才设置.即使你很自信能证明你的app需要这么多内存,你也应该尽可能避免。使用额外的内存,会影响用户体验,因为垃圾收回需要更长的时间,当切换任务或执行其他普通操作时,系统性能也会变慢。 另外large heap的大小并不是在所有机器上都一样,并且,当运行在限制RAM的设备上时,large heap大小可能会跟正常的heap大小相同。所以,即使你请求了large heap,也应该调用 getMemoryClass()来得到heap大小,并努力让内存在这个限制之内.

避免在图片上浪费内存

载入图片时,应该仅仅在内存中保留适应当前设备屏幕大小的图片。如果原图分辨率高,做下缩小。谨记,图片分辨率的增加伴随着内存占用的增加,因为x,y的值都增加。 提示:在Android 2.3.x(API level 10)或更低版本,bitmap对象总是跟app的堆大小相同,而不管分辨率图片真实分辨率(实际像素数据分开保存在native内存中,非dalvik)。这使得调试图片内存分配更加困难,因为大多数heap分析工具,看不到native的内存分配。 然而,从Android 3.0 (API level 11)开始,图片像素数据在app的Dalvik heap里分配,改善了垃圾回收和可调试性。所以,如果你的app使用图片,在老设备上要找到app使用内存有些困难时,你可以切换到高版本的设备来调试. 更多关于位图的提示,阅读 Managing Bitmap Memory.

使用优化的数据容器

利用Android框架里的优化过的容器,像SparseArray, SparseBooleanArray, 和 LongSparseArray.一般的HashMap实现内存方面效率较差,因为它需要为每个映射分开对象条目。另外,SparseArray更高效因为它们避免了系统对key(value有时也行)做自动封装,如果有意义,不要担心使用原始的数组。

注意内存开销

了解你用的使用和库的成本和开销,在设计app时谨记,从开始到结束。经常,表面上看起来无害的东西会带来庞大的开销。例子包括: 枚举比静态常量需要大于两倍的内存,在Android里,应该严格避免使用数据。 Java里每个类(包括匿名和内部类)使用大约500的字节的代码。 每个类实例有12-16字节的开销. 放单一的条目到HashMap,需要创建额外的entry对象,消费32字节(参考上一节 使用优化的数据容器) 积少成多,app设计会被这些开销所影响。在内存里的一大堆小对象里分析,找到问题,并非易事。

小心使用代码抽象

经常,开发者们使用抽象,仅仅是因为”良好的编程实践”,因为抽象可以提高代码可扩展性,可维护性。然后,抽象意味着成本:通常它们需要一定数量的额外代码,需要更多的时间,更多的内存把那部分代码映射到内存。所以,如果你的抽象没有实现的作用,你应该避免使用。

使用nano protobufs序列化数据

Protocol buffers是一个由google设计的语言中立,平台中立,可扩展机制,用来序列化结构数据.像xml,但更小,更快,更简单.如果你决定使用protobufs,你应该在客户端代码使用nano protobufs。普通的protobufs生成非常冗长的代码,可能会给app带来各自问题:增加内存占用,apk体积增长,执行慢,并且很快会达到dex文件的限制。 更多信息,参见 protobuf readme里的Nano version一节。

避免依赖注入框架

使用像Guice或RoboGuice的注入框架,可能很吸引人,因为可以简化你写的代码,提供一个用于测试或其他配置的有用的可适配的环境。然而,这些框架倾向于在初始化时扫描注解执行一大堆的方法,这意味着大量的代码被映射到内存包括你不需要的。这些映射页被分配到clean内存里,android可以扔掉他们,但在一个很长的周期内不会被移除。

当心使用外部库

外部库通常不是为移动环境写的,在移动客户端上使用可能会导致效能低。至少,当你决定要用一个外部库时,你应该承担起移植维护并为移动优化的工作。在决定使用前,为这些工作作计划,分析库大小,内存占用。 即使为Android设计的库,也可能会带来风险,因为每个库做不同的事情。比如,一个库使用nano protobufs另一个库使用micro protobufs。现在你有两种不同的protobuf实现。这些是不可预料的。ProGuard救不了你,因为这些都是你需要的库的特性需要的低级别的依赖。当你从库中使用一个Activity的子类(会有大片的依赖)或使用反射或其他,更容易出问题。 也要小心不要掉入为几十个特性中的一两个特性使用一个共享库的陷阱。你不需要增加一大堆你不会用的代码开销。找了很久也没找到一个与你的需求非常符合的现有实现,自己创建实现也许是最好的。

优化整体性能

Best Practices for Performance里列出了各种各样的关于优化整体性能方法。很多这些文档包括了cpu性能优化建议,但很多建议同时对优化内存也有帮助,像减少UI的layout对象数量。 同时,你也应该阅读关于用布局调试工具优化UI,利用lint提供的优化建议.

使用ProGuard除去无用的代码

ProGuard通过移除无用的代码,语义无关地重命名类,字段,方法来收缩,优化,混淆你的代码。使用ProGuard能使你的代码更加紧凑,减少映射的ram页。

在最终的apk上使用zipalign

如果你对系统构建的apk(包括用最终产品签名的)做后处理,你必须运行zipalign,让其重新对齐。否则会导致你的app需要更多的内存,因为像资源这些东西可能不必从apk映射。 提示:Google Play商店不接受没有zipaligne过的APK

分析你的内存使用

一旦你实现了一个相对稳定的版本,就要开始分析贯穿整个生命周期的内存占用。关于如何分析app,阅读Investigating Your RAM Usage

使用多进程

如果合适,一项可以帮助你管理应用内存的高级技术是拆分你的应用组件到多个进程。这项技术使用时必须小心,并且大多数应用不应该使用,因为如果错误使用,它很容易造成内存占用大幅增长。它主要适用于那些在后台执行跟前台一样的重要工作,并且能分开管理这些操作的app。 一个适用多进程的例子是,构建一个音乐播放器,有一个Service长期在后台播放音乐。如果整个app运行在一个进程,为Activity UI分配的很多内存必须保持跟播放音乐一样长的时间,即使当前用户已经跳到另一个app,但Service仍在播放。一个类似这样的app,可以拆分成两个进程,一个用作UI,一个用于后台服务. 你可以通过声明android:process属性给每个组件指定一个单独的进程。比如,你可以指定service运行在一个与app主进程不同的叫”background”(随你喜欢,任意)的进程.

<service android:name=".PlaybackService"
         android:process=":background" />

你的进程名应该以冒号开始,来确保这个进程对app私有。 在你决定创建一个新进程时,你必须知道对内存的影响。为了展示每个进程的影响,给大家展示下dump出来的内存信息,一个空的基本上不做什么的进程要消耗额外的1.4M内存, adb shell dumpsys meminfo com.example.android.apis:empty ** MEMINFO in pid 10172 [com.example.android.apis:empty] ** Pss Pss Shared Private Shared Private Heap Heap Heap Total Clean Dirty Dirty Clean Clean Size Alloc Free ------ ------ ------ ------ ------ ------ ------ ------ ------ Native Heap 0 0 0 0 0 0 1864 1800 63 Dalvik Heap 764 0 5228 316 0 0 5584 5499 85 Dalvik Other 619 0 3784 448 0 0 Stack 28 0 8 28 0 0 Other dev 4 0 12 0 0 4 .so mmap 287 0 2840 212 972 0 .apk mmap 54 0 0 0 136 0 .dex mmap 250 148 0 0 3704 148 Other mmap 8 0 8 8 20 0 Unknown 403 0 600 380 0 0 TOTAL 2417 148 12480 1392 4832 152 7448 7299 148 提示:更多关于如何阅读输出,参见Investigating Your RAM Usage,这里的关键数据是Private Dirty和Private Clean内存,展示了这个进程正在使用大约1.4M不可分页的内存(分布在Dalvik堆,Native分配,预订保留,库加载),另一个150K的内存用来映射执行。 内存印迹对一个空进程来说是相当重要的,当这个进程开始工作时,会快速增长。如,这是一个仅仅用来在Activity上展示文本的进程内存使用量: ** MEMINFO in pid 10226 [com.example.android.helloactivity] ** Pss Pss Shared Private Shared Private Heap Heap Heap Total Clean Dirty Dirty Clean Clean Size Alloc Free ------ ------ ------ ------ ------ ------ ------ ------ ------ Native Heap 0 0 0 0 0 0 3000 2951 48 Dalvik Heap 1074 0 4928 776 0 0 5744 5658 86 Dalvik Other 802 0 3612 664 0 0 Stack 28 0 8 28 0 0 Ashmem 6 0 16 0 0 0 Other dev 108 0 24 104 0 4 .so mmap 2166 0 2824 1828 3756 0 .apk mmap 48 0 0 0 632 0 .ttf mmap 3 0 0 0 24 0 .dex mmap 292 4 0 0 5672 4 Other mmap 10 0 8 8 68 0 Unknown 632 0 412 624 0 0 TOTAL 5169 4 11832 4032 10152 8 8744 8609 134 简单地在UI上显示一些文本,进程占的内存几乎达到了3倍,4MB。这引出了一个重要的结论:如果你想要拆分你的app到多个进程,应该只有一个负责UI,其他进程应该避免UI操作,因为这会进程需要的内存快速增长(特别是你开始加载图片或其他资源时)。一旦UI被画出来,要减少内存使用就很难或几乎不可能。 此外,当运行多个进程时,尽可能保持代码精简就更加重要。因为在进程里会有一些没必要重复的内存开销。如,如果你用枚举(尽管你不应该用枚举),每个进程都需要创建和初始化这些常量。其他适配器和临时变量里的抽象或其他开销也会重复。 另一个需要关心的多进程问题是他们之间存在的依赖。如,如果你的app有一个在默认进程里的Content Provider,这个进程同时也处理UI,后台进程里的代码要访问Content Provider就需要UI进程也留在内存里。如果你的目标是让后台进程可以独立于重量级的UI进程,它就不能依赖UI进程里的Content Provider或Service.

在Android 3.0中,新引入了除补间动画Tween Animation、帧动画Frame Animation以外的第三种动画,属性动画Property Animation就是ValueAnimator类.特点:ValueAnimator通过改变对象的属性值来实现界面的改变,而其他动画,只是界面显示上的改变,动画结束后,你会发现,即使view已经运动到别的位置,但click事件还是在原来的地方,ValueAnimator不会有这个问题。ValueAnimator有两个子类,一个是TimeAnimator,一个是ObjectAnimator。 TimeAnimator在api 16才引入,它并不能直接实现动画效果,而是在TimeListener里返回动画持续的时间,与上次调用的间隔时间,要怎么改变view,需要自己操作。 ObjectAnimator可以直接改变对象的属性值,比如,我们可以通过改变ProgressBar的progress属性,实现进度的改变.

                ObjectAnimator objectAnimator=ObjectAnimator.ofInt(progressBar,"progress",1,100);
                objectAnimator.setDuration(10000);
                objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
                objectAnimator.start();

                //api 16 android 4.1以上版本才有
                TimeAnimator timeAnimator=new TimeAnimator();
                timeAnimator.setTimeListener(new TimeAnimator.TimeListener() {
                    @Override
                    public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
                        System.out.println(totalTime+"  "+deltaTime);
                    }
                });
                timeAnimator.start();;

GPU过度绘制指的是在屏幕一个像素上绘制多次(超过一次),比如一个TextView后有背景,那么显示文本的像素至少绘了两次,一次是背景,一次是文本。GPU过度绘制或多或少对性能有些影响。 如何查看是否过度绘制: 设置-开发者选项-调试GPU过度绘制(过度渲染等,不同机器可能不同) 开启后,启动我们的应用,可以看到各种颜色的区域,其中: 蓝色 1x过度绘制 绿色 2x过度绘制 淡红色 3x过度绘制 红色 超过4x过度绘制 最理想的是蓝色,一个像素只绘制一次。这里需要说明的是,如果你的Activity使用了自定义的Theme,在Theme里设了背景,这个不算第一层,但如果在xml的根节点加background,那要算一层. 写了个demo做测试,Activity里除了onCreate里setContentView以外,没有其他逻辑代码。 gpu 最深的是TextView和ImageView,位于第4层,几个外层的LinearLayout都设了背景(不设就是透明,像素不会重绘),效果如下图: device-2014-05-18-140514 可以看到,位于第三层的LinearLayout已经是淡红色,位于第四层的TextView和ImageView呈红色。 如何优化? 1、优化布局,减少层级。 2、减少没必要的背景。 还不行,给Activity定义一个Theme,通过theme定义背景,这样可以减少一层。 如果应用了以上几种方法,还是一片红,好吧,你该考虑换UI设计了.

翻自http://developer.android.com/training/backward-compatible-ui/older-implementation.html 现在你已经有了两个TabHelper 和CompatTab的实现,一个针对Android 3.0以上版本,一个针对老版本。这节课讨论创建两个实现切换的逻辑,创建版本相关的布局,最终使用UI向后兼容组件。 添加切换逻辑 TabHelper抽象类扮演了一个创建合适版本的TabHelper和instances的工厂,基本当前设备平台版本。

public abstract class TabHelper {
    ...
    // Usage is TabHelper.createInstance(activity)
    public static TabHelper createInstance(FragmentActivity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            return new TabHelperHoneycomb(activity);
        } else {
            return new TabHelperEclair(activity);
        }
    }

    // Usage is mTabHelper.newTab("tag")
    public CompatTab newTab(String tag) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            return new CompatTabHoneycomb(mActivity, tag);
        } else {
            return new CompatTabEclair(mActivity, tag);
        }
    }
    ...
}

创建版本相关的Activity布局 下一步是给Activity提供布局来支持两个标签实现.针对老平台实现(TabHelperEclair),你需要确保你的Activity布局包含一个TabWidget和TabHost: res/layout/main.xml:

<!-- This layout is for API level 5-10 only. -->
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp">

        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />

    </LinearLayout>
</TabHost>

在TabHelperHoneycomb的实现中,你所需要的只是一个FrameLayout用来放内容,因为ActionBar提供了指示器 res/layout-v11/main.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabcontent"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

在运行时,Android会根据平台选择哪个版本的main.xml.这跟上一节说的决定用哪个TabHelper实现是一样的逻辑。 在Activity中使用TabHelper 在Activity里的onCreate()方法,你可以通过以下代码得到一个TabHelper对象,添加Tab

@Override
public void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.main);

    TabHelper tabHelper = TabHelper.createInstance(this);
    tabHelper.setUp();

    CompatTab photosTab = tabHelper
            .newTab("photos")
            .setText(R.string.tab_photos);
    tabHelper.addTab(photosTab);

    CompatTab videosTab = tabHelper
            .newTab("videos")
            .setText(R.string.tab_videos);
    tabHelper.addTab(videosTab);
}

当运行应用时,代码会载入正确的Activity布局,初始化TabHelperHoneycomb或TabHelperEclair对象。实际使用的类对Activity是不透明的,因为他们共享通用的TabHelper接口。 下面是两种实现的截图:

翻自http://developer.android.com/training/backward-compatible-ui/older-implementation.html 本课讨论如何创建一个反应新的API,还支持旧设备的实现。 选定一个替代方案 在使用新的UI特性又要保持向后兼容时,最大的挑战是选定旧平台上的实现。很多情况下,可以满足使用旧的框架特性,而使用新UI组件的目的。如: ActionBar可以用一个水平的LinearLayout,包含图片按钮,自定义的标题栏,或者作为一个Activity布局的视图。Overflow actions(右上角三个点,点击弹菜单)可以通过设备的菜单按钮实现。 ActionBar标签可以使用水平LinearLayout包含按钮来实现,或者使用TabWidget NumberPicker和Switch widgets可以用Spinner和ToggleButton分别实现。 ListPopupWindow和PopupMenu可以用PopupWindow实现 用新UI适配老设备,没有通用的方案。考虑到用户体验:在老设备上,用户可能对新设计模式和UI组件不熟悉。多想想如何用用户熟悉的元素跳到相同的功能。很多情况下,这样做需要关注更少(如果新的UI组件在应用生态系统中是突出的,像ActionBar,或者交互模型是简单直观的,像ViewPager滑动page) 使用旧API实现Action Bar Tab,你可以用TabWidget和TabHost(虽然也可以使用水平布局的按钮).在命名为TabHelperEclair和CompatTabEclair的类中实现,因为这个实现用了Android 2.0(Eclair)中引用的api. Eclair实现类图: CompatTabEclair实现保存tab属性,像实例中的tab的文本和图标,因为没有ActionBar.Tab做保存.

public class CompatTabEclair extends CompatTab {
    // Store these properties in the instance,
    // as there is no ActionBar.Tab object.
    private CharSequence mText;
    ...

    public CompatTab setText(int resId) {
        // Our older implementation simply stores this
        // information in the object instance.
        mText = mActivity.getResources().getText(resId);
        return this;
    }

    ...
    // Do the same for other properties (icon, callback, etc.)
}

TabHelperEclair实现用了TabHost 来创建 TabHost.TabSpec对象和指示器

public class TabHelperEclair extends TabHelper {
    private TabHost mTabHost;
    ...

    protected void setUp() {
        if (mTabHost == null) {
            // Our activity layout for pre-Honeycomb devices
            // must contain a TabHost.
            mTabHost = (TabHost) mActivity.findViewById(
                    android.R.id.tabhost);
            mTabHost.setup();
        }
    }

    public void addTab(CompatTab tab) {
        ...
        TabSpec spec = mTabHost
                .newTabSpec(tag)
                .setIndicator(tab.getText()); // And optional icon
        ...
        mTabHost.addTab(spec);
    }

    // The other important method, newTab() is part of
    // the base implementation.
}

现在,你有了两个 CompatTab and TabHelper的实现,一个运行在是Android 3.0以上版本,用了新的API,另一个工作在Android 2.0以上版本,用了旧的API.下一节讨论在你的应用中使用这些实现.

代理新API 翻自http://developer.android.com/training/backward-compatible-ui/new-implementation.html#compattabhoneycomb 本课向你展示了如何继承CompatTab和TabHelper抽象类,并且使用新API,你的应用可以在支持他们的平台上使用这个实现。 用新的API实现Tab 用新API实现CompatTab和TabHelper的实现类是代理实现。前面介绍了定义抽象类,复制新API(类结构,方法等),实现类简单地代理使用新API,并返回他们的结果. 你可以在这些实现类中直接使用新API,并且在早期版本中不会Crash,因为懒加载。类在第一次访问实例化类或一个静态字段或方法时被载入和初始化。因此,只要你不在Honeycomb之前的版本实例化Honeycomb专有的实现,Dalvik虚拟机不会有任何验证错误异常. 一个良好的实现命名约定是加上API版本或者平台版本。比如,本地tab实现可以命名为CompatTabHoneycomb和TabHelperHoneycomb,因为他们依赖于Android 3.0或以上版本 实现CompatTabHoneycomb CompatTabHoneycomb是CompatTab抽象类的一个实现,TabHelperHoneycomb用它来引用独立标签。CompatTabHoneycomb简单地代理了所有方法调用给它代理的ActionBar.Tab对象

public class CompatTabHoneycomb extends CompatTab {
    // The native tab object that this CompatTab acts as a proxy for.
    ActionBar.Tab mTab;
    ...

    protected CompatTabHoneycomb(FragmentActivity activity, String tag) {
        ...
        // Proxy to new ActionBar.newTab API
        mTab = activity.getActionBar().newTab();
    }

    public CompatTab setText(int resId) {
        // Proxy to new ActionBar.Tab.setText API
        mTab.setText(resId);
        return this;
    }

    ...
    // Do the same for other properties (icon, callback, etc.)
}

实现TabHelperHoneycomb TabHelperHoneycomb是TabHelper的实现,用来代理方法调用到一个从Activity上获取的ActionBar上.

public class TabHelperHoneycomb extends TabHelper {
    ActionBar mActionBar;
    ...

    protected void setUp() {
        if (mActionBar == null) {
            mActionBar = mActivity.getActionBar();
            mActionBar.setNavigationMode(
                    ActionBar.NAVIGATION_MODE_TABS);
        }
    }

    public void addTab(CompatTab tab) {
        ...
        // Tab is a CompatTabHoneycomb instance, so its
        // native tab object is an ActionBar.Tab.
        mActionBar.addTab((ActionBar.Tab) tab.getTab());
    }

    // The other important method, newTab() is part of
    // the base implementation.
}

翻自http://developer.android.com/training/backward-compatible-ui/abstracting.html 假设你要在应用中使用Action Bar Tab作为主要的导航。不幸的是,ActionBar api只有Android 3.0以上版本才有(API Level 11+).因此,如果你想让应用运行在低版本的平台上,你需要提供新API的实现,同时提供旧API的回退机制。 本课中,你会构建一个标签式UI组件,使用特定版本实现的抽象类来提供向后兼容。这节课讲述了如何为新的标签API创建抽象实现层作为创建标签组件的第一步。 准备抽象 抽象在Java编程语言中包含了创建一个或多个接口或抽象类来隐藏具体实现。在新Android API的案例中,你可以使用抽象构建版本相关的组件,在新设备中使用当前的API,旧设备中回退到旧的,更兼容的api. 当使用这个方法,你首先确定用向后兼容的方式使用哪些新的类,然后创建抽象类,基于新类的public接口。在定义抽象接口时,你应该尽可能地复制新的api。这样做可以最大化向前兼容,并且在未来不需要它的时候,可以更容易的抛弃抽象层。 在为这些新api创建抽象类以后,可以创建任意数量的实现,并且在运行时选择。为了向后兼容的目的,这些实现可以根据api level不同而不同,因此,一个实现可以使用最新的api,其他的可以用旧的. 创建抽象Tab接口 为了创建向后兼容的tab,你应该先确定你的应用需要哪些特性和特别的api。在顶层选项标签中,假设你有以下功能需求: 标签指示器应该显示文本和图标 标签可以与fragment实例关联 activity应该可以监听标签改变 提前准备这些需求允许你控制抽象层的范围。这意味着你可以花更少的时间在创建多个抽象层实现上,更快使用向后兼容的实现。 标签的关键api在ActionBar和ActionBar.Tab里。为了让你的标签与版本相关,这些是需要抽象的api。案例项目中的需求需要Honeycomb(API Level 11)中的标签特性兼容到Eclair(API Level 5).类结构图和两个抽象基类实现如下: 抽象ActionBar.Tab 通过创建一个抽象类展示一个tab,复制ActionBar.Tab接口构建tab抽象层

public abstract class CompatTab {
    ...
    public abstract CompatTab setText(int resId);
    public abstract CompatTab setIcon(int resId);
    public abstract CompatTab setTabListener(
            CompatTabListener callback);
    public abstract CompatTab setFragment(Fragment fragment);

    public abstract CharSequence getText();
    public abstract Drawable getIcon();
    public abstract CompatTabListener getCallback();
    public abstract Fragment getFragment();
    ...
}

你可以使用一个抽象类代替接口,简化常用的特性(像把tab和activity关联)实现 抽象ActionBar Tab方法 下一步,定义一个允许你在Activity中创建和添加标签的抽象类,就像ActionBar.newTab()和ActionBar.addTab():

public abstract class TabHelper {
    ...

    public CompatTab newTab(String tag) {
        // This method is implemented in a later lesson.
    }

    public abstract void addTab(CompatTab tab);

    ...
}

在下一节中,创建在新旧平台中工作的TabHelper和CompatTab实现

翻自:http://developer.android.com/training/backward-compatible-ui/index.html 本课示范了如何用向后兼容的方式使用UI组件和Android新版本的API,确保你的应用仍能在老版本的平台上运行。 在课程中,使用了Android 3.0(API Level 11)中引入的新特性 Action Bar Tab,但你可以应用这些技术到其他UI组件和API上. 课程: 抽象新的API 决定你的应用需要哪些特性和API.学会如何定义程序特征,剥离Java接口,抽象实现应用的UI组件。 代理新API 学会如何使用新的API创建你的接口实现。 为老API创建实现 学会如何使用老的API创建接口的自定义实现 使用版本相关的组件 学会如何在运行时选择实现,开始在应用中使用接口

翻自:http://developer.android.com/training/custom-views/optimizing-view.html 现在,你已经有一个良好设计的视图,可以响应手势,在两个状态间平滑切换.你要确保这个视图跑得快。为了避免感觉到卡,你应该确保动画能保持在每秒60帧。 少做,别太频繁 为了加速你的视图,你要消除在运行时频繁调用的不必要代码。先从onDraw()方法开始,它会给你最大的回报。特别是你应该避免在onDraw里new对象,因为内存分配会引发垃圾回收导致卡顿。应该在初始化时创建对象,或在动画间。永远不要在动画运行时创建。 除了精简onDraw(),你应该确保它尽可能地少调用。大多数onDraw()调用是调用invalidate()的结果。所以,消除不必要的invalidate()调用.如果有可能,调用四个参数的invalidate()要比没有参数的好。没参数的invalidate()会让整个View失效,四个参数 的invalidate()只会让指定的部分失效.这个方法能让绘图更高效,消除不必要的重绘.如果发现测量有冲突, 另一个成本高昂的操作是遍历布局.任何时候,一个视图调用requestLayout(),Android UI系统需要遍历整个视图层次,确定每个视图需要多大。如果发现测量有冲突,还可能需要遍历多次。UI设计者有时为了让UI正确的表现,创建了深层嵌套的ViewGroup对象,这些深层视图导致性能问题。UI层次越浅越好. 如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup去执行他的布局.不像内建的视图,你的自定义视图可以专为这个程序假定子控件的尺寸和形状.以此避免子控件的遍历计算.PieChart案例展示了如何继承ViewGroup作为自定义视图的一部分。PieChart有子视图,但是从不测量他们,而是直接通过它自己自定义布局算法设置他们的大小 使用硬件加速 从Android 3.0开始,Android 2D图形系统可以使用GPU硬件加速。对很多应用来说,开启GPU加速会有巨大的性能提升,但不是每个程序都适用。Android框架让你可以精确控制哪些部分开启硬件加速,而哪些不要。 可参考Android Developers Guide中硬件加速的部分,指引你如何加速Application,Activity,或者Window级别。注意,你必须把你的应用target API设到11以上,在AndroidManifest.xml中,加上。 一旦你开启硬件加速,你或者看不到性能的提升。移动GPU擅长于某些的任务,像缩放,旋转,转换位图。它们不擅长其他像画直线或曲线的任务。为了充分利用GPU加速,你应该让GPU擅长的操作最大化,让它不擅长的操作最小化。 在PieChart案例中,画饼是相对耗资源的,每次旋转都重绘饼导致UI卡顿。解决方案是把饼图放到一个子视图里,并设置视图的layer type为LAYER_TYPE_HARDWARE,GPU就可以把它当成一张静态图片缓存它.例子把子视图定义成了一个PieChart的内部类,这是实现这个方案所需修改代码最少的办法。

private class PieView extends View {

       public PieView(Context context) {
           super(context);
           if (!isInEditMode()) {
               setLayerType(View.LAYER_TYPE_HARDWARE, null);
           }
       }
       
       @Override
       protected void onDraw(Canvas canvas) {
           super.onDraw(canvas);

           for (Item it : mData) {
               mPiePaint.setShader(it.mShader);
               canvas.drawArc(mBounds,
                       360 - it.mEndAngle,
                       it.mEndAngle - it.mStartAngle,
                       true, mPiePaint);
           }
       }

       @Override
       protected void onSizeChanged(int w, int h, int oldw, int oldh) {
           mBounds = new RectF(0, 0, w, h);
       }

       RectF mBounds;
   }

改了这些代码后,PieChart.PieView.onDraw()只在第一次显示时调用,在application生命周期的其他时候,饼图被当做图片缓存,通过CPU重绘不同角度的旋转.GPU擅长做这些事,性能差异立即明显。 不过,有一个权衡。硬件层缓存图片会消耗显存,这个资源是有限的。因此,最终版本的 PieChart.PieView 只在用户滚动的时候设置了LAYER_TYPE_HARDWARE,其他时候,设为LAYER_TYPE_NONE,允许GPU停止缓存图片. 最后,别忘了性能分析。同样的技术,在一个视图上可以提升性能,但对另一个可能会有反效果。