> 编写:[kesenhoo](https://github.com/kesenhoo) - 原文:[http://developer.android.com/training/articles/memory.html](http://developer.android.com/training/articles/memory.html)
Random Access Memory(RAM)在任何软件开发环境中都是一个很宝贵的资源。这一点在物理内存通常很有限的移动操作系统上,显得尤为突出。尽管Android的Dalvik虚拟机扮演了常规的垃圾回收的角色,但这并不意味着你可以忽视app的内存分配与释放的时机与地点。
为了GC能够从你的app中及时回收内存,你需要避免内存泄露(通常由于在全局成员变量中持有对象引用而导致)并且在适当的时机(下面会讲到的lifecycle callbacks)来释放引用对象。对于大多数apps来说,Dalvik的GC会自动把离开活动线程的对象进行回收。
这篇文章会解释Android是如何管理app的进程与内存分配,以及在开发Android应用的时候如何主动的减少内存的使用。关于Java的资源管理机制,请参考其它书籍或者线上材料。如果你正在寻找如何分析你的内存使用情况的文章,请参考这里[Investigating Your RAM Usage](http://developer.android.com/tools/debugging/debugging-memory.html)。
### 第1部分: Android是如何管理内存的
Android并没有提供内存的交换区(Swap space),但是它有使用[paging](http://en.wikipedia.org/wiki/Paging)与[memory-mapping(mmapping)](http://en.wikipedia.org/wiki/Memory-mapped_files)的机制来管理内存。这意味着任何你修改的内存(无论是通过分配新的对象还是访问到mmaped pages的内容)都会贮存在RAM中,而且不能被paged out。因此唯一完整释放内存的方法是释放那些你可能hold住的对象的引用,这样使得它能够被GC回收。只有一种例外是:如果系统想要在其他地方reuse这个对象。
### 1) 共享内存
Android通过下面几个方式在不同的Process中来共享RAM:
- 每一个app的process都是从同一个被叫做**Zygote**的进程中fork出来的。Zygote进程在系统启动并且载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的进程,然后在新的进程中加载并运行app的代码。这使得大多数的RAM pages被用来分配给framework的代码与资源,并在应用的所有进程中进行共享。
- 大多数static的数据被mmapped到一个进程中。这不仅仅使得同样的数据能够在进程间进行共享,而且使得它能够在需要的时候被paged out。例如下面几种static的数据:
- Dalvik 代码 (放在一个已经链接好的 .odex file 用于直接内存映射查找)
- App resources (一个资源表结构,可以被内存映射,以及在APK文件中做zip aligning)
- 传统项目元素,比如 .so 文件中的本地代码.
- 在许多地方,Android通过显式的分配共享内存区域(例如ashmem或者gralloc)来实现一些动态RAM区域的能够在不同进程间的共享。例如,window surfaces在app与screen compositor之间使用共享的内存,cursor buffers在content provider与client之间使用共享的内存。
关于如何查看app所使用的共享内存,请查看[Investigating Your RAM Usage](http://developer.android.com/tools/debugging/debugging-memory.html)
### 2) 分配与回收内存
这里有下面几点关于Android如何分配与回收内存的事实:
- 每一个进程的Dalvik heap都有一个限制的虚拟内存范围。这就是逻辑上讲的heap size,它可以随着需要进行增长,但是会有一个系统为它所定义的上限。
- 逻辑上讲的heap size和实际物理上使用的内存数量是不等的,Android会计算一个叫做Proportional Set Size(PSS)的值,它记录了那些和其他进程进行共享的内存大小。(假设共享内存大小是10M,一共有20个Process在共享使用,根据权重,可能认为其中有0.3M才能真正算是你的进程所使用的)
- Dalvik heap与逻辑上的heap size不吻合,这意味着Android并不会去做heap中的碎片整理用来关闭空闲区域。Android仅仅会在heap的尾端出现不使用的空间时才会做收缩逻辑heap size大小的动作。但是这并不是意味着被heap所使用的物理内存大小不能被收缩。在垃圾回收之后,Dalvik会遍历heap并找出不使用的pages,然后使用madvise(系统调用)把那些pages返回给kernal。因此,成对的allocations与deallocations大块的数据可以使得物理内存能够被正常的回收。然而,回收碎片化的内存则会使得效率低下很多,因为那些碎片化的分配页面也许会被其他地方所共享到。
### 3) 限制应用的内存
为了维持多任务的功能环境,Android为每一个app都设置了一个硬性的heap size限制。准确的heap size限制随着不同设备的不同RAM大小而各有差异。如果你的app已经到了heap的限制大小并且再尝试分配内存的话,会引起`OutOfMemoryError`的错误。
在一些情况下,你也许想要查询当前设备的heap size限制大小是多少,然后决定cache的大小。可以通过`getMemoryClass()`来查询。这个方法会返回一个整数,表明你的app heap size限制是多少Mb(megabates)。
### 4) 切换应用
Android并不会在用户切换不同应用时候做交换内存的操作。Android会把那些不包含foreground组件的进程放到LRU cache中。例如,当用户刚开始启动了一个应用,这个时候为它创建了一个进程,但是当用户离开这个应用,这个进程并没有离开。系统会把这个进程放到cache中,如果用户后来回到这个应用,这个进程能够被resued,从而实现app的快速切换。
如果你的应用有一个当前并不需要使用到的被缓存的进程,它被保留在内存中,这会对系统的整个性能有影响。因此当系统开始进入低内存状态时,它会由系统根据LRU的规则与其他因素选择杀掉某些进程,为了保持你的进程能够尽可能长久的被cached,请参考下面的章节学习何时释放你的引用。
对于不在foreground的进程是Android是如何决定kill掉哪一类进程的问题,请参考[Processes and Threads](http://developer.android.com/guide/components/processes-and-threads.html).
### 第2部分: 你的应用该如何管理内存
你应该在开发过程的每一个阶段都考虑到RAM的有限性,甚至包括在开发开始之前的设计阶段就应该开始考虑RAM的限制。我们可以有许多种设计与实现方式,他们有着不同的效率,即使这些方式是对同样一种技术的不断组合与演变。
为了使得你的应用效率更高,你应该在设计与实现代码时,遵循下面的技术要点。
### 1) 珍惜Services资源
如果你的app需要在后台使用service,除非它被触发执行一个任务,否则其他时候都应该是非运行状态。同样需要注意当这个service已经完成任务后停止service失败引起的泄漏。
当你启动一个service,系统会倾向为了这个Service而一直保留它的进程。这使得进程的运行代价很高,因为系统没有办法把Service所占用的RAM让给其他组件或者被paged out。这减少了系统能够存放到LRU缓存当中的进程数量,它会影响app之间的切换效率。它甚至会导致系统内存使用不稳定,从而无法继续hold住所有目前正在运行的Service。
限制你的Service的最好办法是使用[IntentService](http://developer.android.com/reference/android/app/IntentService.html), 它会在处理完扔给它的intent任务之后尽快结束自己。更多信息,请阅读[Running in a Background Service](http://developer.android.com/training/run-background-service/index.html).
当一个Service已经不需要的时候还继续保留它,这对Android应用的内存管理来说是**最糟糕的错误之一**。因此千万不要贪婪的使得一个Service持续保留。不仅仅是因为它会使得你的app因RAM的限制而性能糟糕,而且用户会发现那些有着常驻后台行为的app并且卸载它。
### 2) 你的UI隐藏时释放内存
当用户切换到其它app并且你的app UI不再可见时,你应该释放你的UI上占用的任何资源。在这个时候释放UI资源可以显著的增加系统cached process的能力,它会对用户体验有着直接的影响。
为了能够接收到用户离开你的UI时的通知,你需要实现Activtiy类里面的[onTrimMemory()](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#onTrimMemory(int))回调方法。你应该使用这个方法来监听到[TRIM_MEMORY_UI_HIDDEN](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_UI_HIDDEN)级别, 它意味着你的UI已经隐藏,你应该释放那些仅仅被你的UI使用的资源。
请注意:你的app仅仅会在所有UI组件的被隐藏的时候接收到onTrimMemory()的回调并带有参数`TRIM_MEMORY_UI_HIDDEN`。这与onStop()的回调是不同的,onStop会在[activity](# "An activity represents a single screen with a user interface.")的实例隐藏时会执行,例如当用户从你的app的某个[activity](# "An activity represents a single screen with a user interface.")跳转到另外一个[activity](# "An activity represents a single screen with a user interface.")时onStop会被执行。因此你应该实现onStop回调,并且在此回调里面释放[activity](# "An activity represents a single screen with a user interface.")的资源,例如网络连接,unregister广播接收者。除非接收到[onTrimMemory(TRIM_MEMORY_UI_HIDDEN)](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#onTrimMemory(int))的回调,否者你不应该释放你的UI资源。这确保了用户从其他[activity](# "An activity represents a single screen with a user interface.")切回来时,你的UI资源仍然可用,并且可以迅速恢复[activity](# "An activity represents a single screen with a user interface.")。
### 3) 当内存紧张时释放部分内存
在你的app生命周期的任何阶段,onTrimMemory回调方法同样可以告诉你整个设备的内存资源已经开始紧张。你应该根据onTrimMemory方法中的内存级别来进一步决定释放哪些资源。
- [TRIM_MEMORY_RUNNING_MODERATE](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_RUNNING_MODERATE):你的app正在运行并且不会被列为可杀死的。但是设备此时正运行于低内存状态下,系统开始触发杀死LRU Cache中的Process的机制。
- [TRIM_MEMORY_RUNNING_LOW](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_RUNNING_LOW):你的app正在运行且没有被列为可杀死的。但是设备正运行于更低内存的状态下,你应该释放不用的资源用来提升系统性能(但是这也会直接影响到你的app的性能)。
- [TRIM_MEMORY_RUNNING_CRITICAL](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_RUNNING_CRITICAL):你的app仍在运行,但是系统已经把LRU Cache中的大多数进程都已经杀死,因此你应该立即释放所有非必须的资源。如果系统不能回收到足够的RAM数量,系统将会清除所有的LRU缓存中的进程,并且开始杀死那些之前被认为不应该杀死的进程,例如那个包含了一个运行态Service的进程。
同样,当你的app进程正在被cached时,你可能会接受到从onTrimMemory()中返回的下面的值之一:
- [TRIM_MEMORY_BACKGROUND](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_BACKGROUND): 系统正运行于低内存状态并且你的进程正处于LRU缓存名单中**最不容易杀掉的位置**。尽管你的app进程并不是处于被杀掉的高危险状态,系统可能已经开始杀掉LRU缓存中的其他进程了。你应该释放那些容易恢复的资源,以便于你的进程可以保留下来,这样当用户回退到你的app的时候才能够迅速恢复。
- [TRIM_MEMORY_MODERATE](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_MODERATE): 系统正运行于低内存状态并且你的进程已经已经接近LRU名单的**中部位置**。如果系统开始变得更加内存紧张,你的进程是有可能被杀死的。
- [TRIM_MEMORY_COMPLETE](http://developer.android.com/reference/android/content/ComponentCallbacks2.html#TRIM_MEMORY_COMPLETE): 系统正运行与低内存的状态并且你的进程正处于LRU名单中**最容易被杀掉的位置**。你应该释放任何不影响你的app恢复状态的资源。
因为onTrimMemory()的回调是在**API 14**才被加进来的,对于老的版本,你可以使用[onLowMemory](http://developer.android.com/reference/android/content/ComponentCallbacks.html#onLowMemory())回调来进行兼容。onLowMemory相当与TRIM_MEMORY_COMPLETE。
**Note:** 当系统开始清除LRU缓存中的进程时,尽管它首先按照LRU的顺序来操作,但是它同样会考虑进程的内存使用量。因此消耗越少的进程则越容易被留下来。
### 4) 检查你应该使用多少的内存
正如前面提到的,每一个Android设备都会有不同的RAM总大小与可用空间,因此不同设备为app提供了不同大小的heap限制。你可以通过调用[getMemoryClass()](http://developer.android.com/reference/android/app/ActivityManager.html#getMemoryClass())来获取你的app的可用heap大小。如果你的app尝试申请更多的内存,会出现OutOfMemory的错误。
在一些特殊的情景下,你可以通过在manifest的application标签下添加`largeHeap=true`的属性来声明一个更大的heap空间。如果你这样做,你可以通过[getLargeMemoryClass()](http://developer.android.com/reference/android/app/ActivityManager.html#getLargeMemoryClass())来获取到一个更大的heap size。
然而,能够获取更大heap的设计本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。**不要轻易的因为你需要使用大量的内存而去请求一个大的heap size。**只有当你清楚的知道哪里会使用大量的内存并且为什么这些内存必须被保留时才去使用large heap. 因此请尽量少使用large heap。使用额外的内存会影响系统整体的用户体验,并且会使得GC的每次运行时间更长。在任务切换时,系统的性能会变得大打折扣。
另外, large heap并不一定能够获取到更大的heap。在某些有严格限制的机器上,large heap的大小和通常的heap size是一样的。因此即使你申请了large heap,你还是应该通过执行getMemoryClass()来检查实际获取到的heap大小。
### 5) 避免bitmaps的浪费
当你加载一个bitmap时,仅仅需要保留适配当前屏幕设备分辨率的数据即可,如果原图高于你的设备分辨率,需要做缩小的动作。请记住,增加bitmap的尺寸会对内存呈现出2次方的增加,因为X与Y都在增加。
**Note:**在Android 2.3.x (API level 10)及其以下, bitmap对象的pixel data是存放在native内存中的,它不便于调试。然而,从Android 3.0(API level 11)开始,bitmap pixel data是分配在你的app的Dalvik heap中, 这提升了GC的工作效率并且更加容易Debug。因此如果你的app使用bitmap并在旧的机器上引发了一些内存问题,切换到3.0以上的机器上进行Debug。
### 6) 使用优化的数据容器
利用Android Framework里面优化过的容器类,例如[SparseArray](http://developer.android.com/reference/android/util/SparseArray.html), [SparseBooleanArray](http://developer.android.com/reference/android/util/SparseBooleanArray.html), 与 [LongSparseArray](http://developer.android.com/reference/android/support/v4/util/LongSparseArray.html)。 通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。
### 7) 请注意内存开销
对你所使用的语言与库的成本与开销有所了解,从开始到结束,在设计你的app时谨记这些信息。通常,表面上看起来无关痛痒(innocuous)的事情也许实际上会导致大量的开销。例如:
- Enums的内存消耗通常是static constants的2倍。你应该尽量避免在Android上使用enums。
- 在Java中的每一个类(包括匿名内部类)都会使用大概500 bytes。
- 每一个类的实例花销是12-16 bytes。
- 往HashMap添加一个entry需要额一个额外占用的32 bytes的entry对象。
### 8) 请注意代码“抽象”
通常,开发者使用抽象作为"好的编程实践",因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的开销:通常他们需要同等量的代码用于可执行。那些代码会被map到内存中。因此如果你的抽象没有显著的提升效率,应该尽量避免他们。
### 9) 为序列化的数据使用nano protobufs
[Protocol buffers](https://developers.google.com/protocol-buffers/docs/overview)是由Google为序列化结构数据而设计的,一种语言无关,平台无关,具有良好扩展性的协议。类似XML,却比XML更加轻量,快速,简单。如果你需要为你的数据实现协议化,你应该在客户端的代码中总是使用nano protobufs。通常的协议化操作会生成大量繁琐的代码,这容易给你的app带来许多问题:增加RAM的使用量,显著增加APK的大小,更慢的执行速度,更容易达到DEX的字符限制。
关于更多细节,请参考[protobuf readme](https://android.googlesource.com/platform/external/protobuf/+/master/java/README.txt)的"Nano version"章节。
### 10) 避免使用依赖注入框架
使用类似[Guice](https://code.google.com/p/google-guice/)或者[RoboGuice](https://github.com/roboguice/roboguice)等framework injection包是很有效的,因为他们能够简化你的代码。
> RoboGuice 2 通过依赖注入改变代码风格,让Android开发时的体验更好。你在调用 `getIntent().getExtras()` 时经常忘记检查 null 吗?RoboGuice 2 可以帮你做。你认为将 `findViewById()` 的返回值强制转换成 TextView 是本不必要的工作吗? RoboGuice 2 会帮你。RoboGuice 把这些需要猜测性的工作移到Android 开发以外去了。注入你的 View, Resource, System Service,或者其他对象,RoboGuice 2 会负责这些细节。
然而,那些框架会通过扫描你的代码执行许多初始化的操作,这会导致你的代码需要大量的RAM来map代码。但是mapped pages会长时间的被保留在RAM中。
### 11) 谨慎使用external libraries
很多External library的代码都不是为移动网络环境而编写的,在移动客户端则显示的效率不高。至少,当你决定使用一个external library的时候,你应该针对移动网络做繁琐的porting与maintenance的工作。
即使是针对Android而设计的library,也可能是很危险的,因为每一个library所做的事情都是不一样的。例如,其中一个lib使用的是nano protobufs, 而另外一个使用的是micro protobufs。那么这样,在你的app里面就有2种protobuf的实现方式。这样的冲突同样可能发生在输出日志,加载图片,缓存等等模块里面。
同样不要陷入为了1个或者2个功能而导入整个library的陷阱。如果没有一个合适的库与你的需求相吻合,你应该考虑自己去实现,而不是导入一个大而全的解决方案。
### 12) 优化整体性能
官方有列出许多优化整个app性能的文章:[Best Practices for Performance](http://developer.android.com/training/best-performance.html). 这篇文章就是其中之一。有些文章是讲解如何优化app的CPU使用效率,有些是如何优化app的内存使用效率。
你还应该阅读[optimizing your UI](http://developer.android.com/tools/debugging/debugging-ui.html)来为layout进行优化。同样还应该关注lint工具所提出的建议,进行优化。
### 13) 使用ProGuard来剔除不需要的代码
[ProGuard](http://developer.android.com/tools/help/proguard.html)能够通过移除不需要的代码,重命名类,域与方法等方对代码进行压缩,优化与混淆。使用ProGuard可以是的你的代码更加紧凑,这样能够使用更少mapped代码所需要的RAM。
### 14) 对最终的APK使用zipalign
在编写完所有代码,并通过编译系统生成APK之后,你需要使用[zipalign](http://developer.android.com/tools/help/zipalign.html)对APK进行重新校准。如果你不做这个步骤,会导致你的APK需要更多的RAM,因为一些类似图片资源的东西不能被mapped。
**注意: **Google Play不接受没有经过zipalign的APK。
### 15) 分析你的RAM使用情况
一旦你获取到一个相对稳定的版本后,需要分析你的app整个生命周期内使用的内存情况,并进行优化,更多细节请参考[Investigating Your RAM Usage](http://developer.android.com/tools/debugging/debugging-memory.html).
### 16) 使用多进程
如果合适的话,有一个更高级的技术可以帮助你的app管理内存使用:通过把你的app组件切分成多个组件,运行在不同的进程中。这个技术必须谨慎使用,大多数app都不应该运行在多个进程中。因为如果使用不当,它会显著增加内存的使用,而不是减少。当你的app需要在后台运行与前台一样的大量的任务的时候,可以考虑使用这个技术。
一个典型的例子是创建一个可以长时间后台播放的Music Player。如果整个app运行在一个进程中,当后台播放的时候,前台的那些UI资源也没有办法得到释放。类似这样的app可以切分成2个进程:一个用来操作UI,另外一个用来后台的Service.
你可以通过在manifest文件中声明'android:process'属性来实现某个组件运行在另外一个进程的操作。
~~~
<service android:name=".PlaybackService"
android:process=":background" />
~~~
更多关于使用这个技术的细节,请参考原文,链接如下。[http://developer.android.com/training/articles/memory.html](http://developer.android.com/training/articles/memory.html)
- 序言
- Android入门基础:从这里开始
- 建立第一个App
- 创建Android项目
- 执行Android程序
- 建立简单的用户界面
- 启动其他的Activity
- 添加ActionBar
- 建立ActionBar
- 添加Action按钮
- 自定义ActionBar的风格
- ActionBar的覆盖层叠
- 兼容不同的设备
- 适配不同的语言
- 适配不同的屏幕
- 适配不同的系统版本
- 管理Activity的生命周期
- 启动与销毁Activity
- 暂停与恢复Activity
- 停止与重启Activity
- 重新创建Activity
- 使用Fragment建立动态的UI
- 创建一个Fragment
- 建立灵活动态的UI
- Fragments之间的交互
- 数据保存
- 保存到Preference
- 保存到文件
- 保存到数据库
- 与其他应用的交互
- Intent的发送
- 接收Activity返回的结果
- Intent过滤
- Android分享操作
- 分享简单的数据
- 给其他App发送简单的数据
- 接收从其他App返回的数据
- 给ActionBar增加分享功能
- 分享文件
- 建立文件分享
- 分享文件
- 请求分享一个文件
- 获取文件信息
- 使用NFC分享文件
- 发送文件给其他设备
- 接收其他设备的文件
- Android多媒体
- 管理音频播放
- 控制音量与音频播放
- 管理音频焦点
- 兼容音频输出设备
- 拍照
- 简单的拍照
- 简单的录像
- 控制相机硬件
- 打印
- 打印照片
- 打印HTML文档
- 打印自定义文档
- Android图像与动画
- 高效显示Bitmap
- 高效加载大图
- 非UI线程处理Bitmap
- 缓存Bitmap
- 管理Bitmap的内存
- 在UI上显示Bitmap
- 使用OpenGL ES显示图像
- 建立OpenGL ES的环境
- 定义Shapes
- 绘制Shapes
- 运用投影与相机视图
- 添加移动
- 响应触摸事件
- 添加动画
- View间渐变
- 使用ViewPager实现屏幕侧滑
- 展示卡片翻转动画
- 缩放View
- 布局变更动画
- Android网络连接与云服务
- 无线连接设备
- 使得网络服务可发现
- 使用WiFi建立P2P连接
- 使用WiFi P2P服务
- 执行网络操作
- 连接到网络
- 管理网络
- 解析XML数据
- 高效下载
- 为网络访问更加高效而优化下载
- 最小化更新操作的影响
- 避免下载多余的数据
- 根据网络类型改变下载模式
- 云同步
- 使用备份API
- 使用Google Cloud Messaging
- 解决云同步的保存冲突
- 使用Sync Adapter传输数据
- 创建Stub授权器
- 创建Stub Content Provider
- 创建Sync Adpater
- 执行Sync Adpater
- 使用Volley执行网络数据传输
- 发送简单的网络请求
- 建立请求队列
- 创建标准的网络请求
- 实现自定义的网络请求
- Android联系人与位置信息
- Android联系人信息
- 获取联系人列表
- 获取联系人详情
- 使用Intents修改联系人信息
- 显示联系人头像
- Android位置信息
- 获取最后可知位置
- 获取位置更新
- 显示位置地址
- 创建和监视地理围栏
- Android可穿戴应用
- 赋予Notification可穿戴特性
- 创建Notification
- 在Notifcation中接收语音输入
- 为Notification添加显示页面
- 以Stack的方式显示Notifications
- 创建可穿戴的应用
- 创建并运行可穿戴应用
- 创建自定义的布局
- 添加语音功能
- 打包可穿戴应用
- 通过蓝牙进行调试
- 创建自定义的UI
- 定义Layouts
- 创建Cards
- 创建Lists
- 创建2D-Picker
- 创建确认界面
- 退出全屏的Activity
- 发送并同步数据
- 访问可穿戴数据层
- 同步数据单元
- 传输资源
- 发送与接收消息
- 处理数据层的事件
- Android TV应用
- 创建TV应用
- 创建TV应用的第一步
- 处理TV硬件部分
- 创建TV的布局文件
- 创建TV的导航栏
- 创建TV播放应用
- 创建目录浏览器
- 提供一个Card视图
- 创建详情页
- 显示正在播放卡片
- 帮助用户在TV上探索内容
- TV上的推荐内容
- 使得TV App能够被搜索
- 使用TV应用进行搜索
- 创建TV游戏应用
- 创建TV直播应用
- TV Apps Checklist
- Android企业级应用
- Ensuring Compatibility with Managed Profiles
- Implementing App Restrictions
- Building a Work Policy Controller
- Android交互设计
- 设计高效的导航
- 规划屏幕界面与他们之间的关系
- 为多种大小的屏幕进行规划
- 提供向下和横向导航
- 提供向上和历史导航
- 综合:设计样例 App
- 实现高效的导航
- 使用Tabs创建Swipe视图
- 创建抽屉导航
- 提供向上的导航
- 提供向后的导航
- 实现向下的导航
- 通知提示用户
- 建立Notification
- 当启动Activity时保留导航
- 更新Notification
- 使用BigView风格
- 显示Notification进度
- 增加搜索功能
- 建立搜索界面
- 保存并搜索数据
- 保持向下兼容
- 使得你的App内容可被Google搜索
- 为App内容开启深度链接
- 为索引指定App内容
- Android界面设计
- 为多屏幕设计
- 兼容不同的屏幕大小
- 兼容不同的屏幕密度
- 实现可适应的UI
- 创建自定义View
- 创建自定义的View类
- 实现自定义View的绘制
- 使得View可交互
- 优化自定义View
- 创建向后兼容的UI
- 抽象新的APIs
- 代理至新的APIs
- 使用旧的APIs实现新API的效果
- 使用版本敏感的组件
- 实现辅助功能
- 开发辅助程序
- 开发辅助服务
- 管理系统UI
- 淡化系统Bar
- 隐藏系统Bar
- 隐藏导航Bar
- 全屏沉浸式应用
- 响应UI可见性的变化
- 创建使用Material Design的应用
- 开始使用Material Design
- 使用Material的主题
- 创建Lists与Cards
- 定义Shadows与Clipping视图
- 使用Drawables
- 自定义动画
- 维护兼容性
- Android用户输入
- 使用触摸手势
- 检测常用的手势
- 跟踪手势移动
- Scroll手势动画
- 处理多触摸手势
- 拖拽与缩放
- 管理ViewGroup中的触摸事件
- 处理键盘输入
- 指定输入法类型
- 处理输入法可见性
- 兼容键盘导航
- 处理按键动作
- 兼容游戏控制器
- 处理控制器输入动作
- 支持不同的Android系统版本
- 支持多个控制器
- Android后台任务
- 在IntentService中执行后台任务
- 创建IntentService
- 发送工作任务到IntentService
- 报告后台任务执行状态
- 使用CursorLoader在后台加载数据
- 使用CursorLoader执行查询任务
- 处理查询的结果
- 管理设备的唤醒状态
- 保持设备的唤醒
- 制定重复定时的任务
- Android性能优化
- 管理应用的内存
- 代码性能优化建议
- 提升Layout的性能
- 优化layout的层级
- 使用include标签重用layouts
- 按需加载视图
- 使得ListView滑动顺畅
- 优化电池寿命
- 监测电量与充电状态
- 判断与监测Docking状态
- 判断与监测网络连接状态
- 根据需要操作Broadcast接受者
- 多线程操作
- 在一个线程中执行一段特定的代码
- 为多线程创建线程池
- 启动与停止线程池中的线程
- 与UI线程通信
- 避免出现程序无响应ANR
- JNI使用指南
- 优化多核处理器(SMP)下的Android程序
- Android安全与隐私
- Security Tips
- 使用HTTPS与SSL
- 为防止SSL漏洞而更新Security
- 使用设备管理条例增强安全性
- Android测试程序
- 测试你的Activity
- 建立测试环境
- 创建与执行测试用例
- 测试UI组件
- 创建单元测试
- 创建功能测试
- 術語表