Repository: kesenhoo/android-training-course-in-chinese Branch: master Commit: fc15d6fb682d Files: 336 Total size: 9.3 MB Directory structure: gitextract_k1yxr9w4/ ├── .gitignore ├── GLOSSARY.md ├── LICENSE ├── README.md ├── SUMMARY.md ├── animations/ │ ├── anim_card_flip.ogv │ ├── anim_card_flip.webm │ ├── anim_crossfade.ogv │ ├── anim_crossfade.webm │ ├── anim_layout_changes.ogv │ ├── anim_layout_changes.webm │ ├── anim_page_transformer_depth.ogv │ ├── anim_page_transformer_depth.webm │ ├── anim_page_transformer_zoomout.ogv │ ├── anim_page_transformer_zoomout.webm │ ├── anim_screenslide.ogv │ ├── anim_screenslide.webm │ ├── anim_zoom.ogv │ ├── anim_zoom.webm │ ├── cardflip.md │ ├── crossfade.md │ ├── index.md │ ├── layout.md │ ├── screen-slide.md │ └── zoom.md ├── background-jobs/ │ ├── load-data-background/ │ │ ├── handle-result.md │ │ ├── index.md │ │ └── setup-loader.md │ ├── run-background-service/ │ │ ├── create-service.md │ │ ├── index.md │ │ ├── report-status.md │ │ └── send-request.md │ └── scheduling/ │ ├── alarms.md │ ├── index.md │ └── wake-lock.md ├── basics/ │ ├── actionbar/ │ │ ├── adding-buttons.md │ │ ├── index.md │ │ ├── overlaying.md │ │ ├── setting-up.md │ │ └── styling.md │ ├── activity-lifecycle/ │ │ ├── index.md │ │ ├── pausing.md │ │ ├── recreating.md │ │ ├── starting.md │ │ └── stopping.md │ ├── data-storage/ │ │ ├── database.md │ │ ├── files.md │ │ ├── index.md │ │ └── shared-preference.md │ ├── firstapp/ │ │ ├── building-ui.md │ │ ├── creating-project.md │ │ ├── index.md │ │ ├── running-app.md │ │ └── starting-activity.md │ ├── fragments/ │ │ ├── communicating.md │ │ ├── creating.md │ │ ├── fragment-ui.md │ │ └── index.md │ ├── index.md │ ├── intents/ │ │ ├── filters.md │ │ ├── index.md │ │ ├── result.md │ │ └── sending.md │ ├── permissions/ │ │ ├── declaring.md │ │ └── index.md │ └── supporting-devices/ │ ├── index.md │ ├── languages.md │ ├── platforms.md │ └── screens.md ├── best-background.md ├── best-performance.md ├── best-security.md ├── best-testing.md ├── best-ui.md ├── best-user-input.md ├── best-ux.md ├── book.json ├── building-connectivity.md ├── building-content-sharing.md ├── building-graphics.md ├── building-multimedia.md ├── building-tv.md ├── building-userinfo.md ├── building-wearables.md ├── config.json ├── connectivity/ │ ├── cloudsave/ │ │ └── index.md │ ├── cloudsync/ │ │ ├── backupapi.md │ │ ├── gcm.md │ │ └── index.md │ ├── connect-devices-wireless/ │ │ ├── index.md │ │ ├── nsd-wifi-index.md │ │ ├── nsd.md │ │ └── wifi-direct.md │ ├── efficient-downloads/ │ │ ├── connectivity-patterns.md │ │ ├── efficient-network-access.md │ │ ├── index.md │ │ ├── redundant-redundant.md │ │ └── regular-update.md │ ├── network-ops/ │ │ ├── connecting.md │ │ ├── index.md │ │ ├── managing.md │ │ └── xml.md │ ├── sync-adapters/ │ │ ├── create-authenticator.md │ │ ├── create-stub-provider.md │ │ ├── create-sync-adapter.md │ │ ├── index.md │ │ └── running-sync-adapter.md │ └── volley/ │ ├── index.md │ ├── request-custom.md │ ├── request-queue.md │ ├── request.md │ └── simple.md ├── contacts-provider/ │ ├── display-badge.md │ ├── index.md │ ├── modify-data.md │ ├── retrieve-detail.md │ └── retrieve-names.md ├── content-sharing/ │ ├── beam-files/ │ │ ├── index.md │ │ ├── receive-files.md │ │ └── sending-files.md │ ├── secure-file-sharing/ │ │ ├── index.md │ │ ├── request-file.md │ │ ├── retrieve-info.md │ │ ├── setup-sharing.md │ │ └── sharing-file.md │ └── sharing/ │ ├── index.md │ ├── receive.md │ ├── send.md │ └── shareaction.md ├── enterprise/ │ ├── app-compatibility.md │ ├── app-restrictions.md │ ├── cosu.md │ ├── index.md │ └── work-policy-ctrl.md ├── gitbook/ │ ├── app.js │ ├── fonts/ │ │ └── fontawesome/ │ │ └── FontAwesome.otf │ ├── jsrepl/ │ │ ├── engines/ │ │ │ └── javascript-default.js │ │ ├── jsrepl.js │ │ ├── langs/ │ │ │ └── javascript/ │ │ │ └── jsrepl_js.js │ │ ├── sandbox.html │ │ └── sandbox.js │ ├── plugins/ │ │ ├── gitbook-plugin-disqus/ │ │ │ └── plugin.js │ │ └── gitbook-plugin-mathjax/ │ │ └── plugin.js │ ├── print.css │ └── style.css ├── graphics/ │ ├── displaying-bitmaps/ │ │ ├── cache-bitmap.md │ │ ├── display-bitmap.md │ │ ├── index.md │ │ ├── load-bitmap.md │ │ ├── manage-memory.md │ │ └── process-bitmap.md │ └── opengl/ │ ├── draw.md │ ├── environment.md │ ├── index.md │ ├── motion.md │ ├── projection.md │ ├── shapes.md │ └── touch.md ├── input/ │ ├── game-controller/ │ │ ├── compatibility.md │ │ ├── controller-inputs.md │ │ ├── index.md │ │ └── multi-controller.md │ ├── gestures/ │ │ ├── detector.md │ │ ├── index.md │ │ ├── movement.md │ │ ├── multi.md │ │ ├── scale.md │ │ ├── scroll.md │ │ └── viewgroup.md │ └── keyboard-input/ │ ├── commands.md │ ├── index.md │ ├── navigation.md │ ├── type.md │ └── visibility.md ├── location/ │ ├── activity-recognition.md │ ├── display-address.md │ ├── geofencing.md │ ├── index.md │ ├── location-testing.md │ ├── retrieve-current.md │ └── retrieve-location-updates.md ├── manifest.appcache ├── material/ │ ├── animations.md │ ├── compatibility.md │ ├── drawables.md │ ├── get-started.md │ ├── index.md │ ├── lists-cards.md │ ├── shadows-clipping.md │ └── theme.md ├── multimedia/ │ ├── audio/ │ │ ├── audio-focus.md │ │ ├── audio-output.md │ │ ├── index.md │ │ └── volume-playback.md │ ├── camera/ │ │ ├── cameradirect.md │ │ ├── index.md │ │ ├── photobasics.md │ │ └── videobasics.md │ └── printing/ │ ├── custom-docs.md │ ├── html-docs.md │ ├── index.md │ └── photos.md ├── package.json ├── performance/ │ ├── improving-layouts/ │ │ ├── index.md │ │ ├── loading-ondemand.md │ │ ├── optimizing-layout.md │ │ ├── reuse-layouts.md │ │ └── smooth-scrolling.md │ ├── memory.md │ ├── monitor-device-state/ │ │ ├── battery-monitor.md │ │ ├── connectivity-monitor.md │ │ ├── docking-monitor.md │ │ ├── index.md │ │ └── manifest-receivers.md │ ├── multi-threads/ │ │ ├── communicate-ui.md │ │ ├── create-threadpool.md │ │ ├── define-runnable.md │ │ ├── index.md │ │ └── run-code.md │ ├── perf-anr/ │ │ └── index.md │ ├── perf-jni/ │ │ └── index.md │ ├── performance-tips.md │ └── smp/ │ └── index.md ├── permissions/ │ ├── DeclaringPermissions.md │ ├── Requesting Permission at Run Time.md │ └── Using Permissions.md ├── search_index.json ├── security/ │ ├── device-management-policy.md │ ├── enterprise/ │ │ └── device-management-policy.md │ ├── security-gms-provider.md │ ├── security-ssl.md │ └── security-tips.md ├── testing/ │ └── activity-testing/ │ ├── activity-basic-testing.md │ ├── activity-function-testing.md │ ├── activity-ui-testing.md │ ├── activity-unit-testing.md │ ├── index.md │ └── prepare-activity-testing.md ├── tv/ │ ├── discovery/ │ │ ├── in-app-search.md │ │ ├── index.md │ │ ├── recommendations.md │ │ └── searchable.md │ ├── games/ │ │ └── index.md │ ├── playback/ │ │ ├── browse.md │ │ ├── card.md │ │ ├── details.md │ │ ├── index.md │ │ └── now-playing.md │ ├── publishing/ │ │ └── checklist.md │ ├── start/ │ │ ├── hardware.md │ │ ├── index.md │ │ ├── layouts.md │ │ ├── navigation.md │ │ └── start.md │ └── tif/ │ └── index.md ├── ui/ │ ├── accessibility/ │ │ ├── accessible-app.md │ │ ├── accessible-service.md │ │ └── index.md │ ├── backward-compatible-ui/ │ │ ├── abstract.md │ │ ├── index.md │ │ ├── new-impl.md │ │ ├── older-impl.md │ │ └── using-component.md │ ├── custom-view/ │ │ ├── create-view.md │ │ ├── custom-draw.md │ │ ├── index.md │ │ ├── make-interactive.md │ │ └── optimize-view.md │ ├── multiscreen/ │ │ ├── adapt-ui.md │ │ ├── index.md │ │ ├── screen-desities.md │ │ └── screen-sizes.md │ └── system-ui/ │ ├── dim.md │ ├── hide-nav.md │ ├── hide-ui.md │ ├── immersive.md │ ├── index.md │ └── visibility.md ├── ux/ │ ├── app-indexing/ │ │ ├── deep-linking.md │ │ ├── enable-app-indexing.md │ │ └── index.md │ ├── design-nav/ │ │ ├── ancestral-temporal.md │ │ ├── descendant-lateral.md │ │ ├── index.md │ │ ├── multi-sizes.md │ │ ├── screen-planning.md │ │ └── wireframing.md │ ├── implement-nav/ │ │ ├── ancestral.md │ │ ├── descendant.md │ │ ├── index.md │ │ ├── lateral.md │ │ ├── nav-drawer.md │ │ └── temporal.md │ ├── notify-user/ │ │ ├── build-notification.md │ │ ├── expand-notification.md │ │ ├── index.md │ │ ├── nav.md │ │ ├── progess-notification.md │ │ └── update-notification.md │ └── search/ │ ├── back-compat.md │ ├── index.md │ ├── search.md │ └── setup.md └── wearables/ ├── apps/ │ ├── bt-debugging.md │ ├── creating.md │ ├── index.md │ ├── layouts.md │ ├── packaging.md │ └── voice.md ├── data-layer/ │ ├── accessing.md │ ├── assets.md │ ├── data-items.md │ ├── events.md │ ├── index.md │ └── messages.md ├── location/ │ └── wear-location-detection.md ├── notifications/ │ ├── creating.md │ ├── index.md │ ├── pages.md │ ├── stacks.md │ └── voice-input.md ├── ui/ │ ├── 2d-picker.md │ ├── cards.md │ ├── confirm.md │ ├── exit.md │ ├── index.md │ ├── layouts.md │ └── lists.md └── watch-faces/ ├── configuration.md ├── designing.md ├── drawing.md ├── index.md ├── information.md ├── issues.md ├── performance.md └── service.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store ================================================ FILE: GLOSSARY.md ================================================ ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Android官方培训课程中文版(v0.9.7) ![Android Training](android_training.jpg) Google Android团队在2012年的时候开设了**Android Training**板块 - ,这些课程是学习Android应用开发的绝佳资料。我们通过Github发起开源协作翻译的项目,完成中文版的输出,欢迎大家传阅学习! 文章中难免会有很多写的不对不好的地方,欢迎读者加入此协作项目,进行纠错,为完善这份教程贡献一点力量! ## Github托管主页 **** 请读者点击Star进行关注并支持! ## 在线阅读 **** ### 更新记录 * v0.9.7 - 2016/11/04 * v0.9.6 - 2016/05/22 * v0.9.5 - 2015/12/15 * v0.9.4 - 2015/06/11 * v0.9.3 - 2015/05/18 * v0.9.2 - 2015/03/30 * v0.9.1 - 2015/03/14 * v0.9.0 - 2015/03/09 * v0.8.0 - 2015/02/12 * v0.7.0 - 2014/11/30 * v0.6.0 - 2014/11/02 * v0.5.0 - 2014/10/18 * v0.4.0 - 2014/09/11 * v0.3.0 - 2014/08/31 * v0.2.0 - 2014/08/14 * v0.1.0 - 2014/08/05 ## 参与方式 你可以选择以下的方式帮忙修改纠正这份教程(推荐使用方法1): 1. 通过[在线阅读](http://hukai.me/android-training-course-in-chinese/index.html)课程的页面,找到[Github仓库](https://github.com/kesenhoo/android-training-course-in-chinese)对应的章节文件,直接在线编辑修改提交即可。 2. 在线阅读的文章底部留言,提出问题与修改意见,我会抽时间及时处理。 3. 写邮件给发起人:**[胡凯](http://hukai.me)**,邮箱是kesenhoo at gmail.com,邮件内容注明需要纠正的章节段落位置,并给出纠正的建议。 ## 致谢 发起这个项目之后,得到很多人的支持,有经验丰富的Android开发者,也有刚接触Android的爱好者。他们有些已经上班,有些还是学生,有些在国内,还有的在国外!感谢所有参与或者关注这个项目的小伙伴! 下面是参与翻译的小伙伴(Github ID按照课程结构排序): | 0 | 1 | 2 | | --- | --- | --- | | @[yuanfentiank789](https://github.com/yuanfentiank789) | @[vincent4j](https://github.com/vincent4j) | @[Lin-H](https://github.com/Lin-H) | | @[kesenhoo](https://github.com/kesenhoo) | @[fastcome1985](https://github.com/fastcome1985) | @[jdneo](https://github.com/jdneo) | | @[XizhiXu](https://github.com/XizhiXu) | @[naizhengtan](https://github.com/naizhengtan) | @[spencer198711](https://github.com/spencer198711) | | @[penkzhou](https://github.com/penkzhou) | @[wangyachen](https://github.com/wangyachen) | @[wly2014](https://github.com/wly2014) | | @[fastcome1985](https://github.com/fastcome1985) | @[riverfeng](https://github.com/riverfeng) | @[xrayzh](https://github.com/xrayzh) | | @[K0ST](https://github.com/K0ST) | @[Andrwyw](https://github.com/Andrwyw) | @[zhaochunqi](https://github.com/zhaochunqi) | | @[lltowq](https://github.com/lltowq) | @[allenlsy](https://github.com/allenlsy) | @[AllenZheng1991](https://github.com/AllenZheng1991) | | @[pedant](https://github.com/pedant) | @[craftsmanBai](https://github.com/craftsmanBai) | @[huanglizhuo](https://github.com/huanglizhuo) | | @[Roya](https://github.com/RoyaAoki) | @[awong1900](https://github.com/awong1900) | @[dupengwei](https://github.com/dupengwei) | | 0:10 | 1:10 | 2:10 | @发起人:[胡凯](),博客:,Github:,微博: 还有众多参与纠错校正的同学名字就不一一列举了,谢谢所有关注这个项目的小伙伴!特别感谢[安卓巴士社区](http://www.apkbus.com),[爱开发社区](http://akaifa.com),[码农周刊](http://weekly.manong.io)对项目的宣传! ## License 本站作品由创作,采用[知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可](http://creativecommons.org/licenses/by-nc-sa/4.0/)协议进行许可。 ================================================ FILE: SUMMARY.md ================================================ # Summary * [序言](README.md) * [Android入门基础:从这里开始](basics/index.md) * [建立第一个App](basics/firstapp/index.md) * [创建Android项目](basics/firstapp/creating-project.md) * [执行Android程序](basics/firstapp/running-app.md) * [建立简单的用户界面](basics/firstapp/building-ui.md) * [启动其他的Activity](basics/firstapp/starting-activity.md) * [添加ActionBar](basics/actionbar/index.md) * [建立ActionBar](basics/actionbar/setting-up.md) * [添加Action按钮](basics/actionbar/adding-buttons.md) * [自定义ActionBar的风格](basics/actionbar/styling.md) * [ActionBar的覆盖层叠](basics/actionbar/overlaying.md) * [兼容不同的设备](basics/supporting-devices/index.md) * [适配不同的语言](basics/supporting-devices/languages.md) * [适配不同的屏幕](basics/supporting-devices/screens.md) * [适配不同的系统版本](basics/supporting-devices/platforms.md) * [管理Activity的生命周期](basics/activity-lifecycle/index.md) * [启动与销毁Activity](basics/activity-lifecycle/starting.md) * [暂停与恢复Activity](basics/activity-lifecycle/pausing.md) * [停止与重启Activity](basics/activity-lifecycle/stopping.md) * [重新创建Activity](basics/activity-lifecycle/recreating.md) * [使用Fragment建立动态的UI](basics/fragments/index.md) * [创建一个Fragment](basics/fragments/creating.md) * [建立灵活动态的UI](basics/fragments/fragment-ui.md) * [Fragments之间的交互](basics/fragments/communicating.md) * [数据保存](basics/data-storage/index.md) * [保存到Preference](basics/data-storage/shared-preference.md) * [保存到文件](basics/data-storage/files.md) * [保存到数据库](basics/data-storage/database.md) * [与其他应用的交互](basics/intents/index.md) * [Intent的发送](basics/intents/sending.md) * [接收Activity返回的结果](basics/intents/result.md) * [Intent过滤](basics/intents/filters.md) * [Android分享操作](building-content-sharing.md) * [分享简单的数据](content-sharing/sharing/index.md) * [给其他App发送简单的数据](content-sharing/sharing/send.md) * [接收从其他App返回的数据](content-sharing/sharing/receive.md) * [给ActionBar增加分享功能](content-sharing/sharing/shareaction.md) * [分享文件](content-sharing/secure-file-sharing/index.md) * [建立文件分享](content-sharing/secure-file-sharing/setup-sharing.md) * [分享文件](content-sharing/secure-file-sharing/sharing-file.md) * [请求分享一个文件](content-sharing/secure-file-sharing/request-file.md) * [获取文件信息](content-sharing/secure-file-sharing/retrieve-info.md) * [使用NFC分享文件](content-sharing/beam-files/index.md) * [发送文件给其他设备](content-sharing/beam-files/sending-files.md) * [接收其他设备的文件](content-sharing/beam-files/receive-files.md) * [Android多媒体](building-multimedia.md) * [管理音频播放](multimedia/audio/index.md) * [控制音量与音频播放](multimedia/audio/volume-playback.md) * [管理音频焦点](multimedia/audio/audio-focus.md) * [兼容音频输出设备](multimedia/audio/audio-output.md) * [拍照](multimedia/camera/index.md) * [简单的拍照](multimedia/camera/photobasics.md) * [简单的录像](multimedia/camera/videobasics.md) * [控制相机硬件](multimedia/camera/cameradirect.md) * [打印](multimedia/printing/index.md) * [打印照片](multimedia/printing/photos.md) * [打印HTML文档](multimedia/printing/html-docs.md) * [打印自定义文档](multimedia/printing/custom-docs.md) * [Android图像与动画](building-graphics.md) * [高效显示Bitmap](graphics/displaying-bitmaps/index.md) * [高效加载大图](graphics/displaying-bitmaps/load-bitmap.md) * [非UI线程处理Bitmap](graphics/displaying-bitmaps/process-bitmap.md) * [缓存Bitmap](graphics/displaying-bitmaps/cache-bitmap.md) * [管理Bitmap的内存](graphics/displaying-bitmaps/manage-memory.md) * [在UI上显示Bitmap](graphics/displaying-bitmaps/display-bitmap.md) * [使用OpenGL ES显示图像](graphics/opengl/index.md) * [建立OpenGL ES的环境](graphics/opengl/environment.md) * [定义Shapes](graphics/opengl/shapes.md) * [绘制Shapes](graphics/opengl/draw.md) * [运用投影与相机视图](graphics/opengl/projection.md) * [添加移动](graphics/opengl/motion.md) * [响应触摸事件](graphics/opengl/touch.md) * [添加动画](animations/index.md) * [View间渐变](animations/crossfade.md) * [使用ViewPager实现屏幕滑动](animations/screen-slide.md) * [展示Card翻转动画](animations/cardflip.md) * [缩放View](animations/zoom.md) * [布局变更动画](animations/layout.md) * [Android网络连接与云服务](building-connectivity.md) * [无线连接设备](connectivity/connect-devices-wireless/index.md) * [使用网络服务发现](connectivity/connect-devices-wireless/nsd.md) * [使用WiFi建立P2P连接](connectivity/connect-devices-wireless/wifi-direct.md) * [使用WiFi P2P服务](connectivity/connect-devices-wireless/nsd-wifi-index.md) * [执行网络操作](connectivity/network-ops/index.md) * [连接到网络](connectivity/network-ops/connecting.md) * [管理网络的使用情况](connectivity/network-ops/managing.md) * [解析XML数据](connectivity/network-ops/xml.md) * [传输数据时避免消耗大量电量](connectivity/efficient-downloads/index.md) * [优化下载以高效地访问网络](connectivity/efficient-downloads/efficient-network-access.md) * [最小化定期更新造成的影响](connectivity/efficient-downloads/regular-update.md) * [重复的下载是冗余的](connectivity/efficient-downloads/redundant-redundant.md) * [根据网络连接类型来调整下载模式](connectivity/efficient-downloads/connectivity-patterns.md) * [云同步](connectivity/cloudsync/index.md) * [使用备份API](connectivity/cloudsync/backupapi.md) * [使用Google Cloud Messaging](connectivity/cloudsync/gcm.md) * [解决云同步的保存冲突](connectivity/cloudsave/index.md) * [使用Sync Adapter传输数据](connectivity/sync-adapters/index.md) * [创建Stub授权器](connectivity/sync-adapters/create-authenticator.md) * [创建Stub Content Provider](connectivity/sync-adapters/create-stub-provider.md) * [创建Sync Adpater](connectivity/sync-adapters/create-sync-adapter.md) * [执行Sync Adpater](connectivity/sync-adapters/running-sync-adapter.md) * [使用Volley执行网络数据传输](connectivity/volley/index.md) * [发送简单的网络请求](connectivity/volley/simple.md) * [建立请求队列](connectivity/volley/request-queue.md) * [创建标准的网络请求](connectivity/volley/request.md) * [实现自定义的网络请求](connectivity/volley/request-custom.md) * [Android联系人与位置信息](building-userinfo.md) * [Android联系人信息](contacts-provider/index.md) * [获取联系人列表](contacts-provider/retrieve-names.md) * [获取联系人详情](contacts-provider/retrieve-detail.md) * [使用Intents修改联系人信息](contacts-provider/modify-data.md) * [显示联系人头像](contacts-provider/display-badge.md) * [Android位置信息](location/index.md) * [获取最后可知位置](location/retrieve-current.md) * [获取位置更新](location/retrieve-location-updates.md) * [显示位置地址](location/display-address.md) * [创建和监视地理围栏](location/geofencing.md) * [Android可穿戴应用](building-wearables.md) * [赋予Notification可穿戴特性](wearables/notifications/index.md) * [创建Notification](wearables/notifications/creating.md) * [在Notifcation中接收语音输入](wearables/notifications/voice-input.md) * [为Notification添加显示页面](wearables/notifications/pages.md) * [以Stack的方式显示Notifications](wearables/notifications/stacks.md) * [创建可穿戴的应用](wearables/apps/index.md) * [创建并运行可穿戴应用](wearables/apps/creating.md) * [创建自定义的布局](wearables/apps/layouts.md) * [添加语音功能](wearables/apps/voice.md) * [打包可穿戴应用](wearables/apps/packaging.md) * [通过蓝牙进行调试](wearables/apps/bt-debugging.md) * [创建自定义的UI](wearables/ui/index.md) * [定义Layouts](wearables/ui/layouts.md) * [创建Card](wearables/ui/cards.md) * [创建List](wearables/ui/lists.md) * [创建2D Picker](wearables/ui/2d-picker.md) * [创建确认界面](wearables/ui/confirm.md) * [退出全屏的Activity](wearables/ui/exit.md) * [发送并同步数据](wearables/data-layer/index.md) * [访问可穿戴数据层](wearables/data-layer/accessing.md) * [同步数据单元](wearables/data-layer/data-items.md) * [传输资源](wearables/data-layer/assets.md) * [发送与接收消息](wearables/data-layer/messages.md) * [处理数据层的事件](wearables/data-layer/events.md) * [创建表盘](wearables/watch-faces/index.md) * [设计表盘](wearables/watch-faces/designing.md) * [构建表盘服务](wearables/watch-faces/service.md) * [绘制表盘](wearables/watch-faces/drawing.md) * [在表盘上显示信息](wearables/watch-faces/information.md) * [提供配置 Activity](wearables/watch-faces/configuration.md) * [定位常见的问题](wearables/watch-faces/issues.md) * [优化性能和电池使用时间](wearables/watch-faces/performance.md) * [位置检测](wearables/location/wear-location-detection.md) * [Android TV应用](building-tv.md) * [创建TV应用](tv/start/index.md) * [创建TV应用的第一步](tv/start/start.md) * [处理TV硬件部分](tv/start/hardware.md) * [创建TV的布局文件](tv/start/layouts.md) * [创建TV的导航栏](tv/start/navigation.md) * [创建TV播放应用](tv/playback/index.md) * [创建目录浏览器](tv/playback/browse.md) * [提供一个Card视图](tv/playback/card.md) * [创建详情页](tv/playback/details.md) * [显示正在播放卡片](tv/playback/now-playing.md) * [帮助用户在TV上探索内容](tv/discovery/index.md) * [TV上的推荐内容](tv/discovery/recommendations.md) * [使得TV App能够被搜索](tv/discovery/searchable.md) * [使用TV应用进行搜索](tv/discovery/in-app-search.md) * [创建TV游戏应用](tv/games/index.md) * [创建TV直播应用](tv/tif/index.md) * [TV Apps Checklist](tv/publishing/checklist.md) * [Android企业级应用](enterprise/index.md) * [Ensuring Compatibility with Managed Profiles](enterprise/app-compatibility.md) * [Implementing App Restrictions](enterprise/app-restrictions.md) * [Building a Work Policy Controller](enterprise/work-policy-ctrl.md) * [Android交互设计](best-ux.md) * [设计高效的导航](ux/design-nav/index.md) * [规划屏幕界面与他们之间的关系](ux/design-nav/screen-planning.md) * [为多种大小的屏幕进行规划](ux/design-nav/multi-sizes.md) * [提供向下和横向导航](ux/design-nav/descendant-lateral.md) * [提供向上和历史导航](ux/design-nav/ancestral-temporal.md) * [综合:设计样例 App](ux/design-nav/wireframing.md) * [实现高效的导航](ux/implement-nav/index.md) * [使用Tabs创建Swipe视图](ux/implement-nav/lateral.md) * [创建抽屉导航](ux/implement-nav/nav-drawer.md) * [提供向上的导航](ux/implement-nav/ancestral.md) * [提供向后的导航](ux/implement-nav/temporal.md) * [实现向下的导航](ux/implement-nav/descendant.md) * [通知提示用户](ux/notify-user/index.md) * [建立Notification](ux/notify-user/build-notification.md) * [当启动Activity时保留导航](ux/notify-user/nav.md) * [更新Notification](ux/notify-user/update-notification.md) * [使用BigView风格](ux/notify-user/expand-notification.md) * [显示Notification进度](ux/notify-user/progess-notification.md) * [增加搜索功能](ux/search/index.md) * [建立搜索界面](ux/search/setup.md) * [保存并搜索数据](ux/search/search.md) * [保持向下兼容](ux/search/back-compat.md) * [使得你的App内容可被Google搜索](ux/app-indexing/index.md) * [为App内容开启深度链接](ux/app-indexing/deep-linking.md) * [为索引指定App内容](ux/app-indexing/enable-app-indexing.md) * [Android界面设计](best-ui.md) * [为多屏幕设计](ui/multiscreen/index.md) * [兼容不同的屏幕大小](ui/multiscreen/screen-sizes.md) * [兼容不同的屏幕密度](ui/multiscreen/screen-desities.md) * [实现可适应的UI](ui/multiscreen/adapt-ui.md) * [创建自定义View](ui/custom-view/index.md) * [创建自定义的View类](ui/custom-view/create-view.md) * [实现自定义View的绘制](ui/custom-view/custom-draw.md) * [使得View可交互](ui/custom-view/make-interactive.md) * [优化自定义View](ui/custom-view/optimize-view.md) * [创建向后兼容的UI](ui/backward-compatible-ui/index.md) * [抽象新的APIs](ui/backward-compatible-ui/abstract.md) * [代理至新的APIs](ui/backward-compatible-ui/new-impl.md) * [使用旧的APIs实现新API的效果](ui/backward-compatible-ui/older-impl.md) * [使用版本敏感的组件](ui/backward-compatible-ui/using-component.md) * [实现辅助功能](ui/accessibility/index.md) * [开发辅助程序](ui/accessibility/accessible-app.md) * [开发辅助服务](ui/accessibility/accessible-service.md) * [管理系统UI](ui/system-ui/index.md) * [淡化系统Bar](ui/system-ui/dim.md) * [隐藏系统Bar](ui/system-ui/hide-ui.md) * [隐藏导航Bar](ui/system-ui/hide-nav.md) * [全屏沉浸式应用](ui/system-ui/immersive.md) * [响应UI可见性的变化](ui/system-ui/visibility.md) * [创建使用Material Design的应用](material/index.md) * [开始使用Material Design](material/get-started.md) * [使用Material的主题](material/theme.md) * [创建Lists与Cards](material/lists-cards.md) * [定义Shadows与Clipping视图](material/shadows-clipping.md) * [使用Drawables](material/drawables.md) * [自定义动画](material/animations.md) * [维护兼容性](material/compatibility.md) * [Android用户输入](best-user-input.md) * [使用触摸手势](input/gestures/index.md) * [检测常用的手势](input/gestures/detector.md) * [跟踪手势移动](input/gestures/movement.md) * [滚动手势动画](input/gestures/scroll.md) * [处理多点触控手势](input/gestures/multi.md) * [拖拽与缩放](input/gestures/scale.md) * [管理ViewGroup中的触摸事件](input/gestures/viewgroup.md) * [处理键盘输入](input/keyboard-input/index.md) * [指定输入法类型](input/keyboard-input/type.md) * [处理输入法可见性](input/keyboard-input/visibility.md) * [支持键盘导航](input/keyboard-input/navigation.md) * [处理按键动作](input/keyboard-input/commands.md) * [支持游戏控制器](input/game-controller/index.md) * [处理控制器输入动作](input/game-controller/controller-inputs.md) * [在不同的 Android 系统版本支持控制器](input/game-controller/compatibility.md) * [支持多个控制器](input/game-controller/multi-controller.md) * [Android后台任务](best-background.md) * [在IntentService中执行后台任务](background-jobs/run-background-service/index.md) * [创建IntentService](background-jobs/run-background-service/create-service.md) * [发送工作任务到IntentService](background-jobs/run-background-service/send-request.md) * [报告后台任务执行状态](background-jobs/run-background-service/report-status.md) * [使用CursorLoader在后台加载数据](background-jobs/load-data-background/index.md) * [使用CursorLoader执行查询任务](background-jobs/load-data-background/setup-loader.md) * [处理CursorLoader查询的结果](background-jobs/load-data-background/handle-result.md) * [管理设备的唤醒状态](background-jobs/scheduling/index.md) * [保持设备的唤醒](background-jobs/scheduling/wake-lock.md) * [制定重复定时的任务](background-jobs/scheduling/alarms.md) * [Android性能优化](best-performance.md) * [管理应用的内存](performance/memory.md) * [代码性能优化建议](performance/performance-tips.md) * [提升Layout的性能](performance/improving-layouts/index.md) * [优化layout的层级](performance/improving-layouts/optimizing-layout.md) * [使用include标签重用layouts](performance/improving-layouts/reuse-layouts.md) * [按需加载视图](performance/improving-layouts/loading-ondemand.md) * [使得ListView滑动顺畅](performance/improving-layouts/smooth-scrolling.md) * [优化电池寿命](performance/monitor-device-state/index.md) * [监测电量与充电状态](performance/monitor-device-state/battery-monitor.md) * [判断与监测Docking状态](performance/monitor-device-state/docking-monitor.md) * [判断与监测网络连接状态](performance/monitor-device-state/connectivity-monitor.md) * [根据需要操作Broadcast接受者](performance/monitor-device-state/manifest-receivers.md) * [多线程操作](performance/multi-threads/index.md) * [在一个线程中执行一段特定的代码](performance/multi-threads/define-runnable.md) * [为多线程创建线程池](performance/multi-threads/create-threadpool.md) * [启动与停止线程池中的线程](performance/multi-threads/run-code.md) * [与UI线程通信](performance/multi-threads/communicate-ui.md) * [避免出现程序无响应ANR](performance/perf-anr/index.md) * [JNI使用指南](performance/perf-jni/index.md) * [优化多核处理器(SMP)下的Android程序](performance/smp/index.md) * [Android安全与隐私](best-security.md) * [Security Tips](security/security-tips.md) * [使用HTTPS与SSL](security/security-ssl.md) * [为防止SSL漏洞而更新Security](security/security-gms-provider.md) * [使用设备管理条例增强安全性](security/device-management-policy.md) * [Android测试程序](best-testing.md) * [测试你的Activity](testing/activity-testing/index.md) * [建立测试环境](testing/activity-testing/prepare-activity-testing.md) * [创建与执行测试用例](testing/activity-testing/activity-basic-testing.md) * [测试UI组件](testing/activity-testing/activity-ui-testing.md) * [创建单元测试](testing/activity-testing/activity-unit-testing.md) * [创建功能测试](testing/activity-testing/activity-function-testing.md) ================================================ FILE: animations/cardflip.md ================================================ # 展示Card翻转动画 > 编写:[XizhiXu](https://github.com/XizhiXu) - 原文: 这节课展示如何使用自定义Fragment动画实现Card翻转动画。Card翻转动画通过模拟Card翻转的效果实现view内容的切换。 下面是card翻转动画的样子:
如果你想直接查看整个例子,[下载](http://developer.android.com/shareables/training/Animations.zip)并运行App样例然后选择Card翻转例子。查看下列文件中的代码实现: * `src/CardFlipActivity.java` * `animator/card_flip_right_in.xml` * `animator/card_flip_right_out.xml` * `animator/card_flip_left_in.xml` * `animator/card_flip_left_out.xml` * `layout/fragment_card_back.xml` * `layout/fragment_card_front.xml` ## 创建Animator 创建Card翻转动画,我们需要两个Animator。一个让正面的card的右侧向左翻转渐出,一个让背面的Card向右翻转渐入。我们还需要两个 Animator让背面的card的右侧向左翻转渐入,一个让向右翻转渐入。 **card_flip_left_in.xml** ```xml ``` **card_flip_left_out.xml** ```xml ``` **card_flip_right_in.xml** ```xml ``` **card_flip_right_out.xml** ```xml ``` ## 创建View Card的每一面是一个独立的布局,比如两屏文字,两张图片,或者任何View的组合。然后我们将在应用动画的Fragment里面用到这两个布局。下面的布局创建了展示文本Card的一面: ```xml ``` Card另一面显示一个 [`ImageView`](http://developer.android.com/reference/android/widget/ImageView.html): ```xml ``` ## 创建Fragment 为Card正反面创建Fragment,这些类从 `onCreateView()` 方法中分别为每个Framgent返回你之前创建的布局。在想要显示Card的父Activity中,我们可以创建对应的Fragment 实例。下面的例子展示父Activity内嵌套的Fragment: ```java public class CardFlipActivity extends Activity { ... /** * A fragment representing the front of the card. */ public class CardFrontFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_card_front, container, false); } } /** * A fragment representing the back of the card. */ public class CardBackFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_card_back, container, false); } } } ``` ## 应用card翻转动画 现在,我们需要在父Activity中展示Fragment。为此,首先创建Activity的布局。下面例子创建了一个可以在运行时添加Fragment的 [`FrameLayout`](http://developer.android.com/reference/android/widget/FrameLayout.html)。 ```xml ``` 在Activity代码中,把先前创建的布局设置成其ContentVew。当Activity创建时展示一个默认的Fragment是个不错的注意。所以下面的Activity样例表明了如何默认显示卡片正面: ```java public class CardFlipActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_activity_card_flip); if (savedInstanceState == null) { getFragmentManager() .beginTransaction() .add(R.id.container, new CardFrontFragment()) .commit(); } } ... } ``` 既然现在显示了卡片的正面,我们可以在合适时机用翻转动画显示卡片背面了。创建一个方法来显示背面,它需要做下面这些事情: * 将Fragment转换设置我们刚做的自定义动画 * 用新Fragment替换当前显示的Fragment,并且应用之前创建的动画到该事件中。 * 添加之前显示的Fragment到Fragment的回退栈(back stack)中,所以当用户按下 *Back* 键时,Card会翻转回来。 ```java private void flipCard() { if (mShowingBack) { getFragmentManager().popBackStack(); return; } // Flip to the back. mShowingBack = true; // Create and commit a new fragment transaction that adds the fragment for the back of // the card, uses custom animations, and is part of the fragment manager's back stack. getFragmentManager() .beginTransaction() // Replace the default fragment animations with animator resources representing // rotations when switching to the back of the card, as well as animator // resources representing rotations when flipping back to the front (e.g. when // the system Back button is pressed). .setCustomAnimations( R.animator.card_flip_right_in, R.animator.card_flip_right_out, R.animator.card_flip_left_in, R.animator.card_flip_left_out) // Replace any fragments currently in the container view with a fragment // representing the next page (indicated by the just-incremented currentPage // variable). .replace(R.id.container, new CardBackFragment()) // Add this transaction to the back stack, allowing users to press Back // to get to the front of the card. .addToBackStack(null) // Commit the transaction. .commit(); } ``` ================================================ FILE: animations/crossfade.md ================================================ # View间渐变 > 编写:[XizhiXu](https://github.com/XizhiXu) - 原文: 渐变动画(也叫消失)通常指渐渐的淡出某个UI组件,同时同步地淡入另一个。当App想切换内容或View的情况下,这种动画很有用。渐变简短不易察觉,同时又提供从一个界面到下一个之间流畅的转换。如果在需要转换的时候没有使用任何动画效果,这会使得转换看上去感到生硬而仓促。 下面是一个利用进度指示渐变到一些文本内容的例子。
如果你想跳过这部分介绍直接查看样例,[下载](http://developer.android.com/shareables/training/Animations.zip)并运行样例App然后选择渐变例子。查看下列文件中的代码实现: * `src/CrossfadeActivity.java` * `layout/activity_crossfade.xml` * `menu/activity_crossfade.xml` ## 创建View 创建两个我们想相互渐变的View。下面的例子创建了一个进度提示圈和可滑动文本View。 ```xml ``` ## 设置动画 为设置动画,我们需要按照如下步骤来做: 1. 为我们想渐变的View 创建成员变量。在之后动画应用途中修改View的时候我们会需要这些引用。 2. 对于被淡入的View,设置它的visibility为[`GONE`](http://developer.android.com/reference/android/view/View.html#GONE)。这样防止view再占据布局的空间,而且也能在布局计算中将其忽略,加速处理过程。 3. 将[`config_shortAnimTime`](http://developer.android.com/reference/android/R.integer.html#config_shortAnimTime)系统属性暂存到一个成员变量里。这个属性为动画定义了一个标准的“短”持续时间。对于细微或者快速发生的动画,这是个很理想的持续时段。也可以根据实际需求使用[`config_longAnimTime`](http://developer.android.com/reference/android/R.integer.html#config_longAnimTime)或[`config_mediumAnimTime`](http://developer.android.com/reference/android/R.integer.html#config_mediumAnimTime)。 下面的例子使用了前文提到的布局文件: ```java public class CrossfadeActivity extends Activity { private View mContentView; private View mLoadingView; private int mShortAnimationDuration; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_crossfade); mContentView = findViewById(R.id.content); mLoadingView = findViewById(R.id.loading_spinner); // Initially hide the content view. mContentView.setVisibility(View.GONE); // Retrieve and cache the system's default "short" animation time. mShortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime); } ``` ## 渐变View 进行了上述配置之后,接下来就让我们实现渐变动画吧: 1. 对于正在淡入的View,设置它的alpha值为0并且设置visibility为 [`VISIBLE`](http://developer.android.com/reference/android/view/View.html#VISIBLE)(记住他起初被设置成了 [`GONE`](http://developer.android.com/reference/android/view/View.html#GONE))。这样View就变成可见的了,但是此时它是透明的。 2. 对于正在淡入的View,把alpha值从0动态改变到1。同时,对于淡出的View,把alpha值从1动态变到0。 3. 使用[`Animator.AnimatorListener`](http://developer.android.com/reference/android/animation/Animator.AnimatorListener.html)中的 `onAnimationEnd()`,设置淡出View的visibility为[`GONE`](http://developer.android.com/reference/android/view/View.html#GONE)。即使alpha值为0,也要把View的visibility设置成[`GONE`](http://developer.android.com/reference/android/view/View.html#GONE)来防止 view 占据布局空间,还能把它从布局计算中忽略,加速处理过程。 详见下面的例子: ```java private View mContentView; private View mLoadingView; private int mShortAnimationDuration; ... private void crossfade() { // Set the content view to 0% opacity but visible, so that it is visible // (but fully transparent) during the animation. mContentView.setAlpha(0f); mContentView.setVisibility(View.VISIBLE); // Animate the content view to 100% opacity, and clear any animation // listener set on the view. mContentView.animate() .alpha(1f) .setDuration(mShortAnimationDuration) .setListener(null); // Animate the loading view to 0% opacity. After the animation ends, // set its visibility to GONE as an optimization step (it won't // participate in layout passes, etc.) mLoadingView.animate() .alpha(0f) .setDuration(mShortAnimationDuration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLoadingView.setVisibility(View.GONE); } }); } ``` ================================================ FILE: animations/index.md ================================================ # 添加动画 > 编写:[XizhiXu](https://github.com/XizhiXu) - 原文: 动画可以为我们的App增加精细的视觉提示,并且能改进App界面的思维模型。当界面改变其状态时(例如加载内容或新操作可用时),动画特别有帮助。另外,动画也能让我们的App外观更加优雅,为用户提供一种更好的使用体验。 但是记住:滥用动画或者在错误时机使用动画也是有害的,比如他们会造成延迟。本系列课程将会讲解如何应用常用动画类型来提升易用性。我们的目标是在不给用户用户增加烦恼的前提下提升App的“气质”。 ## Lessons * [View间渐变](crossfade.html) 学习在重叠View间的淡入淡出。作为一个例子,我们将会学习如何在一个进度条与一个包含了文本内容的View之间实现淡入淡出的效果。 * [用ViewPager实现屏幕滑动](screen-slide.html) 学习怎样为水平相邻的界面提供滑动动画。 * [展示Card翻转动画](cardflip.html) 学习怎样实现两个View之间的翻转动画。 * [缩放View](zoom.html) 学习怎样通过触控放大一个View。 * [布局变更动画](layout.html) 学习在增加、移除或更新子View时,怎样使用内置的动画效果。 ================================================ FILE: animations/layout.md ================================================ # 布局变更动画 > 编写:[XizhiXu](https://github.com/XizhiXu) - 原文: 布局动画是一种预加载动画,系统在每次改变布局配置时运行它。我们需要做的仅是在布局文件里设置属性告诉Android系统为这些布局的变更应用动画,然后系统的默认动画便会执行。 > **小贴士:** 如果你想补充自定义布局动画,创建 [`LayoutTransition`](http://developer.android.com/reference/android/animation/LayoutTransition.html) 对象,然后用 `setLayoutTransition()` 方法把它加到布局中。 下面的例子在一个list中添加一项的默认布局动画:
如果你想直接查看整个例子,[下载](http://developer.android.com/shareables/training/Animations.zip) App 样例并运行然后选择布局渐变的例子。查看下列文件中的代码实现: * `src/LayoutChangesActivity.java` * `layout/activity_layout_changes.xml` * `menu/activity_layout_changes.xml` ## 创建布局 在Activity的XML布局文件中,为想开启动画的布局设置`android:animateLayoutChanges`属性为`true`。例如: ```xml ``` ## 从布局中添加,更新或删除项目 现在,我们需要做的就是添加,删除或更新布局里的项目,然后这些项目就会自动显示动画: ```java private ViewGroup mContainerView; ... private void addItem() { View newView; ... mContainerView.addView(newView, 0); } ``` ================================================ FILE: animations/screen-slide.md ================================================ # 使用ViewPager实现屏幕滑动 > 编写:[XizhiXu](https://github.com/XizhiXu) - 原文: 屏幕划动是在两个完整界面间的转换,它在一些UI中很常见,比如设置向导和幻灯放映。这节课将告诉你怎样通过[support library](http://developer.android.com/tools/support-library/index.html)提供的[`ViewPager`](http://developer.android.com/reference/android/support/v4/view/ViewPager.html)实现屏幕滑动。[`ViewPager`](http://developer.android.com/reference/android/support/v4/view/ViewPager.html)能自动实现屏幕滑动动画。下面展示了从一个内容界面到一下界面的屏幕滑动转换是什么样子的。
如果你想直接查看整个例子,[下载](http://developer.android.com/shareables/training/Animations.zip)并运行App样例然后选择屏幕滑动例子。查看下列文件中的代码实现: * `src/ScreenSlidePageFragment.java` * `src/ScreenSlideActivity.java` * `layout/activity_screen_slide.xml` * `layout/fragment_screen_slide_page.xml` ##创建View 创建Fragment所使用的布局文件。下面的例子包含一个显示文本的TextView: ```xml ``` 与此同时我们还定义了一个字符串作为该Fragment的内容。 ## 创建Fragment 创建一个 [`Fragment`](http://developer.android.com/reference/android/support/v4/app/Fragment.html) 子类,它从 `onCreateView()` 方法中返回之前创建的布局。无论何时如果我们需要为用户展示一个新的页面,可以在它的父Activity中创建该Fragment的实例: ```java import android.support.v4.app.Fragment; ... public class ScreenSlidePageFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ViewGroup rootView = (ViewGroup) inflater.inflate( R.layout.fragment_screen_slide_page, container, false); return rootView; } } ``` ## 添加ViewPager [`ViewPager`](http://developer.android.com/reference/android/support/v4/view/ViewPager.html) 有内建的滑动手势用来在页面间转换,并且它默认使用滑屏动画,所以我们不用自己为其创建。[`ViewPager`](http://developer.android.com/reference/android/support/v4/view/ViewPager.html)使用[`PagerAdapter`](http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html)来补充新页面,所以[`PagerAdapter`](http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html)会用到你之前新建的Fragment类。 开始之前,创建一个包含[`ViewPager`](http://developer.android.com/reference/android/support/v4/view/ViewPager.html)的布局: ```xml ``` 创建一个Activity来做下面这些事情: * 把ContentView设置成这个包含[`ViewPager`](http://developer.android.com/reference/android/support/v4/view/ViewPager.html)的布局。 * 创建一个继承自[`FragmentStatePagerAdapter `](http://developer.android.com/reference/android/support/v13/app/FragmentStatePagerAdapter.html)抽象类的类,然后实现`getItem()`方法来把`ScreenSlidePageFragment`实例作为新页面补充进来。PagerAdapter还需要实现`getCount()`方法,它返回 Adapter将要创建页面的总数(例如5个)。 * 把[`PagerAdapter`](http://developer.android.com/reference/android/support/v4/view/PagerAdapter.html)和[`ViewPager`](http://developer.android.com/reference/android/support/v4/view/ViewPager.html)关联起来。 * 处理Back按钮,按下变为在虚拟的Fragment栈中回退。如果用户已经在第一个页面了,则在Activity的回退栈(back stack)中回退。 ```java import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; ... public class ScreenSlidePagerActivity extends FragmentActivity { /** * The number of pages (wizard steps) to show in this demo. */ private static final int NUM_PAGES = 5; /** * The pager widget, which handles animation and allows swiping horizontally to access previous * and next wizard steps. */ private ViewPager mPager; /** * The pager adapter, which provides the pages to the view pager widget. */ private PagerAdapter mPagerAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_screen_slide); // Instantiate a ViewPager and a PagerAdapter. mPager = (ViewPager) findViewById(R.id.pager); mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager()); mPager.setAdapter(mPagerAdapter); } @Override public void onBackPressed() { if (mPager.getCurrentItem() == 0) { // If the user is currently looking at the first step, allow the system to handle the // Back button. This calls finish() on this activity and pops the back stack. super.onBackPressed(); } else { // Otherwise, select the previous step. mPager.setCurrentItem(mPager.getCurrentItem() - 1); } } /** * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in * sequence. */ private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter { public ScreenSlidePagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return new ScreenSlidePageFragment(); } @Override public int getCount() { return NUM_PAGES; } } } ``` ## 用PageTransformer自定义动画 要展示不同于默认滑屏效果的动画,我们需要实现[`ViewPager.PageTransformer`](http://developer.android.com/reference/android/support/v4/view/ViewPager.PageTransformer.html)接口,然后把它补充到ViewPager里就行了。这个接口只暴露了一个方法,`transformPage()`。每次界面切换,这个方法都会为每个可见页面(通常只有一个页面可见)和刚消失的相邻页面调用一次。例如,第三页可见而且用户向第四页拖动,`transformPage()`在操作的各个阶段为第二,三,四页分别调用。 在`transformPage()`的实现中,基于当前屏幕显示的页面的`position`(`position` 由`transformPage()`方法的参数给出)决定哪些页面需要被动画转换,这样我们就能创建自己的动画。 `position`参数表示特定页面相对于屏幕中的页面的位置。它的值在用户滑动页面过程中动态变化。当某一页面填充屏幕,它的值为0。当页面刚向屏幕右侧方向被拖走,它的值为1。如果用户在页面1和页面2间滑动到一半,那么页面1的position为-0.5并且页面2的position为 0.5。根据屏幕上页面的position,我们可以通过`setAlpha()``setTranslationX()``setScaleY()`这些方法设定页面属性来自定义滑动动画。 当我们实现了[`PageTransformer`](http://developer.android.com/reference/android/support/v4/view/ViewPager.PageTransformer.html)后,用我们的实现调用`setPageTransformer()`来应用这些自定义动画。例如,如果我们有一个叫做`ZoomOutPageTransformer`的[`PageTransformer`](http://developer.android.com/reference/android/support/v4/view/ViewPager.PageTransformer.html),可以这样设置自定义动画: ```java ViewPager mPager = (ViewPager) findViewById(R.id.pager); ... mPager.setPageTransformer(true, new ZoomOutPageTransformer()); ``` 详情查看[Zoom-out Page Transformer](#Zoom-out Page Transformer)和[Depth Page Transformer](#Depth Page Transformer)部分的 [`PageTransformer`](http://developer.android.com/reference/android/support/v4/view/ViewPager.PageTransformer.html)视频和例子。 ### Zoom-out Page Transformer 当在相邻界面滑动时,这个Page Transformer使页面收缩并褪色。当页面越靠近中心,它将渐渐还原到正常大小并且图像渐入。
```java public class ZoomOutPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.85f; private static final float MIN_ALPHA = 0.5f; public void transformPage(View view, float position) { int pageWidth = view.getWidth(); int pageHeight = view.getHeight(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 1) { // [-1,1] // Modify the default slide transition to shrink the page as well float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); float vertMargin = pageHeight * (1 - scaleFactor) / 2; float horzMargin = pageWidth * (1 - scaleFactor) / 2; if (position < 0) { view.setTranslationX(horzMargin - vertMargin / 2); } else { view.setTranslationX(-horzMargin + vertMargin / 2); } // Scale the page down (between MIN_SCALE and 1) view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); // Fade the page relative to its size. view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } } ``` ### Depth Page Transformer 这个Page Transformer使用默认动画的屏幕左滑动画。但是为右滑使用一种“潜藏”效果的动画。潜藏动画将page淡出,并且线性缩小它。
> **注意:**在潜藏过程中,默认动画(屏幕滑动)是仍旧发生的,所以你必须用负的X平移来抵消它。例如: ```java view.setTranslationX(-1 * view.getWidth() * position); ``` 下面的例子展示了如何抵消默认滑屏动画: ```java public class DepthPageTransformer implements ViewPager.PageTransformer { private static final float MIN_SCALE = 0.75f; public void transformPage(View view, float position) { int pageWidth = view.getWidth(); if (position < -1) { // [-Infinity,-1) // This page is way off-screen to the left. view.setAlpha(0); } else if (position <= 0) { // [-1,0] // Use the default slide transition when moving to the left page view.setAlpha(1); view.setTranslationX(0); view.setScaleX(1); view.setScaleY(1); } else if (position <= 1) { // (0,1] // Fade the page out. view.setAlpha(1 - position); // Counteract the default slide transition view.setTranslationX(pageWidth * -position); // Scale the page down (between MIN_SCALE and 1) float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)); view.setScaleX(scaleFactor); view.setScaleY(scaleFactor); } else { // (1,+Infinity] // This page is way off-screen to the right. view.setAlpha(0); } } } ``` ================================================ FILE: animations/zoom.md ================================================ # 缩放View > 编写:[XizhiXu](https://github.com/XizhiXu) - 原文: 这节课展示怎样实现点击缩放动画,这对相册很有用,他能为相片从缩略图转换成原图并填充屏幕提供动画。 下面展示了触摸缩放动画的效果,它将缩略图扩大并填充屏幕。
如果你想直接查看整个例子,[下载](http://developer.android.com/shareables/training/Animations.zip)并运行App样例然后选择缩放的例子。查看下列文件中的代码实现: * `src/TouchHighlightImageButton.java`(简单的helper类,当Image Button被按下它显示蓝色高亮) * `src/ZoomActivity.java` * `layout/activity_zoom.xml` ## 创建View 为想要缩放的内容创建一大一小两个版本布局文件。下面的例子为可点击的缩略图新建了一个[`ImageButton`](http://developer.android.com/reference/android/widget/ImageButton.html)和一个[`ImageView`](http://developer.android.com/reference/android/widget/ImageView.html)来展示原图: ```xml ``` ## 设置缩放动画 一旦实现了布局,我们需要设置触发缩放动画的事件handler。下面的例子为[`ImageButton`](http://developer.android.com/reference/android/widget/ImageButton.html)添加了一个[`View.OnClickListener`](http://developer.android.com/reference/android/view/View.OnClickListener.html),当用户点击按钮时它执行放大动画。 ```java public class ZoomActivity extends FragmentActivity { // Hold a reference to the current animator, // so that it can be canceled mid-way. private Animator mCurrentAnimator; // The system "short" animation time duration, in milliseconds. This // duration is ideal for subtle animations or animations that occur // very frequently. private int mShortAnimationDuration; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_zoom); // Hook up clicks on the thumbnail views. final View thumb1View = findViewById(R.id.thumb_button_1); thumb1View.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { zoomImageFromThumb(thumb1View, R.drawable.image1); } }); // Retrieve and cache the system's default "short" animation time. mShortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime); } ... } ``` ## 缩放View 我们现在需要适时应用放大动画了。通常来说,我们需要按边界来从小号View放大到大号View。下面的方法展示了如何实现缩放动画: 1. 把高清图像资源设置到已经被隐藏的“放大版”的[`ImageView`](http://developer.android.com/reference/android/widget/ImageView.html)中。为表简单,下面的例子在UI线程中加载了一张大图。但是我们需要在一个单独的线程中来加载以免阻塞UI线程,然后再回到UI线程中设置。理想状况下,图片不要大过屏幕尺寸。 2. 计算[`ImageView`](http://developer.android.com/reference/android/widget/ImageView.html)开始和结束时的边界。 3. 从起始边到结束边同步地动态改变四个位置和大小属性[`X`](http://developer.android.com/reference/android/view/View.html#X),[`Y`](http://developer.android.com/reference/android/view/View.html#Y)([`SCALE_X`](http://developer.android.com/reference/android/view/View.html#SCALE_X) 和 [`SCALE_Y`](http://developer.android.com/reference/android/view/View.html#SCALE_Y))。这四个动画被加入到了[`AnimatorSet`](http://developer.android.com/reference/android/animation/AnimatorSet.html),所以我们可以让它们一起开始。 4. 缩小则运行相同的动画,但是是在用户点击屏幕放大时的逆向效果。我们可以在[`ImageView`](http://developer.android.com/reference/android/widget/ImageView.html)中添加一个[`View.OnClickListener`](http://developer.android.com/reference/android/view/View.OnClickListener.html)来实现它。当点击时,[`ImageView`](http://developer.android.com/reference/android/widget/ImageView.html)缩回到原来缩略图的大小,然后设置它的visibility为[`GONE`](http://developer.android.com/reference/android/view/View.html#GONE)来隐藏。 ```java private void zoomImageFromThumb(final View thumbView, int imageResId) { // If there's an animation in progress, cancel it // immediately and proceed with this one. if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); } // Load the high-resolution "zoomed-in" image. final ImageView expandedImageView = (ImageView) findViewById( R.id.expanded_image); expandedImageView.setImageResource(imageResId); // Calculate the starting and ending bounds for the zoomed-in image. // This step involves lots of math. Yay, math. final Rect startBounds = new Rect(); final Rect finalBounds = new Rect(); final Point globalOffset = new Point(); // The start bounds are the global visible rectangle of the thumbnail, // and the final bounds are the global visible rectangle of the container // view. Also set the container view's offset as the origin for the // bounds, since that's the origin for the positioning animation // properties (X, Y). thumbView.getGlobalVisibleRect(startBounds); findViewById(R.id.container) .getGlobalVisibleRect(finalBounds, globalOffset); startBounds.offset(-globalOffset.x, -globalOffset.y); finalBounds.offset(-globalOffset.x, -globalOffset.y); // Adjust the start bounds to be the same aspect ratio as the final // bounds using the "center crop" technique. This prevents undesirable // stretching during the animation. Also calculate the start scaling // factor (the end scaling factor is always 1.0). float startScale; if ((float) finalBounds.width() / finalBounds.height() > (float) startBounds.width() / startBounds.height()) { // Extend start bounds horizontally startScale = (float) startBounds.height() / finalBounds.height(); float startWidth = startScale * finalBounds.width(); float deltaWidth = (startWidth - startBounds.width()) / 2; startBounds.left -= deltaWidth; startBounds.right += deltaWidth; } else { // Extend start bounds vertically startScale = (float) startBounds.width() / finalBounds.width(); float startHeight = startScale * finalBounds.height(); float deltaHeight = (startHeight - startBounds.height()) / 2; startBounds.top -= deltaHeight; startBounds.bottom += deltaHeight; } // Hide the thumbnail and show the zoomed-in view. When the animation // begins, it will position the zoomed-in view in the place of the // thumbnail. thumbView.setAlpha(0f); expandedImageView.setVisibility(View.VISIBLE); // Set the pivot point for SCALE_X and SCALE_Y transformations // to the top-left corner of the zoomed-in view (the default // is the center of the view). expandedImageView.setPivotX(0f); expandedImageView.setPivotY(0f); // Construct and run the parallel animation of the four translation and // scale properties (X, Y, SCALE_X, and SCALE_Y). AnimatorSet set = new AnimatorSet(); set .play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left, finalBounds.left)) .with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top, finalBounds.top)) .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f)); set.setDuration(mShortAnimationDuration); set.setInterpolator(new DecelerateInterpolator()); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mCurrentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { mCurrentAnimator = null; } }); set.start(); mCurrentAnimator = set; // Upon clicking the zoomed-in image, it should zoom back down // to the original bounds and show the thumbnail instead of // the expanded image. final float startScaleFinal = startScale; expandedImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); } // Animate the four positioning/sizing properties in parallel, // back to their original values. AnimatorSet set = new AnimatorSet(); set.play(ObjectAnimator .ofFloat(expandedImageView, View.X, startBounds.left)) .with(ObjectAnimator .ofFloat(expandedImageView, View.Y,startBounds.top)) .with(ObjectAnimator .ofFloat(expandedImageView, View.SCALE_X, startScaleFinal)) .with(ObjectAnimator .ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal)); set.setDuration(mShortAnimationDuration); set.setInterpolator(new DecelerateInterpolator()); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { thumbView.setAlpha(1f); expandedImageView.setVisibility(View.GONE); mCurrentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { thumbView.setAlpha(1f); expandedImageView.setVisibility(View.GONE); mCurrentAnimator = null; } }); set.start(); mCurrentAnimator = set; } }); } ``` ================================================ FILE: background-jobs/load-data-background/handle-result.md ================================================ # 处理查询的结果 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 正如前面一节课讲到的,你应该在 [onCreateLoader()](1)的回调里面使用CursorLoader执行加载数据的操作。Loader查询完后会调用Activity或者FragmentActivity的[LoaderCallbacks.onLoadFinished()](2)将结果回调回来。这个回调方法的参数之一是[Cursor](4),它包含了查询的数据。你可以使用Cursor对象来更新需要显示的数据或者进行下一步的处理。 除了[onCreateLoader()](1)与[onLoadFinished()](2),你也需要实现[onLoaderReset()](3)。这个方法在CursorLoader检测到[Cursor](4)上的数据发生变化的时候会被触发。当数据发生变化时,系统也会触发重新查询的操作。 ## 处理查询结果 为了显示CursorLoader返回的Cursor数据,需要使用实现AdapterView的视图组件,,并为这个组件绑定一个实现了CursorAdapter的Adapter。系统会自动把Cursor中的数据显示到View上。 你可以在显示数据之前建立View与Adapter的关联。然后在[onLoadFinished()](2)的时候把Cursor与Adapter进行绑定。一旦你把Cursor与Adapter进行绑定之后,系统会自动更新View。当Cursor上的内容发生改变的时候,也会触发这些操作。 例如: ```java public String[] mFromColumns = { DataProviderContract.IMAGE_PICTURENAME_COLUMN }; public int[] mToFields = { R.id.PictureName }; // Gets a handle to a List View ListView mListView = (ListView) findViewById(R.id.dataList); /* * Defines a SimpleCursorAdapter for the ListView * */ SimpleCursorAdapter mAdapter = new SimpleCursorAdapter( this, // Current context R.layout.list_item, // Layout for a single row null, // No Cursor yet mFromColumns, // Cursor columns to use mToFields, // Layout fields to use 0 // No flags ); // Sets the adapter for the view mListView.setAdapter(mAdapter); ... /* * Defines the callback that CursorLoader calls * when it's finished its query */ @Override public void onLoadFinished(Loader loader, Cursor cursor) { ... /* * Moves the query results into the adapter, causing the * ListView fronting this adapter to re-display */ mAdapter.changeCursor(cursor); } ``` ## 删除废旧的Cursor引用 当Cursor失效的时候,CursorLoader会被重置。这通常发生在Cursor相关的数据改变的时候。在重新执行查询操作之前,系统会执行你的[onLoaderReset()](3)回调方法。在这个回调方法中,你应该删除当前Cursor上的所有数据,避免发生内存泄露。一旦onLoaderReset()执行结束,CursorLoader就会重新执行查询操作。 例如: ```java /* * Invoked when the CursorLoader is being reset. For example, this is * called if the data in the provider changes and the Cursor becomes stale. */ @Override public void onLoaderReset(Loader loader) { /* * Clears out the adapter's reference to the Cursor. * This prevents memory leaks. */ mAdapter.changeCursor(null); } ``` *** [1]: http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html "onCreateLoader()" [2]: http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html "onLoadFinished()" [3]: http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html "onLoaderReset()" [4]: http://developer.android.com/reference/android/database/Cursor.html "Cursor" ================================================ FILE: background-jobs/load-data-background/index.md ================================================ # 使用CursorLoader在后台加载数据 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 从[ContentProvider](http://developer.android.com/reference/android/content/ContentProvider.html)查询你需要显示的数据是比较耗时的。如果你在Activity中直接执行查询的操作,那么有可能导致Activity出现ANR的错误。即使没有发生ANR,用户也容易感知到一个令人烦恼的UI卡顿。为了避免那些问题,你应该在另外一个线程中执行查询的操作,等待查询操作完成,然后再显示查询结果。 通过[CursorLoader](http://developer.android.com/reference/android/support/v4/content/CursorLoader.html)对象,你可以用一种简单的方式实现异步查询,查询结束时它会和Activity进行重新连接。 CursorLoader不仅仅能够实现在后台查询数据,还能够在查询数据发生变化时自动执行重新查询的操作。 这节课会介绍如何使用CursorLoader来执行一个后台查询数据的操作。在这节课中的演示代码使用的是[v4 Support Library](http://developer.android.com/tools/support-library/features.html#v4)中的类。 ## Demos ** [ThreadSample](http://developer.android.com/shareables/training/ThreadSample.zip) ** ## Lessons * [使用CursorLoader执行查询任务](setup-loader.html) 学习如何使用CursorLoader在后台执行查询操作。 * [处理CursorLoader查询的结果](handle-result.html) 学习如何处理从CursorLoader查询到的数据,以及在loader框架重置CursorLoader时如何解除当前Cursor的引用。 ================================================ FILE: background-jobs/load-data-background/setup-loader.md ================================================ # 使用CursorLoader执行查询任务 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: CursorLoader通过ContentProvider在后台执行一个异步的查询操作,并且返回数据给调用它的Activity或者FragmentActivity。这使得Activity或者FragmentActivity能够在查询任务正在执行的同时继续与用户进行其他的交互操作。 ## 定义使用CursorLoader的Activity 为了在Activity或者FragmentActivity中使用CursorLoader,它们需要实现`LoaderCallbacks`接口。CursorLoader会调用`LoaderCallbacks`定义的这些回调方法与Activity进行交互;这节课与下节课会详细介绍每一个回调方法。 例如,下面演示了FragmentActivity如何使用CursorLoader。 ```java public class PhotoThumbnailFragment extends FragmentActivity implements LoaderManager.LoaderCallbacks { ... } ``` ## 初始化查询 为了初始化查询,需要调用`LoaderManager.initLoader()`。这个方法可以初始化LoaderManager的后台查询框架。你可以在用户输入查询条件之后触发初始化的操作,如果你不需要用户输入数据作为查询条件,你可以在`onCreate()`或者`onCreateView()`里面触发这个方法。例如: ```java // Identifies a particular Loader being used in this component private static final int URL_LOADER = 0; ... /* When the system is ready for the Fragment to appear, this displays * the Fragment's View */ public View onCreateView( LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { ... /* * Initializes the CursorLoader. The URL_LOADER value is eventually passed * to onCreateLoader(). */ getLoaderManager().initLoader(URL_LOADER, null, this); ... } ``` > **Note:** `getLoaderManager()`仅仅是在Fragment类中可以直接访问。为了在FragmentActivity中获取到LoaderManager,需要执行`getSupportLoaderManager()`. ## 开始查询 一旦后台任务被初始化好,它会执行你实现的回调方法`onCreateLoader()`。为了启动查询任务,会在这个方法里面返回CursorLoader。你可以初始化一个空的CursorLoader然后使用它的方法来定义你的查询条件,或者你可以在初始化CursorLoader对象的时候就同时定义好查询条件: ```java /* * Callback that's invoked when the system has initialized the Loader and * is ready to start the query. This usually happens when initLoader() is * called. The loaderID argument contains the ID value passed to the * initLoader() call. */ @Override public Loader onCreateLoader(int loaderID, Bundle bundle) { /* * Takes action based on the ID of the Loader that's being created */ switch (loaderID) { case URL_LOADER: // Returns a new CursorLoader return new CursorLoader( getActivity(), // Parent activity context mDataUrl, // Table to query mProjection, // Projection to return null, // No selection clause null, // No selection arguments null // Default sort order ); default: // An invalid id was passed in return null; } } ``` 一旦后台查询任务获取到了这个Loader对象,就开始在后台执行查询的任务。当查询完成之后,会执行`onLoadFinished()`这个回调函数,关于这些内容会在下一节讲到。 ================================================ FILE: background-jobs/run-background-service/create-service.md ================================================ # 创建后台服务 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: IntentService为在单一后台线程中执行任务提供了一种直接的实现方式。它可以处理一个耗时的任务并确保不影响到UI的响应性。另外IntentService的执行还不受UI生命周期的影响,以此来确保AsyncTask能够顺利运行。 但是IntentService有下面几个局限性: * 不可以直接和UI做交互。为了把他执行的结果体现在UI上,需要把结果返回给Activity。 * 工作任务队列是顺序执行的,如果一个任务正在IntentService中执行,此时你再发送一个新的任务请求,这个新的任务会一直等待直到前面一个任务执行完毕才开始执行。 * 正在执行的任务无法打断。 虽然有上面那些限制,然而在在大多数情况下,IntentService都是执行简单后台任务操作的理想选择。 这节课会演示如何创建继承的IntentService。同样也会演示如何创建必须的回调方法`onHandleIntent()`。最后,还会解释如何在manifest文件中定义这个IntentService。 ## 1)创建IntentService 为你的app创建一个IntentService组件,需要自定义一个新的类,它继承自IntentService,并重写onHandleIntent()方法,如下所示: ```java public class RSSPullService extends IntentService { @Override protected void onHandleIntent(Intent workIntent) { // Gets data from the incoming Intent String dataString = workIntent.getDataString(); ... // Do work here, based on the contents of dataString ... } } ``` 注意一个普通Service组件的其他回调,例如`onStartCommand()`会被IntentService自动调用。在IntentService中,要避免重写那些回调。 ## 2)在Manifest文件中定义IntentService IntentService需要在manifest文件添加相应的条目,将此条目``作为``元素的子元素下进行定义,如下所示: ```xml ... ... ``` `android:name`属性指明了IntentService的名字。 注意``标签并没有包含任何intent filter。因为发送任务给IntentService的Activity需要使用显式Intent,所以不需要filter。这也意味着只有在同一个app或者其他使用同一个UserID的组件才能够访问到这个Service。 至此,你已经有了一个基本的IntentService类,你可以通过构造Intent对象向它发送操作请求。构造这些对象以及发送它们到你的IntentService的方式,将在接下来的课程中描述。 ================================================ FILE: background-jobs/run-background-service/index.md ================================================ # 在IntentService中执行后台任务 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 除非我们特别为某个操作指定特定的线程,否则大部分在前台UI界面上的操作任务都执行在一个叫做UI Thread的特殊线程中。这可能存在某些隐患,因为部分在UI界面上的耗时操作可能会影响界面的响应性能。UI界面的性能问题会容易惹恼用户,甚至可能导致系统ANR错误。为了避免这样的问题,Android Framework提供了几个类,用来帮助你把那些耗时操作移动到后台线程中执行。那些类中最常用的就是[IntentService](http://developer.android.com/reference/android/app/IntentService.html). 这一章节会讲到如何实现一个IntentService,向它发送任务并反馈任务的结果给其他模块。 ## Demos [**ThreadSample.zip**](http://developer.android.com/shareables/training/ThreadSample.zip) ## Lessons * [创建IntentService](create-service.html) 学习如何创建一个IntentService。 * [发送任务请求给IntentService](send-request.html) 学习如何发送工作任务给IntentService。 * [报告后台任务的执行状态](report-status.html) 学习如何使用Intent与LocalBroadcastManager在Activit与IntentService之间进行交互。 ================================================ FILE: background-jobs/run-background-service/report-status.md ================================================ # 报告任务执行状态 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 这章节会演示如何回传IntentService中执行的任务状态与结果给发送方。 例如,回传任务的执行状态给Activity并进行更新UI。推荐的方式是使用[LocalBroadcastManager](http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html),这个组件可以限制broadcast intent只在自己的app中进行传递。 ## 利用IntentService 发送任务状态 为了在IntentService中向其他组件发送任务状态,首先创建一个Intent并在data字段中包含需要传递的信息。作为一个可选项,还可以给这个Intent添加一个action与data URI。 下一步,通过执行`LocalBroadcastManager.sendBroadcast()` 来发送Intent。Intent被发送到任何有注册接受它的组件中。为了获取到LocalBroadcastManager的实例,可以执行getInstance()。代码示例如下: ```java public final class Constants { ... // Defines a custom Intent action public static final String BROADCAST_ACTION = "com.example.android.threadsample.BROADCAST"; ... // Defines the key for the status "extra" in an Intent public static final String EXTENDED_DATA_STATUS = "com.example.android.threadsample.STATUS"; ... } public class RSSPullService extends IntentService { ... /* * Creates a new Intent containing a Uri object * BROADCAST_ACTION is a custom Intent action */ Intent localIntent = new Intent(Constants.BROADCAST_ACTION) // Puts the status into the Intent .putExtra(Constants.EXTENDED_DATA_STATUS, status); // Broadcasts the Intent to receivers in this app. LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent); ... } ``` 下一步是在发送任务的组件中接收发送出来的broadcast数据。 ## 接收来自IntentService的状态广播 为了接受广播的数据对象,需要使用BroadcastReceiver的子类并实现`BroadcastReceiver.onReceive()` 的方法,这里可以接收LocalBroadcastManager发出的广播数据。 ```java // Broadcast receiver for receiving status updates from the IntentService private class ResponseReceiver extends BroadcastReceiver { // Prevents instantiation private DownloadStateReceiver() { } // Called when the BroadcastReceiver gets an Intent it's registered to receive @ public void onReceive(Context context, Intent intent) { ... /* * Handle Intents here. */ ... } } ``` 一旦定义了BroadcastReceiver,也应该定义actions,categories与data用过滤广播。为了实现这些,需要使用[IntentFilter](http://developer.android.com/reference/android/content/IntentFilter.html)。如下所示: ```java // Class that displays photos public class DisplayActivity extends FragmentActivity { ... public void onCreate(Bundle stateBundle) { ... super.onCreate(stateBundle); ... // The filter's action is BROADCAST_ACTION IntentFilter mStatusIntentFilter = new IntentFilter( Constants.BROADCAST_ACTION); // Adds a data filter for the HTTP scheme mStatusIntentFilter.addDataScheme("http"); ... ``` 为了给系统注册这个BroadcastReceiver和IntentFilter,需要通过LocalBroadcastManager执行registerReceiver()的方法。如下所示: ```java // Instantiates a new DownloadStateReceiver DownloadStateReceiver mDownloadStateReceiver = new DownloadStateReceiver(); // Registers the DownloadStateReceiver and its intent filters LocalBroadcastManager.getInstance(this).registerReceiver( mDownloadStateReceiver, mStatusIntentFilter); ... ``` 一个BroadcastReceiver可以处理多种类型的广播数据。每个广播数据都有自己的ACTION。这个功能使得不用定义多个不同的BroadcastReceiver来分别处理不同的ACTION数据。为BroadcastReceiver定义另外一个IntentFilter,只需要创建一个新的IntentFilter并重复执行registerReceiver()即可。例如: ```java /* * Instantiates a new action filter. * No data filter is needed. */ statusIntentFilter = new IntentFilter(Constants.ACTION_ZOOM_IMAGE); ... // Registers the receiver with the new filter LocalBroadcastManager.getInstance(getActivity()).registerReceiver( mDownloadStateReceiver, mIntentFilter); ``` 发送一个广播Intent并不会启动或重启一个Activity。即使是你的app在后台运行,Activity的BroadcastReceiver也可以接收、处理Intent对象。但是这不会迫使你的app进入前台。当你的app不可见时,如果想通知用户一个发生在后台的事件,建议使用[Notification](http://developer.android.com/reference/android/app/Notification.html)。**永远**不要为了响应一个广播Intent而去启动Activity。 ================================================ FILE: background-jobs/run-background-service/send-request.md ================================================ # 向后台服务发送任务请求 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 前一篇文章演示了如何创建一个IntentService类。这次会演示如何通过发送一个Intent来触发IntentService执行任务。这个Intent可以传递一些数据给IntentService。我们可以在Activity或者Fragment的任何时间点发送这个Intent。 ## 创建任务请求并发送到IntentService 为了创建一个任务请求并发送到IntentService。需要先创建一个显式Intent,并将请求数据添加到intent中,然后通过调用 `startService()` 方法把任务请求数据发送到IntentService。 下面的是代码示例: * 创建一个新的显式Intent用来启动IntentService。 ```java /* * Creates a new Intent to start the RSSPullService * IntentService. Passes a URI in the * Intent's "data" field. */ mServiceIntent = new Intent(getActivity(), RSSPullService.class); mServiceIntent.setData(Uri.parse(dataUrl)); ``` * 执行`startService()` ```java // Starts the IntentService getActivity().startService(mServiceIntent); ``` 注意可以在Activity或者Fragment的任何位置发送任务请求。例如,如果你先获取用户输入,您可以从响应按钮单击或类似手势的回调方法里面发送任务请求。 一旦执行了startService(),IntentService在自己本身的`onHandleIntent()`方法里面开始执行这个任务,任务结束之后,会自动停止这个Service。 下一步是如何把工作任务的执行结果返回给发送任务的Activity或者Fragment。下节课会演示如何使用[BroadcastReceiver](http://developer.android.com/reference/android/content/BroadcastReceiver.html)来完成这个任务。 ================================================ FILE: background-jobs/scheduling/alarms.md ================================================ # 调度重复的闹钟 > 编写:[jdneo](https://github.com/jdneo) - 原文: 闹钟(基于[AlarmManager](https://developer.android.com/reference/android/app/AlarmManager.html)类)给予你一种在应用使用期之外执行与时间相关的操作的方法。你可以使用闹钟初始化一个长时间的操作,例如每天开启一次后台服务,下载当日的天气预报。 闹钟具有如下特性: * 允许你通过预设时间或者设定某个时间间隔,来触发Intent; * 你可以将它与BroadcastReceiver相结合,来启动服务并执行其他操作; * 可在应用范围之外执行,所以你可以在你的应用没有运行或设备处于睡眠状态的情况下,使用它来触发事件或行为; * 帮助你的应用最小化资源需求,你可以使用闹钟调度你的任务,来替代计时器或者长时间连续运行的后台服务。 > **Note**:对于那些需要确保在应用使用期之内发生的定时操作,可以使用闹钟替代使用[Handler](https://developer.android.com/reference/android/os/Handler.html)结合[Timer](https://developer.android.com/reference/java/util/Timer.html)与[Thread](https://developer.android.com/reference/java/lang/Thread.html)的方法。因为它可以让Android系统更好地统筹系统资源。 ## 权衡利弊 重复闹钟的机制比较简单,没有太多的灵活性。它对于你的应用来说或许不是一种最好的选择,特别是当你想要触发网络操作的时候。设计不佳的闹钟会导致电量快速耗尽,而且会对服务端产生巨大的负荷。 当我们从服务端同步数据时,往往会在应用不被使用的时候时被唤醒触发执行某些操作。此时你可能希望使用重复闹钟。但是如果存储数据的服务端是由你控制的,使用[Google Cloud Messaging](https://developer.android.com/google/gcm/index.html)(GCM)结合[sync adapter](https://developer.android.com/training/sync-adapters/index.html)是一种更好解决方案。SyncAdapter提供的任务调度选项和[AlarmManager](https://developer.android.com/reference/android/app/AlarmManager.html)基本相同,但是它能提供更多的灵活性。比如:同步的触发可能基于一条“新数据”提示消息,而消息的产生可以基于服务器或设备,用户的操作(或者没有操作),每天的某一时刻等等。 ### 最佳实践方法 在设计重复闹钟过程中,你所做出的每一个决定都有可能影响到你的应用将会如何使用系统资源。例如,我们假想一个会从服务器同步数据的应用。同步操作基于的是时钟时间,具体来说,每一个应用的实例会在下午十一点整进行同步,巨大的服务器负荷会导致服务器响应时间变长,甚至拒绝服务。因此在我们使用闹钟时,请牢记下面的最佳实践建议: * 对任何由重复闹钟触发的网络请求添加一定的随机性(抖动): * 在闹钟触发时做一些本地任务。“本地任务”指的是任何不需要访问服务器或者从服务器获取数据的任务; * 同时对于那些包含有网络请求的闹钟,在调度时机上增加一些随机性。 * 尽量让你的闹钟频率最小; * 如果不是必要的情况,不要唤醒设备(这一点与闹钟的类型有关,本节课后续部分会提到); * 触发闹钟的时间不必过度精确; 尽量使用`setInexactRepeating()`方法替代`setRepeating()`方法。当你使用`setInexactRepeating()`方法时,Android系统会集中多个应用的重复闹钟同步请求,并一起触发它们。这可以减少系统将设备唤醒的总次数,以此减少电量消耗。从Android 4.4(API Level19)开始,所有的重复闹钟都将是非精确型的。注意虽然`setInexactRepeating()`是`setRepeating()`的改进版本,它依然可能会导致每一个应用的实例在某一时间段内同时访问服务器,造成服务器负荷过重。因此如之前所述,对于网络请求,我们需要为闹钟的触发时机增加随机性。 * 尽量避免让闹钟基于时钟时间。 想要在某一个精确时刻触发重复闹钟是比较困难的。我们应该尽可能使用[ELAPSED_REALTIME](https://developer.android.com/reference/android/app/AlarmManager.html#ELAPSED_REALTIME)。不同的闹钟类型会在本节课后半部分展开。 ## 设置重复闹钟 如上所述,对于定期执行的任务或者数据查询而言,使用重复闹钟是一个不错的选择。它具有下列属性: * 闹钟类型(后续章节中会展开讨论); * 触发时间。如果触发时间是过去的某个时间点,闹钟会立即被触发; * 闹钟间隔时间。例如,一天一次,每小时一次,每五秒一次,等等; * 在闹钟被触发时才被发出的Pending Intent。如果你为同一个Pending Intent设置了另一个闹钟,那么它会将第一个闹钟覆盖。 ### 选择闹钟类型 使用重复闹钟要考虑的第一件事情是闹钟的类型。 闹钟类型有两大类:`ELAPSED_REALTIME`和`REAL_TIME_CLOCK`(RTC)。`ELAPSED_REALTIME`从系统启动之后开始计算,`REAL_TIME_CLOCK`使用的是世界统一时间(UTC)。也就是说由于`ELAPSED_REALTIME`不受地区和时区的影响,所以它适合于基于时间差的闹钟(例如一个每过30秒触发一次的闹钟)。`REAL_TIME_CLOCK`适合于那些依赖于地区位置的闹钟。 两种类型的闹钟都还有一个唤醒(`WAKEUP`)版本,也就是可以在设备屏幕关闭的时候唤醒CPU。这可以确保闹钟会在既定的时间被激活,这对于那些实时性要求比较高的应用(比如含有一些对执行时间有要求的操作)来说非常有效。如果你没有使用唤醒版本的闹钟,那么所有的重复闹钟会在下一次设备被唤醒时被激活。 如果你只是简单的希望闹钟在一个特定的时间间隔被激活(例如每半小时一次),那么你可以使用任意一种`ELAPSED_REALTIME`类型的闹钟,通常这会是一个更好的选择。 如果你的闹钟是在每一天的特定时间被激活,那么你可以选择`REAL_TIME_CLOCK`类型的闹钟。不过需要注意的是,这个方法会有一些缺陷——如果地区发生了变化,应用可能无法做出正确的改变;另外,如果用户改变了设备的时间设置,这可能会造成应用产生预期之外的行为。使用`REAL_TIME_CLOCK`类型的闹钟还会有精度的问题,因此我们建议你尽可能使用`ELAPSED_REALTIME`类型。 下面列出闹钟的具体类型: * [ELAPSED_REALTIME](https://developer.android.com/reference/android/app/AlarmManager.html#ELAPSED_REALTIME):从设备启动之后开始算起,度过了某一段特定时间后,激活Pending Intent,但不会唤醒设备。其中设备睡眠的时间也会包含在内。 * [ELAPSED_REALTIME_WAKEUP](https://developer.android.com/reference/android/app/AlarmManager.html#ELAPSED_REALTIME_WAKEUP):从设备启动之后开始算起,度过了某一段特定时间后唤醒设备。 * [RTC](https://developer.android.com/reference/android/app/AlarmManager.html#RTC):在某一个特定时刻激活Pending Intent,但不会唤醒设备。 * [RTC_WAKEUP](https://developer.android.com/reference/android/app/AlarmManager.html#RTC_WAKEUP):在某一个特定时刻唤醒设备并激活Pending Intent。 ### ELAPSED_REALTIME_WAKEUP案例 下面是使用[ELAPSED_REALTIME_WAKEUP](https://developer.android.com/reference/android/app/AlarmManager.html#ELAPSED_REALTIME_WAKEUP)的例子。 每隔在30分钟后唤醒设备以激活闹钟: ```java // Hopefully your alarm will have a lower frequency than this! alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, AlarmManager.INTERVAL_HALF_HOUR, AlarmManager.INTERVAL_HALF_HOUR, alarmIntent); ``` 在一分钟后唤醒设备并激活一个一次性(无重复)闹钟: ```java private AlarmManager alarmMgr; private PendingIntent alarmIntent; ... alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, AlarmReceiver.class); alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0); alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60 * 1000, alarmIntent); ``` ### RTC案例 下面是使用[RTC_WAKEUP](https://developer.android.com/reference/android/app/AlarmManager.html#RTC_WAKEUP)的例子。 在大约下午2点唤醒设备并激活闹钟,并不断重复: ```java // Set the alarm to start at approximately 2:00 p.m. Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, 14); // With setInexactRepeating(), you have to use one of the AlarmManager interval // constants--in this case, AlarmManager.INTERVAL_DAY. alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, alarmIntent); ``` 让设备精确地在上午8点半被唤醒并激活闹钟,自此之后每20分钟唤醒一次: ```java private AlarmManager alarmMgr; private PendingIntent alarmIntent; ... alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, AlarmReceiver.class); alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0); // Set the alarm to start at 8:30 a.m. Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, 8); calendar.set(Calendar.MINUTE, 30); // setRepeating() lets you specify a precise custom interval--in this case, // 20 minutes. alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 1000 * 60 * 20, alarmIntent); ``` ### 决定闹钟的精确度 如上所述,创建闹钟的第一步是要选择闹钟的类型,然后你需要决定闹钟的精确度。对于大多数应用而言,`setInexactRepeating()`会是一个正确的选择。当你使用该方法时,Android系统会集中多个应用的重复闹钟同步请求,并一起触发它们。这样可以减少电量的损耗。 对于另一些实时性要求较高的应用——例如,闹钟需要精确地在上午8点半被激活,并且自此之后每隔1小时激活一次——那么可以使用`setRepeating()`。不过你应该尽量避免使用精确的闹钟。 使用`setRepeating()`时,你可以制定一个自定义的时间间隔,但在使用`setInexactRepeating()`时不支持这么做。此时你只能选择一些时间间隔常量,例如:[INTERVAL_FIFTEEN_MINUTES](https://developer.android.com/reference/android/app/AlarmManager.html#INTERVAL_FIFTEEN_MINUTES) ,[INTERVAL_DAY](http://developer.android.com/reference/android/app/AlarmManager.html#INTERVAL_DAY)等。完整的常量列表,可以查看[AlarmManager](https://developer.android.com/reference/android/app/AlarmManager.html)。 ### 取消闹钟 你可能希望在应用中添加取消闹钟的功能。要取消闹钟,可以调用AlarmManager的`cancel()`方法,并把你不想激活的[PendingIntent](https://developer.android.com/reference/android/app/PendingIntent.html)传递进去,例如: ```java // If the alarm has been set, cancel it. if (alarmMgr!= null) { alarmMgr.cancel(alarmIntent); } ``` ###在设备启动后启用闹钟 默认情况下,所有的闹钟会在设备关闭时被取消。要防止闹钟被取消,你可以让你的应用在用户重启设备后自动重启一个重复闹钟。这样可以让[AlarmManager](https://developer.android.com/reference/android/app/AlarmManager.html)继续执行它的工作,且不需要用户手动重启闹钟。 具体步骤如下: 1.在应用的Manifest文件中设置[RECEIVE_BOOT_CMPLETED](https://developer.android.com/reference/android/Manifest.permission.html#RECEIVE_BOOT_COMPLETED)权限,这将允许你的应用接收系统启动完成后发出的[ACTION_BOOT_COMPLETED](https://developer.android.com/reference/android/content/Intent.html#ACTION_BOOT_COMPLETED)广播(只有在用户至少将你的应用启动了一次后,这样做才有效): ```xml ``` 2.实现[BoradcastReceiver](https://developer.android.com/reference/android/content/BroadcastReceiver.html)用于接收广播: ```java public class SampleBootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) { // Set the alarm here. } } } ``` 3.在你的Manifest文件中添加一个接收器,其Intent-Filter接收[ACTION_BOOT_COMPLETED](https://developer.android.com/reference/android/content/Intent.html#ACTION_BOOT_COMPLETED)这一Action: ```xml ``` 注意Manifest文件中,对接收器设置了`android:enabled="false"`属性。这意味着除非应用显式地启用它,不然该接收器将不被调用。这可以防止接收器被不必要地调用。你可以像下面这样启动接收器(比如用户设置了一个闹钟): ```java ComponentName receiver = new ComponentName(context, SampleBootReceiver.class); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); ``` 一旦你像上面那样启动了接收器,它将一直保持启动状态,即使用户重启了设备也不例外。换句话说,通过代码设置的启用配置将会覆盖掉Manifest文件中的现有配置,即使重启也不例外。接收器将保持启动状态,直到你的应用将其禁用。你可以像下面这样禁用接收器(比如用户取消了一个闹钟): ```java ComponentName receiver = new ComponentName(context, SampleBootReceiver.class); PackageManager pm = context.getPackageManager(); pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); ``` ================================================ FILE: background-jobs/scheduling/index.md ================================================ # 管理设备的唤醒状态 > 编写:[jdneo](https://github.com/jdneo),[lttowq](https://github.com/lttowq) - 原文: 当一个Android设备闲置时,首先它的屏幕将会变暗,然后关闭屏幕,最后关闭CPU。 这样可以防止设备的电量被迅速消耗殆尽。但是,有时候也会存在一些特例: * 例如游戏或视频应用需要保持屏幕常亮; * 其它应用也许不需要屏幕常亮,但或许会需要CPU保持运行,直到某个关键操作结束。 这节课描述如何在必要的时候保持设备唤醒,同时又不会过多消耗它的电量。 ## Demos [**Scheduler.zip**](http://developer.android.com/shareables/training/Scheduler.zip) ## Lessons ### [保持设备唤醒](wake-lock.html) 学习如何在必要的时候保持屏幕和CPU唤醒,同时减少对电池寿命的影响。 ### [调度重复闹钟](alarms.html) 对于那些发生在应用生命周期之外的操作,学习如何使用重复闹钟对它们进行调度,即使该应用没有运行或者设备处于睡眠状态。 ================================================ FILE: background-jobs/scheduling/wake-lock.md ================================================ # 保持设备唤醒 > 编写:[jdneo](https://github.com/jdneo) - 原文: 为了避免电量过度消耗,Android设备会在被闲置之后迅速进入睡眠状态。然而有时候应用会需要唤醒屏幕或者是唤醒CPU并且保持它们的唤醒状态,直至一些任务被完成。 想要做到这一点,所采取的方法依赖于应用的具体需求。但是通常来说,我们应该使用最轻量级的方法,减小其对系统资源的影响。在接下来的部分中,我们将会描述在设备默认的睡眠行为与应用的需求不相符合的情况下,我们应该如何进行对应的处理。 ## 保持屏幕常亮 某些应用需要保持屏幕常亮,比如游戏与视频应用。最好的方式是在你的Activity中(且仅在Activity中,而不是在Service或其他应用组件里)使用[FLAG_KEEP_SCREEN_ON](https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON)属性,例如: ```java public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } ``` 该方法的优点与唤醒锁(Wake Locks)不同(唤醒锁的内容在本章节后半部分),它不需要任何特殊的权限,系统会正确地 管理应用之间的切换,且不必关心释放资源的问题。 另外一种方法是在应用的XML布局文件里,使用[android:keepScreenOn](https://developer.android.com/reference/android/R.attr.html#keepScreenOn)属性: ```java ... ``` 使用`android:keepScreenOn="true"`与使用[FLAG_KEEP_SCRRE_ON](https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON)等效。你可以选择最适合你的应用的方法。在Activity中通过代码设置常亮标识的优点在于:你可以通过代码动态清除这个标示,从而使屏幕可以关闭。 > **Notes**:除非你不再希望正在运行的应用长时间点亮屏幕(例如:在一定时间无操作发生后,你想要将屏幕关闭),否则你是不需要清除[FLAG_KEEP_SCRRE_ON](https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON) 标识的。WindowManager会在应用进入后台或者返回前台时,正确管理屏幕的点亮或者关闭。但是如果你想要显式地清除这一标识,从而使得屏幕能够关闭,可以使用`getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)`方法。 ## 保持CPU运行 如果你需要在设备睡眠之前,保持CPU运行来完成一些工作,你可以使用[PowerManager](https://developer.android.com/reference/android/os/PowerManager.html)系统服务中的唤醒锁功能。唤醒锁允许应用控制设备的电源状态。 创建和保持唤醒锁会对设备的电源寿命产生巨大影响。因此你应该仅在你确实需要时使用唤醒锁,且使用的时间应该越短越好。如果想要在Activity中使用唤醒锁就显得没有必要了。如上所述,可以在Activity中使用[FLAG_KEEP_SCRRE_ON](https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_KEEP_SCREEN_ON)让屏幕保持常亮。 使用唤醒锁的一种合理情况可能是:一个后台服务需要在屏幕关闭时利用唤醒锁保持CPU运行。再次强调,应该尽可能规避使用该方法,因为它会影响到电池寿命。 > **不必使用唤醒锁的情况**: > 1. 如果你的应用正在执行一个HTTP长连接的下载任务,可以考虑使用[DownloadManager](http://developer.android.com/reference/android/app/DownloadManager.html)。 > 2. 如果你的应用正在从一个外部服务器同步数据,可以考虑创建一个[SyncAdapter](http://developer.android.com/training/sync-adapters/index.html) > 3. 如果你的应用需要依赖于某些后台服务,可以考虑使用[RepeatingAlarm](http://developer.android.com/training/scheduling/alarms.html)或者[Google Cloud Messaging](http://developer.android.com/google/gcm/index.html),以此每隔特定的时间,将这些服务激活。 为了使用唤醒锁,首先需要在应用的Manifest清单文件中增加[WAKE_LOCK](https://developer.android.com/reference/android/Manifest.permission.html#WAKE_LOCK)权限: ```xml ``` 如果你的应用包含一个BroadcastReceiver并使用Service来完成一些工作,你可以通过[WakefulBroadcastReceiver](https://developer.android.com/reference/android/support/v4/content/WakefulBroadcastReceiver.html)管理你唤醒锁。后续章节中将会提到,这是一种推荐的方法。如果你的应用不满足上述情况,可以使用下面的方法直接设置唤醒锁: ```java PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); Wakelock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag"); wakeLock.acquire(); ``` 可以调用`wakelock.release()`来释放唤醒锁。当应用使用完毕时,应该释放该唤醒锁,以避免电量过度消耗。 ### 使用WakefulBroadcastReceiver 你可以将BroadcastReceiver和Service结合使用,以此来管理后台任务的生命周期。[WakefulBroadcastReceiver](https://developer.android.com/reference/android/support/v4/content/WakefulBroadcastReceiver.html)是一种特殊的BroadcastReceiver,它专注于创建和管理应用的[PARTIAL_WAKE_LOCK](https://developer.android.com/reference/android/os/PowerManager.html#PARTIAL_WAKE_LOCK)。WakefulBroadcastReceiver会将任务交付给[Service](https://developer.android.com/reference/android/app/Service.html)(一般会是一个[IntentService](https://developer.android.com/reference/android/app/IntentService.html)),同时确保设备在此过程中不会进入睡眠状态。如果在该过程当中没有保持住唤醒锁,那么还没等任务完成,设备就有可能进入睡眠状态了。其结果就是:应用可能会在未来的某一个时间节点才把任务完成,这显然不是你所期望的。 要使用WakefulBroadcastReceiver,首先在Manifest文件添加一个标签: ```xml ``` 下面的代码通过`startWakefulService()`启动`MyIntentService`。该方法和`startService()`类似,除了WakeflBroadcastReceiver会在Service启动后将唤醒锁保持住。传递给`startWakefulService()`的Intent会携带有一个Extra数据,用来标识唤醒锁。 ```java public class MyWakefulReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // Start the service, keeping the device awake while the service is // launching. This is the Intent to deliver to the service. Intent service = new Intent(context, MyIntentService.class); startWakefulService(context, service); } } ``` 当Service结束之后,它会调用`MyWakefulReceiver.completeWakefulIntent()`来释放唤醒锁。`completeWakefulIntent()`方法中的Intent参数是和WakefulBroadcastReceiver传递进来的Intent参数一致的: ```java public class MyIntentService extends IntentService { public static final int NOTIFICATION_ID = 1; private NotificationManager mNotificationManager; NotificationCompat.Builder builder; public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { Bundle extras = intent.getExtras(); // Do the work that requires your app to keep the CPU running. // ... // Release the wake lock provided by the WakefulBroadcastReceiver. MyWakefulReceiver.completeWakefulIntent(intent); } } ``` ================================================ FILE: basics/actionbar/adding-buttons.md ================================================ # 添加Action按钮 > 编写:[Vincent 4J](http://github.com/vincent4j) - 原文: Action bar 允许我们为当前环境下最重要的操作添加按钮。那些直接出现在 action bar 中的 icon 和/或文本被称作**action buttons(操作按钮)**。安排不下的或不足够重要的操作被隐藏在 **action overflow** (超出空间的action,译者注)中。 ![actionbar-actions](actionbar-actions.png) 图 1. 一个有search操作按钮和 action overflow 的 action bar,在 action overflow 里能展现额外的操作。 ## 在 XML 中指定操作 所有的操作按钮和 action overflow 中其他可用的条目都被定义在 [menu资源](https://developer.android.com/guide/topics/resources/menu-resource.html) 的 XML 文件中。通过在项目的 `res/menu` 目录中新增一个 XML 文件来为 action bar 添加操作。 为想要添加到 action bar 中的每个条目添加一个 `` 元素。例如: `res/menu/main_activity_actions.xml` ```xml ``` 上述代码声明,当 action bar 有可用空间时,搜索操作将作为一个操作按钮来显示,但设置操作将一直只在 action overflow 中显示。(默认情况下,所有的操作都显示在 action overflow 中,但为每一个操作指明设计意图是很好的做法。) icon 属性要求每张图片提供一个 `resource ID`。在 `@drawable/` 之后的名字必须是存储在项目目录 `res/drawable/` 下位图图片的文件名。例如:`ic_action_search.png` 对应 "@drawable/ic_action_search"。同样地,title 属性使用通过 XML 文件定义在项目目录 `res/values/` 中的一个 `string 资源`,详情请参见 [创建一个简单的 UI](../firstapp/building-ui.html) 。 > **注意**:当创建 icon 和其他 bitmap 图片时,要为不同屏幕密度下的显示效果提供多个优化的版本,这一点很重要。在 [支持不同屏幕](../supporting-devices/screens.html) 课程中将会更详细地讨论。 **如果为了兼容 Android 2.1 的版本使用了 Support 库**,在 `android` 命名空间下 `showAsAction` 属性是不可用的。Support 库会提供替代它的属性,我们必须声明自己的 XML 命名空间,并且使用该命名空间作为属性前缀。(一个自定义 XML 命名空间需要以我们的 app 名称为基础,但是可以取任何想要的名称,它的作用域仅仅在我们声明的文件之内。)例如: `res/menu/main_activity_actions.xml` ```xml ... ``` ## 为 Action Bar 添加操作 要为 action bar 布局菜单条目,就要在 activity 中实现 onCreateOptionsMenu() 回调方法来 `inflate` 菜单资源从而获取 [Menu](https://developer.android.com/reference/android/view/Menu.html) 对象。例如: ```java @Override public boolean onCreateOptionsMenu(Menu menu) { // 为ActionBar扩展菜单项 MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_activity_actions, menu); return super.onCreateOptionsMenu(menu); } ``` ## 为操作按钮添加响应事件 当用户按下某一个操作按钮或者 action overflow 中的其他条目,系统将调用 activity 中onOptionsItemSelected()的回调方法。在该方法的实现里面调用[MenuItem](https://developer.android.com/reference/android/view/MenuItem.html)的getItemId()来判断哪个条目被按下 - 返回的 ID 会匹配我们声明对应的 `` 元素中 `android:id` 属性的值。 ```java @Override public boolean onOptionsItemSelected(MenuItem item) { // 处理动作按钮的点击事件 switch (item.getItemId()) { case R.id.action_search: openSearch(); return true; case R.id.action_settings: openSettings(); return true; default: return super.onOptionsItemSelected(item); } } ``` ## 为下级 Activity 添加向上按钮 在不是程序入口的其他所有屏中(activity 不位于主屏时),需要在 action bar 中为用户提供一个导航到逻辑父屏的**up button(向上按钮)**。 ![actionbar-up.png](actionbar-up.png) 图 2. Gmail 中的 up button。 当运行在 Android 4.1(API level 16) 或更高版本,或者使用 Support 库中的 [ActionBarActivity](https://developer.android.com/reference/android/support/v7/app/ActionBarActivity.html) 时,实现向上导航需要在 manifest 文件中声明父 activity ,同时在 action bar 中设置向上按钮可用。 如何在 manifest 中声明一个 activity 的父类,例如: ```xml ... ... ``` 然后,通过调用setDisplayHomeAsUpEnabled() 来把 app icon 设置成可用的向上按钮: ```java @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_displaymessage); getSupportActionBar().setDisplayHomeAsUpEnabled(true);    // 如果你的minSdkVersion属性是11或更高, 应该这么用: // getActionBar().setDisplayHomeAsUpEnabled(true); } ``` 由于系统已经知道 `MainActivity` 是 `DisplayMessageActivity` 的父 activity,当用户按下向上按钮时,系统会导航到恰当的父 activity - 你不需要去处理向上按钮的事件。 更多关于向上导航的信息,请见 [提供向上导航](../../ux/implement-nav/ancestral.html)。 ================================================ FILE: basics/actionbar/index.md ================================================ # 添加Action Bar > 编写:[Vincent 4J](http://github.com/vincent4j) - 原文: Action Bar是我们可以为activity实现的最重要的设计元素之一。其提供了多种 UI 特性,可以让我们的 app 与其他 Android app 保持较高的一致性,从而为用户所熟悉。核心的功能包括: * 一个专门的空间用来显示你的app的标识,以及指出目前所处在app的哪个页面。 * 以一种可预见的方式访问重要的操作(比如搜索)。 * 支持导航和视图切换(通过Tabs和下拉列表) ![actionbar-actions](actionbar-actions.png) 本章为 action bar 的基本知识提供了一个快速指南。关于 action bar 的更多特性,请查看 [Action Bar](https://developer.android.com/guide/topics/ui/actionbar.html) 指南。 ## Lessons * [**建立ActionBar**](setting-up.html) 学习如何为 activity 添加一个基本的 action bar,是仅仅支持 Android 3.0及以上的版本,还是同时也支持至Android 2.1的版本(通过使用 Andriod Support Library)。 * [**添加Action按钮**](adding-buttons.html) 学习如何在 action bar 中添加和响应用户操作。 * [**ActionBar的风格化**](styling.html) 学习如何自定义 action bar 的外观。 * [**ActionBar覆盖叠加**](overlaying.html) 学习如何在布局上面叠加 action bar,允许 action bar 隐藏时无缝过渡。 ================================================ FILE: basics/actionbar/overlaying.md ================================================ # ActionBar的覆盖叠加 > 编写:[Vincent 4J](http://github.com/vincent4j) - 原文: 默认情况下,action bar 显示在 activity 窗口的顶部,会稍微地减少其他布局的有效空间。如果在用户交互过程中要隐藏和显示 action bar,可以通过调用 [ActionBar](https://developer.android.com/reference/android/app/ActionBar.html) 中的 hide()show()来实现。但是,这将导致 activity 基于新尺寸重新计算与绘制布局。 为避免在 action bar 隐藏和显示过程中调整布局的大小,可以为 action bar 启用叠加模式(**overlay mode**)。在叠加模式下,所有可用的空间都会被用来布局就像ActionBar不存在一样,并且 action bar 会叠加在布局之上。这样布局顶部就会有点被遮挡,但当 action bar 隐藏或显示时,系统不再需要调整布局而是无缝过渡。 > **Note**:如果希望 action bar 下面的布局部分可见,可以创建一个背景部分透明的自定义式样的 action bar,如图 1 所示。关于如何定义 action bar 的背景,请查看 [自定义ActionBar的风格](styling.html)。 ![actionbar-overlay@2x](actionbar-overlay@2x.png) 图 1. 叠加模式下的 gallery action bar ## 启用叠加模式(Overlay Mode) 要为 action bar 启用叠加模式,需要自定义一个主题,该主题继承于已经存在的 action bar 主题,并设置 `android:windowActionBarOverlay` 属性的值为 `true`。 ### 仅支持 Android 3.0 和以上 如果 [minSdkVersion](https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#min) 为 `11` 或更高,自定义主题必须继承 [Theme.Holo](https://developer.android.com/reference/android/R.style.html#Theme_Holo) 主题(或者其子主题)。例如: ```xml ``` ### 支持 Android 2.1 和更高 如果为了兼容运行在 Android 3.0 以下版本的设备而使用了 Support 库,自定义主题必须继承 [Theme.AppCompat](https://developer.android.com/reference/android/support/v7/appcompat/R.style.html#Theme_AppCompat) 主题(或者其子主题)。例如: ```xml ``` 注意,该主题包含两种不同的 `windowActionBarOverlay` 式样定义:一个带 `android:` 前缀,另一个不带。带前缀的适用于包含该式样的 Android 系统版本,不带前缀的适用于通过从 Support 库中读取式样的旧版本。 ## 指定布局的顶部边距 当 action bar 启用叠加模式时,它可能会遮挡住本应保持可见状态的布局。为了确保这些布局始终位于 action bar 下部,可以使用 [actionBarSize](https://developer.android.com/reference/android/R.attr.html#actionBarSize) 属性来指定顶部margin或padding的高度来到达。例如: ```xml ... ``` 如果在 action bar 中使用 Support 库,需要移除 `android:` 前缀。例如: ```xml ... ``` 在这种情况下,不带前缀的 `?attr/actionBarSize` 适用于包括Android 3.0 和更高的所有版本。 ================================================ FILE: basics/actionbar/setting-up.md ================================================ # 建立ActionBar > 编写:[Vincent 4J](http://github.com/vincent4j) - 原文: Action bar 最基本的形式,就是为 Activity 显示标题,并且在标题左边显示一个 app icon。即使在这样简单的形式下,action bar对于所有的 activity 来说是十分有用的。它告知用户他们当前所处的位置,并为你的 app 维护了持续的同一标识。 ![actionbar-basic](actionbar-basic.png) 图 1. 一个有 app icon 和 Activity 标题的 action bar 设置一个基本的 action bar,需要 app 使用一个 activity 主题,该主题必须是 action bar 可用的。如何声明这样的主题取决于我们 app 支持的 Android 最低版本。本课程根据我们 app 支持的 Android 最低版本分为两部分。 ## 仅支持 Android 3.0 及以上版本 从 Android 3.0(API lever 11) 开始,所有使用 [Theme.Holo](http://developer.android.com/reference/android/R.style.html#Theme_Holo) 主题(或者它的子类)的 Activity 都包含了 action bar,当 [targetSdkVersion](http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#target) 或 [minSdkVersion](http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#min) 属性被设置成 “11” 或更大时,它是默认主题。 所以,要为 activity 添加 action bar,只需简单地设置属性为 `11` 或者更大。例如: ```xml ... ``` > **注意**: 如果创建了一个自定义主题,需确保这个主题使用一个 Theme.Holo的主题作为父类。详情见 [Action bar 的风格化](styling.html) 到此,我们的 app 使用了 `Theme.Holo` 主题,并且所有的 activity 都显示 action bar。 ## 支持 Android 2.1 及以上版本 当 app 运行在 Andriod 3.0 以下版本(不低于 Android 2.1)时,如果要添加 action bar,需要加载 Android Support 库。 开始之前,通过阅读[Support Library Setup](http://developer.android.com/tools/support-library/setup.html)文档来建立**v7 appcompat** library(下载完library包之后,按照[Adding libraries with resources](http://developer.android.com/tools/support-library/setup.html#libs-with-res)的指引进行操作)。 在 Support Library集成到你的 app 工程中之后: 1、更新 activity,以便于它继承于 [ActionBarActivity](http://developer.android.com/reference/android/support/v7/app/ActionBarActivity.html)。例如: ```java public class MainActivity extends ActionBarActivity { ... } ``` 2、在 mainfest 文件中,更新 [``](http://developer.android.com/guide/topics/manifest/application-element.html) 标签或者单一的 [``](http://developer.android.com/guide/topics/manifest/application-element.html) 标签来使用一个 [Theme.AppCompat](http://developer.android.com/reference/android/support/v7/appcompat/R.style.html#Theme_AppCompat) 主题。例如: ```xml ``` > **注意**: 如果创建一个自定义主题,需确保其使用一个 `Theme.AppCompat` 主题作为父类。详情见 [Action bar 风格化](styling.html) 现在,当 app 运行在 Android 2.1(API level 7) 或者以上时,activity 将包含 action bar。 切记,在 manifest 中正确地设置 app 支持的 API level: ```xml ... ``` ================================================ FILE: basics/actionbar/styling.md ================================================ # 自定义ActionBar的风格 > 编写:[Vincent 4J](http://github.com/vincent4j) - 原文: Action bar 为用户提供一种熟悉可预测的方式来展示操作和导航,但是这并不意味着我们的 app 要看起来和其他 app 一样。如果想将 action bar 的风格设计的合乎我们产品的定位,只需简单地使用 Android 的 [样式和主题](https://developer.android.com/guide/topics/ui/themes.html) 资源。 Android 包括一少部分内置的 activity 主题,这些主题中包含 “dark” 或 “light” 的 action bar 样式。我们也可以扩展这些主题,以便于更好的为 action bar 自定义外观。 > **注意**:如果我们为 action bar 使用了 Support 库的 API,那我们必须使用(或重写) [Theme.AppCompat](https://developer.android.com/reference/android/support/v7/appcompat/R.style.html#Theme_AppCompat) 家族样式(甚至 [Theme.Holo](https://developer.android.com/reference/android/R.style.html#Theme_Holo) 家族,在 API level 11 或更高版本中可用)。如此一来,声明的每一个样式属性都必须被声明两次:一次使用系统平台的样式属性([android:](http://developer.android.com/reference/android/R.attr.html) 属性),另一次使用 Support 库中的样式属性([appcompat.R.attr](http://developer.android.com/reference/android/support/v7/appcompat/R.attr.html) 属性 - 这些属性的上下文其实就是我们的 app)。更多细节请查看下面的示例。 ## 使用一个 Android 主题 Android 包含两个基本的 activity 主题,这两个主题决定了 action bar 的颜色: * [Theme.Holo](https://developer.android.com/reference/android/R.style.html#Theme_Holo),一个 “dark” 的主题 * [Theme.Holo.Light](https://developer.android.com/reference/android/R.style.html#Theme_Holo_Light),一个 “light” 的主题 ![actionbar-theme-dark@2x.png](actionbar-theme-dark@2x.png) ![actionbar-theme-light-solid@2x.png](actionbar-theme-light-solid@2x.png) 这些主题即可以被应用到 app 全局,也可以通过在 manifest 文件中设置 [``](https://developer.android.com/guide/topics/manifest/application-element.html) 元素 或 [``](https://developer.android.com/guide/topics/manifest/application-element.html) 元素的 `android:theme` 属性,对单一的 activity 进行设置。 例如: ```xml ``` 可以通过声明 activity 的主题为 [Theme.Holo.Light.DarkActionBar](https://developer.android.com/reference/android/R.style.html#Theme_Holo_Light_DarkActionBar) 来达到如下效果:action bar 为dark,其他部分为light。 ![actionbar-theme-light-darkactionbar@2x.png](actionbar-theme-light-darkactionbar@2x.png) 当使用 Support 库时,必须使用 [Theme.AppCompat](https://developer.android.com/reference/android/support/v7/appcompat/R.style.html#Theme_AppCompat) 主题替代: * [Theme.AppCompat](https://developer.android.com/reference/android/support/v7/appcompat/R.style.html#Theme_AppCompat),一个“dark”的主题 * [Theme.AppCompat.Light](https://developer.android.com/reference/android/support/v7/appcompat/R.style.html#Theme_AppCompat_Light),一个“light”的主题 * [Theme.AppCompat.Light.DarkActionBar](https://developer.android.com/reference/android/support/v7/appcompat/R.style.html#Theme_AppCompat_Light_DarkActionBar),一个带有“dark” action bar 的“light”主题 一定要确保我们使用的 action bar icon 的颜色与 action bar 本身的颜色有差异。[Action Bar Icon Pack](https://developer.android.com/design/downloads/index.html#action-bar-icon-pack) 为 Holo “dark”和“light”的 action bar 提供了标准的 action icon。 ## 自定义背景 为改变 action bar的背景,可以通过为 activity 创建一个自定义主题,并重写 [actionBarStyle](https://developer.android.com/reference/android/R.attr.html#actionBarStyle) 属性来实现。[actionBarStyle](https://developer.android.com/reference/android/R.attr.html#actionBarStyle) 属性指向另一个样式;在该样式里,通过指定一个 drawable 资源来重写 [background](https://developer.android.com/reference/android/R.attr.html#background) 属性。 ![actionbar-theme-custom@2x.png](actionbar-theme-custom@2x.png) 如果我们的 app 使用了 [navigation tabs](https://developer.android.com/guide/topics/ui/actionbar.html#Tabs) 或 [split action bar](https://developer.android.com/guide/topics/ui/actionbar.html#SplitBar) ,也可以通过分别设置 [backgroundStacked](https://developer.android.com/reference/android/R.attr.html#backgroundStacked) 和 [backgroundSplit](https://developer.android.com/reference/android/R.attr.html#backgroundSplit) 属性来为这些条指定背景。 > **Note**:为自定义主题和样式声明一个合适的父主题,这点很重要。如果没有父样式,action bar将会失去很多默认的样式属性,除非我们自己显式的对他们进行声明。 ### 仅支持 Android 3.0 和更高 当仅支持 Android 3.0 和更高版本时,可以通过如下方式定义 action bar 的背景: `res/values/themes.xml` ```xml ``` 然后,将主题应用到 app 全局或单个的 activity 之中: ```xml ``` ### 支持 Android 2.1 和更高 当使用 Support 库时,上面同样的主题必须被替代成如下: `res/values/themes.xml` ```xml ``` 然后,将主题应用到 app 全局或单个的 activity 之中: ```xml ``` ## 自定义文本颜色 修改 action bar 中的文本颜色,需要分别设置每个元素的属性: * Action bar 的标题:创建一种自定义样式,并指定 `textColor` 属性;同时,在自定义的 [actionBarStyle](https://developer.android.com/reference/android/R.attr.html#actionBarStyle) 中为 [titleTextStyle](https://developer.android.com/reference/android/R.attr.html#titleTextStyle) 属性指定为刚才的自定义样式。 > **注意**:被应用到 [titleTextStyle](https://developer.android.com/reference/android/R.attr.html#titleTextStyle) 的自定义样式应该使用 [TextAppearance.Holo.Widget.ActionBar.Title](https://developer.android.com/reference/android/R.style.html#TextAppearance_Holo_Widget_ActionBar_Title) 作为父样式。 * Action bar tabs:在 activity 主题中重写 [ actionBarTabTextStyle](https://developer.android.com/reference/android/R.attr.html#actionBarTabTextStyle) * Action 按钮:在 activity 主题中重写 [actionMenuTextColor](https://developer.android.com/reference/android/R.attr.html#actionMenuTextColor) ### 仅支持 Android 3.0 和更高 当仅支持 Android 3.0 和更高时,样式 XML 文件应该是这样的: `res/values/themes.xml` ```xml ``` ### 支持 Android 2.1 和更高 当使用 Support 库时,样式 XML 文件应该是这样的: `res/values/themes.xml` ```xml ``` ## 自定义 Tab Indicator 为 activity 创建一个自定义主题,通过重写 [actionBarTabStyle](https://developer.android.com/reference/android/R.attr.html#actionBarTabStyle) 属性来改变 [navigation tabs](https://developer.android.com/guide/topics/ui/actionbar.html#Tabs) 使用的指示器。[actionBarTabStyle](https://developer.android.com/reference/android/R.attr.html#actionBarTabStyle) 属性指向另一个样式资源;在该样式资源里,通过指定一个state-list drawable 来重写 [background](https://developer.android.com/reference/android/R.attr.html#background) 属性。 ![](actionbar-theme-custom-tabs@2x.png) > **注意**:一个state-list drawable 是重要的,它可以通过不同的背景来指出当前选择的 tab 与其他 tab 的区别。更多关于如何创建一个 drawable 资源来处理多个按钮状态,请阅读 [State List](https://developer.android.com/guide/topics/resources/drawable-resource.html#StateList) 文档。 例如,这是一个状态列表 drawable,为一个 action bar tab 的多种不同状态分别指定背景图片: `res/drawable/actionbar_tab_indicator.xml` ```xml ``` ### 仅支持 Android 3.0 和更高 当仅支持 Android 3.0 和更高时,样式 XML 文件应该是这样的: `res/values/themes.xml` ```xml ``` ### 支持 Android 2.1 和更高 当使用 Support 库时,样式 XML 文件应该是这样的: `res/values/themes.xml` ```xml ``` > **更多资源** * 关于 action bar 的更多样式属性,请查看 [Action Bar](https://developer.android.com/guide/topics/ui/actionbar.html#Style) 指南 * 学习更多样式的工作机制,请查看 [样式和主题](https://developer.android.com/guide/topics/ui/themes.html) 指南 * 全面的 action bar 样式,请尝试 [Android Action Bar 样式生成器](http://www.actionbarstylegenerator.com/) ================================================ FILE: basics/activity-lifecycle/index.md ================================================ # 管理Activity的生命周期 > 原文: 当用户导航、退出和返回您的应用时,应用中的 [Activity](http://developer.android.com/reference/android/app/Activity.html) 实例将在其生命周期中转换不同状态。 例如,当您的Activity初次开始时,它将出现在系统前台并接收用户焦点。 在这个过程中,Android 系统会对Activity调用一系列生命周期方法,通过这些方法,您可以设置用户界面和其他组件。 如果用户执行开始另一Activity或切换至另一应用的操作,当其进入后台(在其中Activity不再可见,但实例及其状态完整保留),系统会对您的Activity调用另外一系列生命周期方法。 在生命周期回调方法内,您可以声明用户离开和再次进入Activity时的Activity行为。比如,如果您正构建流视频播放器,当用户切换至另一应用时,您可能要暂停视频或终止网络连接。当用户返回时,您可以重新连接网络并允许用户从同一位置继续播放视频。 本课讲述每个 [Activity](http://developer.android.com/reference/android/app/Activity.html) 实例接收的重要生命周期回调方法以及您如何使用这些方法以使您的Activity按照用户预期进行并且当您的Activity不需要它们时不会消耗系统资源。 **完整的Demo示例**:[ActivityLifecycle.zip](http://developer.android.com/shareables/training/ActivityLifecycle.zip) ## Lessons * [**启动与销毁Activity**](starting.html) 学习有关Activity生命周期、用户如何启动您的应用以及如何执行基本Activity创建操作的基础知识。 * [**暂停与恢复Activity**](pausing.html) 学习Activity暂停时(部分隐藏)和继续时的情况以及您应在这些状态变化期间执行的操作。 * [**停止与重启Activity**](stopping.html) 学习用户完全离开您的Activity并返回到该Activity时发生的情况。 * [**重新创建Activity**](recreating.html) 学习您的Activity被销毁时的情况以及您如何能够根据需要重新构建Activity。 ================================================ FILE: basics/activity-lifecycle/pausing.md ================================================ # 暂停与恢复Activity > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 在正常使用app时,前端的activity有时会被其他可见的组件阻塞(obstructed),从而导致当前的activity进入Pause状态。例如,当打开一个半透明的activity时(例如以对话框的形式),之前的activity会被暂停。 只要之前的activity仍然被部分可见,这个activity就会一直处于Paused状态。 然而,一旦之前的activity被完全阻塞并不可见时,则其会进入Stop状态(将在下一小节讨论)。 activity一旦进入paused状态,系统就会调用activity中的onPause()方法, 该方法中可以停止不应该在暂停过程中执行的操作,如暂停视频播放;或者保存那些有可能需要长期保存的信息。如果用户从暂停状态回到当前activity,系统应该恢复那些数据并执行onResume()方法。 > **Note:** 当我们的activity收到调用onPause()的信号时,那可能意味者activity将被暂停一段时间,并且用户很可能回到我们的activity。然而,那也是用户要离开我们的activtiy的第一个信号。 ![basic-lifecycle-paused](basic-lifecycle-paused.png) **Figure 1.** 当一个半透明的activity阻塞activity时,系统会调用onPause()方法并且这个activity会停留在Paused 状态(1). 如果用户在这个activity还是在Paused 状态时回到这个activity,系统则会调用它的onResume() (2). ## 暂停Activity 当系统调用activity中的onPause(),从技术上讲,意味着activity仍然处于部分可见的状态.但更多时候意味着用户正在离开这个activity,并马上会进入Stopped state. 通常应该在onPause()回调方法里面做以下事情: * 停止动画或者是其他正在运行的操作,那些都会导致CPU的浪费. * 提交在用户离开时期待保存的内容(例如邮件草稿). * 释放系统资源,例如broadcast receivers, sensors (比如GPS), 或者是其他任何会影响到电量的资源。 例如, 如果程序使用[Camera](http://developer.android.com/reference/android/hardware/Camera.html),onPause()会是一个比较好的地方去做那些释放资源的操作。 ```java @Override public void onPause() { super.onPause(); // Always call the superclass method first // Release the Camera because we don't need it when paused // and other activities might need to use it. if (mCamera != null) { mCamera.release() mCamera = null; } } ``` 通常,**不应该**使用onPause()来保存用户改变的数据 (例如填入表格中的个人信息) 到永久存储(File或者DB)上。仅仅当确认用户期待那些改变能够被自动保存的时候(例如正在撰写邮件草稿),才把那些数据存到永久存储 。但是,我们应该避免在onPause()时执行CPU-intensive 的工作,例如写数据到DB,因为它会导致切换到下一个activity变得缓慢(应该把那些heavy-load的工作放到onStop()去做)。 如果activity实际上是要被Stop,那么我们应该为了切换的顺畅而减少在OnPause()方法里面的工作量。 > **Note:**当activity处于暂停状态,[Activity](http://developer.android.com/reference/android/app/Activity.html)实例是驻留在内存中的,并且在activity 恢复的时候重新调用。我们不需要在恢复到Resumed状态的一系列回调方法中重新初始化组件。 ## 恢复activity 当用户从Paused状态恢复activity时,系统会调用onResume()方法。 请注意,系统每次调用这个方法时,activity都处于前台,包括第一次创建的时候。所以,应该实现onResume()来初始化那些在onPause方法里面释放掉的组件,并执行那些activity每次进入Resumed state都需要的初始化动作 (例如开始动画与初始化那些只有在获取用户焦点时才需要的组件) 下面的onResume()的例子是与上面的onPause()例子相对应的。 ```java @Override public void onResume() { super.onResume(); // Always call the superclass method first // Get the Camera instance as the activity achieves full user focus if (mCamera == null) { initializeCamera(); // Local method to handle camera init } } ``` ================================================ FILE: basics/activity-lifecycle/recreating.md ================================================ # 重新创建Activity > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 有几个场景中,Activity是由于正常的程序行为而被Destory的。例如当用户点击返回按钮或者是Activity通过调用finish()来发出停止信号。系统也有可能会在Activity处于stop状态且长时间不被使用,或者是在前台activity需要更多系统资源的时关闭后台进程,以图获取更多的内存。 当Activity是因为用户点击Back按钮或者是activity通过调用finish()结束自己时,系统就丢失了对Activity实例的引用,因为这一行为意味着不再需要这个activity了。然而,如果因为系统资源紧张而导致Activity的Destory, 系统会在用户回到这个Activity时有这个Activity存在过的记录,系统会使用那些保存的记录数据(描述了当Activity被Destory时的状态)来重新创建一个新的Activity实例。那些被系统用来恢复之前状态而保存的数据被叫做 "instance state" ,它是一些存放在[Bundle](http://developer.android.com/reference/android/os/Bundle.html)对象中的key-value pairs。(*请注意这里的描述,这对理解onSaveInstanceState执行的时刻很重要*) > **Caution:** 你的Activity会在每次旋转屏幕时被destroyed与recreated。当屏幕改变方向时,系统会Destory与Recreate前台的activity,因为屏幕配置被改变,你的Activity可能需要加载另一些替代的资源(例如layout). 默认情况下, 系统使用 Bundle 实例来保存每一个View(视图)对象中的信息(例如输入EditText 中的文本内容)。因此,如果Activity被destroyed与recreated, 则layout的状态信息会自动恢复到之前的状态。然而,activity也许存在更多你想要恢复的状态信息,例如记录用户Progress的成员变量(member variables)。 > **Note:** 为了使Android系统能够恢复Activity中的View的状态,**每个View都必须有一个唯一ID**,由[android:id](http://developer.android.com/reference/android/view/View.html#attr_android:id)定义。 为了可以保存额外更多的数据到saved instance state。在Activity的生命周期里面存在一个额外的回调函数,你必须重写这个函数。该回调函数并没有在前面课程的图片示例中显示。这个方法是onSaveInstanceState() ,当用户离开Activity时,系统会调用它。当系统调用这个函数时,系统会在Activity被异常Destory时传递 Bundle 对象,这样我们就可以增加额外的信息到Bundle中并保存到系统中。若系统在Activity被Destory之后想重新创建这个Activity实例时,之前的Bundle对象会(系统)被传递到你我们activity的onRestoreInstanceState()方法与 onCreate() 方法中。 ![basic-lifecycle-savestate](basic-lifecycle-savestate.png) **Figure 2.** 当系统开始停止Activity时,只有在Activity实例会需要重新创建的情况下才会调用到onSaveInstanceState() (1) ,在这个方法里面可以指定额外的状态数据到Bunde中。如果这个Activity被destroyed然后这个实例又需要被重新创建时,系统会传递在 (1) 中的状态数据到 onCreate() (2) 与 onRestoreInstanceState()(3). *(通常来说,跳转到其他的activity或者是点击Home都会导致当前的activity执行onSaveInstanceState,因为这种情况下的activity都是有可能会被destory并且是需要保存状态以便后续恢复使用的,而从跳转的activity点击back回到前一个activity,那么跳转前的activity是执行退栈的操作,所以这种情况下是不会执行onSaveInstanceState的,因为这个activity不可能存在需要重建的操作)* ## 保存Activity状态 当我们的activity开始Stop,系统会调用 onSaveInstanceState() ,Activity可以用键值对的集合来保存状态信息。这个方法会默认保存Activity视图的状态信息,如在 EditText 组件中的文本或 ListView 的滑动位置。 为了给Activity保存额外的状态信息,你必须实现onSaveInstanceState() 并增加key-value pairs到 Bundle 对象中,例如: ```java static final String STATE_SCORE = "playerScore"; static final String STATE_LEVEL = "playerLevel"; ... @Override public void onSaveInstanceState(Bundle savedInstanceState) { // Save the user's current game state savedInstanceState.putInt(STATE_SCORE, mCurrentScore); savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel); // Always call the superclass so it can save the view hierarchy state super.onSaveInstanceState(savedInstanceState); } ``` > **Caution**: 必须要调用 onSaveInstanceState() 方法的父类实现,这样默认的父类实现才能保存视图状态的信息。 ## 恢复Activity状态 当Activity从Destory中重建,我们可以从系统传递的Activity的Bundle中恢复保存的状态。 onCreate() 与 onRestoreInstanceState() 回调方法都接收到了同样的Bundle,里面包含了同样的实例状态信息。 由于 onCreate() 方法会在第一次创建新的Activity实例与重新创建之前被Destory的实例时都被调用,我们必须在尝试读取 Bundle 对象前检测它是否为null。如果它为null,系统则是创建一个新的Activity实例,而不是恢复之前被Destory的Activity。 下面是一个示例:演示在onCreate方法里面恢复一些数据: ```java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Always call the superclass first // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { // Restore value of members from saved state mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); } else { // Probably initialize members with default values for a new instance } ... } ``` 我们也可以选择实现 onRestoreInstanceState() ,而不是在onCreate方法里面恢复数据。 **onRestoreInstanceState()方法会在 onStart() 方法之后执行. 系统仅仅会在存在需要恢复的状态信息时才会调用 onRestoreInstanceState() ,因此不需要检查 Bundle 是否为null。** ```java public void onRestoreInstanceState(Bundle savedInstanceState) { // Always call the superclass so it can restore the view hierarchy super.onRestoreInstanceState(savedInstanceState); // Restore state members from saved instance mCurrentScore = savedInstanceState.getInt(STATE_SCORE); mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL); } ``` > **Caution**: 与上面保存一样,总是需要调用onRestoreInstanceState()方法的父类实现,这样默认的父类实现才能保存视图状态的信息。更多关于运行时状态改变引起的recreate我们的activity。请参考[Handling Runtime Changes](http://developer.android.com/guide/topics/resources/runtime-changes.html). ================================================ FILE: basics/activity-lifecycle/starting.md ================================================ # 启动与销毁Activity > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 不同于使用 `main()` 方法启动应用的其他编程范例,Android 系统会通过调用对应于其生命周期中特定阶段的特定回调方法在 Activity 实例中启动代码。 有一系列可启动Activity的回调方法,以及一系列可分解Activity的回调方法。 本课程概述了最重要的生命周期方法,并向您展示如何处理创建Activity新实例的第一个生命周期回调。 ## 了解生命周期回调 在Activity的生命周期中,系统会按类似于阶梯金字塔的顺序调用一组核心的生命周期方法。也就是说,Activity生命周期的每个阶段就是金字塔上的一阶。 当系统创建新Activity实例时,每个回调方法会将Activity状态向顶端移动一阶。金字塔的顶端是Activity在前台运行并且用户可以与其交互的时间点。 当用户开始离开Activity时,系统会调用其他方法在金字塔中将Activity状态下移,从而销毁Activity。在有些情况下,Activity将只在金字塔中部分下移并等待(比如,当用户切换到其他应用时),Activity可从该点开始移回顶端(如果用户返回到该Activity),并在用户停止的位置继续。 ![basic-lifecycle](basic-lifecycle.png) **图 1.**简化的Activity生命周期图示,以阶梯金字塔表示。此图示显示,对于用于将Activity朝顶端的“继续”状态移动一阶的每个回调,有一种将Activity下移一阶的回调方法。Activity还可以从“暂停”和“停止”状态回到继续状态。* 根据Activity的复杂程度,您可能不需要实现所有生命周期方法。但是,了解每个方法并实现确保您的应用按照用户期望的方式运行的方法非常重要。正确实现您的Activity生命周期方法可确保您的应用按照以下几种方式良好运行,包括: * 如果用户在使用您的应用时接听来电或切换到另一个应用,它不会崩溃。 * 在用户未主动使用它时不会消耗宝贵的系统资源。 * 如果用户离开您的应用并稍后返回,不会丢失用户的进度。 * 当屏幕在横向和纵向之间旋转时,不会崩溃或丢失用户的进度。 正如您将要在以下课程中要学习的,有Activity会在图 1 所示不同状态之间过渡的几种情况。但是,这些状态中只有三种可以是静态。 也就是说,Activity只能在三种状态之一下存在很长时间。 * **Resumed**:在这种状态下,Activity处于前台,且用户可以与其交互。(有时也称为“运行”状态。) * **Paused**:在这种状态下,Activity被在前台中处于半透明状态或者未覆盖整个屏幕的另一个Activity—部分阻挡。暂停的Activity不会接收用户输入并且无法执行任何代码。 * **Stopped**:在这种状态下,Activity被完全隐藏并且对用户不可见;它被视为处于后台。停止时,Activity实例及其诸如成员变量等所有状态信息将保留,但它无法执行任何代码。 其他状态(“创建”和“开始”)是瞬态, 其它状态 (**Created**与**Started**)都是短暂的瞬态,系统会通过调用下一个生命周期回调方法从这些状态快速移到下一个状态。 也就是说,在系统调用 [onCreate()](http://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle)) 之后,它会快速调用 [onStart()](http://developer.android.com/reference/android/app/Activity.html#onStart()),紧接着快速调用 [onResume()](http://developer.android.com/reference/android/app/Activity.html#onResume())。 基本生命周期部分到此为止。现在,您将开始学习特定生命周期行为的一些知识。 ## 指定程序首次启动的Activity 当用户从主界面点击程序图标时,系统会调用app中被声明为"launcher" (or "main") activity中的onCreate()方法。这个Activity被用来当作程序的主要进入点。 我们可以在[AndroidManifest.xml](http://developer.android.com/guide/topics/manifest/manifest-intro.html)中定义作为主activity的activity。 这个main activity必须在manifest使用包括 `MAIN` action 与 `LAUNCHER` category 的[``](http://developer.android.com/guide/topics/manifest/intent-filter-element.html)标签来声明。例如: ```xml ``` > **Note**:当你使用Android SDK工具来创建Android工程时,工程中就包含了一个默认的声明有这个filter的activity类。 如果程序中没有声明了[MAIN](http://developer.android.com/reference/android/content/Intent.html#ACTION_MAIN) action 或者[LAUNCHER](http://developer.android.com/reference/android/content/Intent.html#CATEGORY_LAUNCHER) category的activity,那么在设备的主界面列表里面不会呈现app图标。 ## 创建一个新的实例 大多数app包括多个activity,使用户可以执行不同的动作。不论这个activity是当用户点击应用图标创建的main activtiy还是为了响应用户行为而创建的其他activity,系统都会调用新activity实例中的onCreate()方法。 我们必须实现onCreate()方法来执行程序启动所需要的基本逻辑。例如可以在onCreate()方法中定义UI以及实例化类成员变量。 例如:下面的onCreate()方法演示了为了建立一个activity所需要的一些基础操作。如声明UI元素,定义成员变量,配置UI等。*(onCreate里面尽量少做事情,避免程序启动太久都看不到界面)* ```java TextView mTextView; // Member variable for text view in the layout @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set the user interface layout for this Activity // The layout file is defined in the project res/layout/main_activity.xml file setContentView(R.layout.main_activity); // Initialize member TextView so we can manipulate it later mTextView = (TextView) findViewById(R.id.text_message); // Make sure we're running on Honeycomb or higher to use ActionBar APIs if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // For the main activity, make sure the app icon in the action bar // does not behave as a button ActionBar actionBar = getActionBar(); actionBar.setHomeButtonEnabled(false); } } ``` > **Caution**:用[SDK_INT](http://developer.android.com/reference/android/os/Build.VERSION.html#SDK_INT)来避免旧的系统调用了只在Android 2.0(API level 5)或者更新的系统可用的方法(上述if条件中的代码)。旧的系统调用了这些方法会抛出一个运行时异常。 一旦onCreate 操作完成,系统会迅速调用onStart() 与onResume()方法。我们的activity不会在Created或者Started状态停留。技术上来说, activity在onStart()被调用后开始被用户可见,但是 onResume()会迅速被执行使得activity停留在Resumed状态,直到一些因素发生变化才会改变这个状态。例如接收到一个来电,用户切换到另外一个activity,或者是设备屏幕关闭。 在后面的课程中,我们将看到其他方法是如何使用的,onStart() 与 onResume()在用户从Paused或Stopped状态中恢复的时候非常有用。 > **Note:** onCreate() 方法包含了一个参数叫做savedInstanceState,这将会在后面的课程 - [重新创建activity](../../activity-lifecycle/recreating.html)涉及到。 ![basic_lifecycle-create](basic-lifecycle-create.png) **Figure 2.** 上图显示了onCreate(), onStart() 和 onResume()是如何执行的。当这三个顺序执行的回调函数完成后,activity会到达Resumed状态。 ## 销毁Activity activity的第一个生命周期回调函数是 onCreate(),它最后一个回调是onDestroy().当收到需要将该activity彻底移除的信号时,系统会调用这个方法。 大多数 app并不需要实现这个方法,因为局部类的references会随着activity的销毁而销毁,并且我们的activity应该在onPause()与onStop()中执行清除activity资源的操作。然而,如果activity含有在onCreate调用时创建的后台线程,或者是其他有可能导致内存泄漏的资源,则应该在OnDestroy()时进行资源清理,杀死后台线程。 ```java @Override public void onDestroy() { super.onDestroy(); // Always call the superclass // Stop method tracing that the activity started during onCreate() android.os.Debug.stopMethodTracing(); } ``` > **Note:** 除非程序在onCreate()方法里面就调用了finish()方法,系统通常是在执行了onPause()与onStop() 之后再调用onDestroy() 。在某些情况下,例如我们的activity只是做了一个临时的逻辑跳转的功能,它只是用来决定跳转到哪一个activity,这样的话,需要在onCreate里面调用finish方法,这样系统会直接调用onDestory,跳过生命周期中的其他方法。 ================================================ FILE: basics/activity-lifecycle/stopping.md ================================================ # 停止与重启Activity > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 恰当的停止与重启我们的activity是很重要的,在activity生命周期中,他们能确保用户感知到程序的存在并不会丢失他们的进度。在下面一些关键的场景中会涉及到停止与重启: * 用户打开最近使用app的菜单并从我们的app切换到另外一个app,这个时候我们的app是被停止的。如果用户通过手机主界面的启动程序图标或者最近使用程序的窗口回到我们的app,那么我们的activity会重启。 * 用户在我们的app里面执行启动一个新activity的操作,当前activity会在第二个activity被创建后stop。如果用户点击back按钮,第一个activtiy会被重启。 * 用户在使用我们的app时接收到一个来电通话. [Activity](http://developer.android.com/reference/android/app/Activity.html)类提供了onStop()onRestart()方法来允许在activity停止与重启时进行调用。不同于暂停状态的部分阻塞UI,停止状态是UI不再可见并且用户的焦点转移到另一个activity中. > **Note:** 因为系统在activity停止时会在内存中保存Activity的实例,所以有时不需要实现onStop(),onRestart()甚至是onStart()方法. 因为大多数的activity相对比较简单,activity会自己停止与重启,我们只需要使用onPause()来停止正在运行的动作并断开系统资源链接。 ![basic-lifecycle-stopped](basic-lifecycle-stopped.png) **Figure 1.** 上图显示:当用户离开我们的activity时,系统会调用onStop()来停止activity (1). 这个时候如果用户返回,系统会调用onRestart()(2), 之后会迅速调用onStart()(3)与onResume()(4). 请注意:无论什么原因导致activity停止,系统总是会在onStop()之前调用onPause()方法。 ## 停止activity 当activity调用onStop()方法, activity不再可见,并且应该释放那些不再需要的所有资源。一旦activity停止了,系统会在需要内存空间时摧毁它的实例(*和栈结构有关,通常back操作会导致前一个activity被销毁*)。极端情况下,系统会直接杀死我们的app进程,并不执行activity的onDestroy()回调方法, 因此我们需要使用onStop()来释放资源,从而避免内存泄漏。*(这点需要注意)* 尽管onPause()方法是在onStop()之前调用,我们应该使用onStop()来执行那些CPU intensive的shut-down操作,例如往数据库写信息。 例如,下面是一个在onStop()的方法里面保存笔记草稿到persistent storage的示例: ```java @Override protected void onStop() { super.onStop(); // Always call the superclass method first // Save the note's current draft, because the activity is stopping // and we want to be sure the current note progress isn't lost. ContentValues values = new ContentValues(); values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText()); values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle()); getContentResolver().update( mUri, // The URI for the note to update. values, // The map of column names and new values to apply to them. null, // No SELECT criteria are used. null // No WHERE columns are used. ); } ``` activity已经停止后,[Activity](http://developer.android.com/reference/android/app/Activity.html)对象会保存在内存中,并在activity resume时被重新调用。我们不需要在恢复到Resumed state状态前重新初始化那些被保存在内存中的组件。系统同样保存了每一个在布局中的视图的当前状态,如果用户在EditText组件中输入了text,它会被保存,因此不需要保存与恢复它。 > **Note:** 即使系统会在activity stop时停止这个activity,它仍然会保存[View](http://developer.android.com/reference/android/view/View.html)对象的状态(比如[EditText](http://developer.android.com/reference/android/widget/EditText.html)中的文字) 到一个[Bundle](http://developer.android.com/reference/android/os/Bundle.html)中,并且在用户返回这个activity时恢复它们(下一小节会介绍在activity销毁与重新建立时如何使用[Bundle](http://developer.android.com/reference/android/os/Bundle.html)来保存其他数据的状态). ## 启动与重启activity 当activity从Stopped状态回到前台时,它会调用onRestart().系统再调用onStart()方法,onStart()方法会在每次activity可见时都会被调用。onRestart()方法则是只在activity从stopped状态恢复时才会被调用,因此我们可以使用它来执行一些特殊的恢复(restoration)工作,请注意之前是被stopped而不是destrory。 使用onRestart()来恢复activity状态是不太常见的,因此对于这个方法如何使用没有任何的guidelines。然而,因为onStop()方法应该做清除所有activity资源的操作,我们需要在重启activtiy时重新实例化那些被清除的资源,同样, 我们也需要在activity第一次创建时实例化那些资源。介于上面的原因,应该使用onStart()作为onStop()所对应方法。因为系统会在创建activity与从停止状态重启activity时都会调用onStart()。也就是说,我们在onStop里面做了哪些清除的操作,就该在onStart里面重新把那些清除掉的资源重新创建出来。 例如:因为用户很可能在回到这个activity之前已经过了很长一段时间,所以onStart()方法是一个比较好的地方来验证某些必须的系统特性是否可用。 ```java @Override protected void onStart() { super.onStart(); // Always call the superclass method first // The activity is either being restarted or started for the first time // so this is where we should make sure that GPS is enabled LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); if (!gpsEnabled) { // Create a dialog here that requests the user to enable GPS, and use an intent // with the android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS action // to take the user to the Settings screen to enable GPS when they click "OK" } } @Override protected void onRestart() { super.onRestart(); // Always call the superclass method first // Activity being restarted from stopped state } ``` 当系统Destory我们的activity,它会为activity调用onDestroy()方法。因为我们会在onStop方法里面做释放资源的操作,那么onDestory方法则是我们最后去清除那些可能导致内存泄漏的地方。因此需要确保那些线程都被destroyed并且所有的操作都被停止。 ================================================ FILE: basics/data-storage/database.md ================================================ # 保存到数据库 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 对于重复或者结构化的数据(如联系人信息)等保存到DB是个不错的主意。本课假定读者已经熟悉SQL数据库的常用操作。在Android上可能会使用到的APIs,可以从[android.database.sqlite](http://developer.android.com/reference/android/database/sqlite/package-summary.html)包中找到。 ## 定义Schema与Contract SQL中一个重要的概念是schema:一种DB结构的正式声明,用于表示database的组成结构。schema是从创建DB的SQL语句中生成的。我们会发现创建一个伴随类(companion class)是很有益的,这个类称为合约类(contract class),它用一种系统化并且自动生成文档的方式,显示指定了schema样式。 Contract Clsss是一些常量的容器。它定义了例如URIs,表名,列名等。这个contract类允许在同一个包下与其他类使用同样的常量。 它让我们只需要在一个地方修改列名,然后这个列名就可以自动传递给整个code。 组织contract类的一个好方法是在类的根层级定义一些全局变量,然后为每一个table来创建内部类。 > **Note:**通过实现 [BaseColumns](http://developer.android.com/reference/android/provider/BaseColumns.html) 的接口,内部类可以继承到一个名为_ID的主键,这个对于Android里面的一些类似cursor adaptor类是很有必要的。这么做不是必须的,但这样能够使得我们的DB与Android的framework能够很好的相容。 例如,下面的例子定义了表名与该表的列名: ```java public final class FeedReaderContract { // To prevent someone from accidentally instantiating the contract class, // give it an empty constructor. public FeedReaderContract() {} /* Inner class that defines the table contents */ public static abstract class FeedEntry implements BaseColumns { public static final String TABLE_NAME = "entry"; public static final String COLUMN_NAME_ENTRY_ID = "entryid"; public static final String COLUMN_NAME_TITLE = "title"; public static final String COLUMN_NAME_SUBTITLE = "subtitle"; ... } } ``` ## 使用SQL Helper创建DB 定义好了的DB的结构之后,就应该实现那些创建与维护db和table的方法。下面是一些典型的创建与删除table的语句。 ```java private static final String TEXT_TYPE = " TEXT"; private static final String COMMA_SEP = ","; private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" + FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," + FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + ... // Any other options for the CREATE command " )"; private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME_ENTRIES; ``` 类似于保存文件到设备的[internal storage](http://developer.android.com/guide/topics/data/data-storage.html#filesInternal) ,Android会将db保存到程序的private的空间。我们的数据是受保护的,因为那些区域默认是私有的,不可被其他程序所访问。 在[SQLiteOpenHelper](http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html)类中有一些很有用的APIs。当使用这个类来做一些与db有关的操作时,系统会对那些有可能比较耗时的操作(例如创建与更新等)在真正需要的时候才去执行,而不是在app刚启动的时候就去做那些动作。我们所需要做的仅仅是执行getWritableDatabase()或者getReadableDatabase(). > **Note:**因为那些操作可能是很耗时的,请确保在background thread(AsyncTask or IntentService)里面去执行 getWritableDatabase() 或者 getReadableDatabase() 。 为了使用 SQLiteOpenHelper, 需要创建一个子类并重写onCreate(), onUpgrade()onOpen()等callback方法。也许还需要实现onDowngrade(), 但这并不是必需的。 例如,下面是一个实现了SQLiteOpenHelper 类的例子: ```java public class FeedReaderDbHelper extends SQLiteOpenHelper { // If you change the database schema, you must increment the database version. public static final int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "FeedReader.db"; public FeedReaderDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // This database is only a cache for online data, so its upgrade policy is // to simply to discard the data and start over db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion); } } ``` 为了访问我们的db,需要实例化 SQLiteOpenHelper的子类: ```java FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext()); ``` ## 添加信息到DB 通过传递一个 [ContentValues](http://developer.android.com/reference/android/content/ContentValues.html) 对象到insert()方法: ```java // Gets the data repository in write mode SQLiteDatabase db = mDbHelper.getWritableDatabase(); // Create a new map of values, where column names are the keys ContentValues values = new ContentValues(); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content); // Insert the new row, returning the primary key value of the new row long newRowId; newRowId = db.insert( FeedReaderContract.FeedEntry.TABLE_NAME, FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE, values); ``` `insert()`方法的第一个参数是table名,第二个参数会使得系统自动对那些`ContentValues` 没有提供数据的列填充数据为`null`,如果第二个参数传递的是null,那么系统则不会对那些没有提供数据的列进行填充。 ## 从DB中读取信息 为了从DB中读取数据,需要使用query()方法,传递需要查询的条件。查询后会返回一个 [Cursor](http://developer.android.com/reference/android/database/Cursor.html) 对象。 ```java SQLiteDatabase db = mDbHelper.getReadableDatabase(); // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { FeedReaderContract.FeedEntry._ID, FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED, ... }; // How you want the results sorted in the resulting Cursor String sortOrder = FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC"; Cursor c = db.query( FeedReaderContract.FeedEntry.TABLE_NAME, // The table to query projection, // The columns to return selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrder // The sort order ); ``` 要查询在cursor中的行,使用cursor的其中一个move方法,但必须在读取值之前调用。一般来说应该先调用`moveToFirst()`函数,将读取位置置于结果集最开始的位置。对每一行,我们可以使用cursor的其中一个get方法如`getString()`或`getLong()`获取列的值。对于每一个get方法必须传递想要获取的列的索引位置(index position),索引位置可以通过调用`getColumnIndex()`或`getColumnIndexOrThrow()`获得。 下面演示如何从course对象中读取数据信息: ```java cursor.moveToFirst(); long itemId = cursor.getLong( cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID) ); ``` ## 删除DB中的信息 和查询信息一样,删除数据同样需要提供一些删除标准。DB的API提供了一个防止SQL注入的机制来创建查询与删除标准。 > **SQL Injection:**(*随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多。但由于程序员的水平及经验也参差不齐,相当大一部分程序员在编写代码时没有对用户输入数据的合法性进行判断,使应用程序存在安全隐患。用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入*) 该机制把查询语句划分为选项条件与选项参数两部分。条件定义了查询的列的特征,参数用于测试是否符合前面的条款。由于处理的结果不同于通常的SQL语句,这样可以避免SQL注入问题。 ```java // Define 'where' part of query. String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; // Specify arguments in placeholder order. String[] selelectionArgs = { String.valueOf(rowId) }; // Issue SQL statement. db.delete(table_name, mySelection, selectionArgs); ``` ## 更新数据 当需要修改DB中的某些数据时,使用 update() 方法。 update结合了插入与删除的语法。 ```java SQLiteDatabase db = mDbHelper.getReadableDatabase(); // New value for one column ContentValues values = new ContentValues(); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); // Which row to update, based on the ID String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; String[] selectionArgs = { String.valueOf(rowId) }; int count = db.update( FeedReaderDbHelper.FeedEntry.TABLE_NAME, values, selection, selectionArgs); ``` ================================================ FILE: basics/data-storage/files.md ================================================ # 保存到文件 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: Android使用与其他平台类似的基于磁盘的文件系统(disk-based file systems)。本课程将描述如何在Android文件系统上使用 [File](http://developer.android.com/reference/java/io/File.html) 的读写APIs对Andorid的file system进行读写。 File 对象非常适合于流式顺序数据的读写。如图片文件或是网络中交换的数据等。 本课程将会演示如何在app中执行基本的文件相关操作。假定读者已对linux的文件系统及[java.io](http://developer.android.com/reference/java/io/package-summary.html)中标准的I/O APIs有一定认识。 ## 存储在内部还是外部 所有的Android设备均有两个文件存储区域:"internal" 与 "external" 。 这两个名称来自于早先的Android系统,当时大多设备都内置了不可变的内存(internal storage)及一个类似于SD card(external storage)这样的可卸载的存储部件。之后有一些设备将"internal" 与 "external" 都做成了不可卸载的内置存储,虽然如此,但是这一整块还是从逻辑上有被划分为"internal"与"external"的。只是现在不再以是否可卸载进行区分了。 下面列出了两者的区别: * **Internal storage:** * 总是可用的 * 这里的文件默认只能被我们的app所访问。 * 当用户卸载app的时候,系统会把internal内该app相关的文件都清除干净。 * Internal是我们在想确保不被用户与其他app所访问的最佳存储区域。 * **External storage:** * 并不总是可用的,因为用户有时会通过USB存储模式挂载外部存储器,当取下挂载的这部分后,就无法对其进行访问了。 * 是大家都可以访问的,因此保存在这里的文件可能被其他程序访问。 * 当用户卸载我们的app时,系统仅仅会删除external根目录(getExternalFilesDir())下的相关文件。 * External是在不需要严格的访问权限并且希望这些文件能够被其他app所共享或者是允许用户通过电脑访问时的最佳存储区域。 > **Tip:** 尽管app是默认被安装到internal storage的,我们还是可以通过在程序的manifest文件中声明[android:installLocation](http://developer.android.com/guide/topics/manifest/manifest-element.html#install) 属性来指定程序安装到external storage。当某个程序的安装文件很大且用户的external storage空间大于internal storage时,用户会倾向于将该程序安装到external storage。更多安装信息见[App Install Location](http://developer.android.com/guide/topics/data/install-location.html)。 ## 获取External存储的权限 为了写数据到external storage, 必须在你[manifest文件](http://developer.android.com/guide/topics/manifest/manifest-intro.html)中请求[WRITE_EXTERNAL_STORAGE](http://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE)权限: ```xml ... ``` > **Caution:**目前,所有的apps都可以在不指定某个专门的权限下做**读**external storage的动作。但这在以后的安卓版本中会有所改变。如果我们的app只需要**读**的权限(不是写), 那么将需要声明 [READ_EXTERNAL_STORAGE](http://developer.android.com/reference/android/Manifest.permission.html#READ_EXTERNAL_STORAGE) 权限。为了确保app能持续地正常工作,我们现在在编写程序时就需要声明读权限。 ```xml ... ``` 但是,如果我们的程序有声明**[WRITE_EXTERNAL_STORAGE](http://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE) **权限,那么就默认有了**读**的权限。 对于internal storage,我们不需要声明任何权限,因为程序默认就有读写程序目录下的文件的权限。 ## 保存到Internal Storage 当保存文件到internal storage时,可以通过执行下面两个方法之一来获取合适的目录作为 [FILE](http://developer.android.com/reference/java/io/File.html) 的对象: * getFilesDir() : 返回一个[File](http://developer.android.com/reference/java/io/File.html),代表了我们app的internal目录。 * getCacheDir() : 返回一个[File](http://developer.android.com/reference/java/io/File.html),代表了我们app的internal缓存目录。请确保这个目录下的文件能够在一旦不再需要的时候马上被删除,并对其大小进行合理限制,例如1MB 。系统的内部存储空间不够时,会自行选择删除缓存文件。 可以使用File() 构造器在那些目录下创建一个新的文件,如下: ```java File file = new File(context.getFilesDir(), filename); ``` 同样,也可以执行openFileOutput() 获取一个 [FileOutputStream](http://developer.android.com/reference/java/io/FileOutputStream.html) 用于写文件到internal目录。如下: ```java String filename = "myfile"; String string = "Hello world!"; FileOutputStream outputStream; try { outputStream = openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(string.getBytes()); outputStream.close(); } catch (Exception e) { e.printStackTrace(); } ``` 如果需要缓存一些文件,可以使用createTempFile()。例如:下面的方法从[URL](http://developer.android.com/reference/java/net/URL.html)中抽取了一个文件名,然后再在程序的internal缓存目录下创建了一个以这个文件名命名的文件。 ```java public File getTempFile(Context context, String url) { File file; try { String fileName = Uri.parse(url).getLastPathSegment(); file = File.createTempFile(fileName, null, context.getCacheDir()); catch (IOException e) { // Error while creating file } return file; } ``` > **Note:** 我们的app的internal storage 目录以app的包名作为标识存放在Android文件系统的特定目录下[data/data/com.example.xx]。 从技术上讲,如果文件被设置为可读的,那么其他app就可以读取该internal文件。然而,其他app需要知道包名与文件名。若没有设置为可读或者可写,其他app是没有办法读写的。因此我们只要使用了[MODE_PRIVATE](http://developer.android.com/reference/android/content/Context.html#MODE_PRIVATE) ,那么这些文件就不可能被其他app所访问。 ## 保存文件到External Storage 因为external storage可能是不可用的,比如遇到SD卡被拔出等情况时。因此在访问之前应对其可用性进行检查。我们可以通过执行 getExternalStorageState()来查询external storage的状态。若返回状态为[MEDIA_MOUNTED](http://developer.android.com/reference/android/os/Environment.html#MEDIA_MOUNTED), 则可以读写。示例如下: ```java /* Checks if external storage is available for read and write */ public boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false; } /* Checks if external storage is available to at least read */ public boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false; } ``` 尽管external storage对于用户与其他app是可修改的,我们可能会保存下面两种类型的文件。 * **Public files** :这些文件对与用户与其他app来说是public的,当用户卸载我们的app时,这些文件应该保留。例如,那些被我们的app拍摄的图片或者下载的文件。 * **Private files**: 这些文件完全被我们的app所私有,它们应该在app被卸载时删除。尽管由于存储在external storage,那些文件从技术上而言可以被用户与其他app所访问,但实际上那些文件对于其他app没有任何意义。因此,当用户卸载我们的app时,系统会删除其下的private目录。例如,那些被我们的app下载的缓存文件。 想要将文件以public形式保存在external storage中,请使用getExternalStoragePublicDirectory()方法来获取一个 File 对象,该对象表示存储在external storage的目录。这个方法会需要带有一个特定的参数来指定这些public的文件类型,以便于与其他public文件进行分类。参数类型包括[DIRECTORY_MUSIC](http://developer.android.com/reference/android/os/Environment.html#DIRECTORY_MUSIC) 或者 [DIRECTORY_PICTURES](http://developer.android.com/reference/android/os/Environment.html#DIRECTORY_PICTURES). 如下: ```java public File getAlbumStorageDir(String albumName) { // Get the directory for the user's public pictures directory. File file = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs()) { Log.e(LOG_TAG, "Directory not created"); } return file; } ``` 想要将文件以private形式保存在external storage中,可以通过执行getExternalFilesDir() 来获取相应的目录,并且传递一个指示文件类型的参数。每一个以这种方式创建的目录都会被添加到external storage封装我们app目录下的参数文件夹下(如下则是albumName)。这下面的文件会在用户卸载我们的app时被系统删除。如下示例: ```java public File getAlbumStorageDir(Context context, String albumName) { // Get the directory for the app's private pictures directory. File file = new File(context.getExternalFilesDir( Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs()) { Log.e(LOG_TAG, "Directory not created"); } return file; } ``` 如果刚开始的时候,没有预定义的子目录存放我们的文件,可以在 getExternalFilesDir()方法中传递`null`. 它会返回app在external storage下的private的根目录。 请记住,getExternalFilesDir() 方法会创建的目录会在app被卸载时被系统删除。如果我们的文件想在app被删除时仍然保留,请使用getExternalStoragePublicDirectory(). 无论是使用 getExternalStoragePublicDirectory() 来存储可以共享的文件,还是使用 getExternalFilesDir() 来储存那些对于我们的app来说是私有的文件,有一点很重要,那就是要使用那些类似`DIRECTORY_PICTURES` 的API的常量。那些目录类型参数可以确保那些文件被系统正确的对待。例如,那些以`DIRECTORY_RINGTONES` 类型保存的文件就会被系统的media scanner认为是ringtone而不是音乐。 ## 查询剩余空间 如果事先知道想要保存的文件大小,可以通过执行getFreeSpace() or getTotalSpace() 来判断是否有足够的空间来保存文件,从而避免发生[IOException](http://developer.android.com/reference/java/io/IOException.html)。那些方法提供了当前可用的空间还有存储系统的总容量。 然而,系统并不能保证可以写入通过`getFreeSpace()`查询到的容量文件, 如果查询的剩余容量比我们的文件大小多几MB,或者说文件系统使用率还不足90%,这样则可以继续进行写的操作,否则最好不要写进去。 > **Note:**并没有强制要求在写文件之前去检查剩余容量。我们可以尝试先做写的动作,然后通过捕获 IOException 。这种做法仅适合于事先并不知道想要写的文件的确切大小。例如,如果在把PNG图片转换成JPEG之前,我们并不知道最终生成的图片大小是多少。 ## 删除文件 在不需要使用某些文件的时候应删除它。删除文件最直接的方法是直接执行文件的`delete()`方法。 ```java myFile.delete(); ``` 如果文件是保存在internal storage,我们可以通过`Context`来访问并通过执行`deleteFile()`进行删除 ```java myContext.deleteFile(fileName); ``` > **Note:** 当用户卸载我们的app时,android系统会删除以下文件: * 所有保存到internal storage的文件。 * 所有使用getExternalFilesDir()方式保存在external storage的文件。 > 然而,通常来说,我们应该手动删除所有通过 getCacheDir() 方式创建的缓存文件,以及那些不会再用到的文件。 ================================================ FILE: basics/data-storage/index.md ================================================ # 数据保存 > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 虽然可以在onPause()时保存一些信息以免用户的使用进度被丢失,但大多数Android app仍然是需执行保存数据的动作。大多数较好的apps都需要保存用户的设置信息,而且有一些apps必须维护大量的文件信息与DB信息。本章节将介绍Android中主要的数据存储方法,包括: * [**保存到Preferences**](shared-preference.html) 学习使用Shared Preferences文件以Key-Value的方式保存简要的信息。 * [**保存到文件**](files.html) 学习保存基本的文件。 * [**保存到数据库**](database.html) 学习使用SQLite数据库读写数据。 ================================================ FILE: basics/data-storage/shared-preference.md ================================================ # 保存到Preference > 编写:[kesenhoo](https://github.com/kesenhoo) - 原文: 当有一个相对较小的key-value集合需要保存时,可以使用[SharedPreferences](http://developer.android.com/reference/android/content/SharedPreferences.html) APIs。 SharedPreferences 对象指向一个保存key-value pairs的文件,并为读写他们提供了简单的方法。每个 SharedPreferences 文件均由framework管理,其既可以是私有的,也可以是共享的。 这节课会演示如何使用 SharedPreferences APIs 来存储与检索简单的数据。 > **Note:** SharedPreferences APIs 仅仅提供了读写key-value对的功能,请不要与[Preference](http://developer.android.com/reference/android/preference/Preference.html) APIs相混淆。后者可以帮助我们建立一个设置用户配置的页面(尽管它实际上是使用SharedPreferences 来实现保存用户配置的)。更多关于Preference APIs的信息,请参考[Settings](http://developer.android.com/guide/topics/ui/settings.html) 指南。 ## 获取SharedPreference 我们可以通过以下两种方法之一创建或者访问shared preference 文件: * getSharedPreferences() — 如果需要多个通过名称参数来区分的shared preference文件, 名称可以通过第一个参数来指定。可在app中通过任何一个[Context](http://developer.android.com/reference/android/content/Context.html) 执行该方法。 * getPreferences() — 当activity仅需要一个shared preference文件时。因为该方法会检索activity下默认的shared preference文件,并不需要提供文件名称。 例:下面的示例在一个 [Fragment](http://developer.android.com/reference/android/app/Fragment.html) 中被执行,它以private模式访问名为 `R.string.preference_file_key` 的shared preference文件。这种情况下,该文件仅能被我们的app访问。 ```java Context context = getActivity(); SharedPreferences sharedPref = context.getSharedPreferences( getString(R.string.preference_file_key), Context.MODE_PRIVATE); ``` 应以与app相关的方式为shared preference文件命名,该名称应唯一。如本例中可将其命名为 `"com.example.myapp.PREFERENCE_FILE_KEY"` 。 当然,当activity仅需要一个shared preference文件时,我们可以使用getPreferences()方法: ```java SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE); ``` > **Caution:** 如果创建了一个[MODE_WORLD_READABLE](http://developer.android.com/reference/android/content/Context.html#MODE_WORLD_READABLE)或者[MODE_WORLD_WRITEABLE](http://developer.android.com/reference/android/content/Context.html#MODE_WORLD_WRITEABLE) 模式的shared preference文件,则其他任何app均可通过文件名访问该文件。 ## 写Shared Preference 为了写`shared preferences`文件,需要通过执行edit()创建一个 [SharedPreferences.Editor](http://developer.android.com/reference/android/content/SharedPreferences.Editor.html)。 通过类似putInt()putString()等方法传递keys与values,接着通过commit() 提交改变. ```java SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPref.edit(); editor.putInt(getString(R.string.saved_high_score), newHighScore); editor.commit(); ``` ## 读Shared Preference 为了从shared preference中读取数据,可以通过类似于 getInt() 及 getString()等方法来读取。在那些方法里面传递我们想要获取的value对应的key,并提供一个默认的value作为查找的key不存在时函数的返回值。如下: ```java SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE); int defaultValue = getResources().getInteger(R.string.saved_high_score_default); long highScore = sharedPref.getInt(getString(R.string.saved_high_score), default); ``` ================================================ FILE: basics/firstapp/building-ui.md ================================================ # 建立简单的用户界面 > 编写:[crazypudding](https://github.com/crazypudding) - 原文: 在本小节里,我们将学习使用Android Studio布局编辑器创建一个带有文本输入框和按钮的界面。下一节课将学会使 APP 对按钮做出响应——按钮被按下时,文本框里的内容被发送到另外一个 [Activity]。 Android 的图形用户界面由多个 *视图*([View])和 *布局*([ViewGroup])构建而成。[View] 是通用的 UI 窗体小组件,如:按钮([Button])、文本框([Text field]);而 [ViewGroup] 则是用来控制子视图如何显示在屏幕上的不可见的容器,如:网格部件(grid)、垂直列表部件(vertical list)。 ![图 1: ViewGroup][figure_1_viewgroup] **图 1** 关于 [ViewGroup] 对象如何组织布局分支和包含其他 [View] 对象。 Android 提供了一系列对应于 [View] 和 [ViewGroup] 子类的 XML 标签,大多数情况下,我们都会使用 XML 来定义自己的UI。不过这节课中我们不会练习 XML 语法,而是练习使用 Android Studio 的布局编辑器来创建布局,布局编辑器通过拖放 View 的方式可以更容易的创建一个布局。 ## 打开布局编辑器 > **注意:** 下面的内容都假定我们使用Android Studio 2.3或2.3以上的版本并且通过[之前的课程]的内容创建了一个Android项目。 开始之前,按照如下步骤设置好工作台: 1.在Android Studio 的 Project 面板中,打开文件 `app/res/layout/activity_main.xml`。 2.为布局编辑器留出更多空间,通过选择 `View > Tool Windows > Project` 来关闭 **Project** 面板(或者点击 Android Studio 左侧的![window-project][figure_window-project]按钮)。 3.如果编辑器显示的是 XML 源码,点击左下角 **Design** 标签切换到 Design 模式。 4.点击 **Show Blueprint** ![layout-editor-blueprint][figure_layout-editor-blueprint]只显示蓝图布局。 5.在布局中显示 Constraints。将鼠标放在工具栏中 ![layout-editor-hide-constraints][figure_layout-editor-hide-constraints]按钮上会看到提示: **Hide Constraints**(当前为显示 Constraints)。 6.关闭自动连接功能。将鼠标放在工具栏中 ![layout-editor-autoconnect-on][figure_layout-editor-autoconnect-on]按钮上会看到提示: **Turn On Autoconnect**(当前为关闭状态)。 7.点击工具栏中 **Default Margins** ![layout-editor-margin][figure_layout-editor-margin]按钮并选择 **16**(稍后仍可以单独为每个 View 调整间距)。 8.点击工具栏中 **Device in Editor** ![layout-editor-device][figure_layout-editor-device]按钮并选择 **Pixel XL**。 以上操作完成后,Android Studio窗口应该如下图2所示 ![图2_layout-editor_2x][figure_layout-editor_2x] **图 2.** 显示 `activity_main.xml` 的布局编辑器 左下角的 **Component Tree** 面板显示的是当前布局中所有 View 的层级结构。本例中,根 View 是一个 `ConstraintLayout`,其中只包含一个 `TextView` 对象。 `ConstraintLayout` 根据每个 View 和与它平级的兄弟 View 以及父布局之间的约束来确定它的位置。通过这个方法,我们可以创建简单或者复杂但是层级结构扁平化的布局。这样一来,就避免了嵌套布局的出现(如图1展示的那样,一个 ViewGroup 嵌套另一个 ViewGroup),缩短了绘制 UI 的时间。 例如,我们可以这样创建布局(如图3): * View A 距父布局顶部16dp * View A 距父布局左边缘16dp * View B 距 View A 右边16dp * View B 与 View A 顶部对齐 ![图3_traint-example_2x][figure_constraint-example_2x] **图 3.** `ConstraintLayout`中两个 View 的位置 在本节后面的部分中,我们将实际建立一个类似的布局。 ## 添加一个文本框 1.首先,要删除布局中已经存在的 View,在 **Component Tree** 面板中选中并删除 **TextView**。 2.在左侧 **Palette** 面板的左半部分窗格中选中 **Text** 分类,从右半部分窗格中拖出 **Plain Text** 并把它放到编辑器中靠近布局顶部的地方。这是一个可以输入纯文本的 [EditText] 3.点击编辑器中的 View。可以看到,在每个角上都有一个方形的锚点,这是用来控制 View 的大小的;在每条边中间都有一个圆形锚点,这是用来添加约束的。  为了更准确的控制这些锚点,可以通过工具栏中的缩放按钮来缩放虚拟 UI 界面。 4.按住 View 顶部的圆形锚点,将它拖动到父布局顶部,直到有吸附效果时放开。可以看到 View 和父布局顶部之间出现一条带箭头的细线,这就是一个约束——它指定 View 距离父布局顶部16dp(因为刚刚设置的默认值是16dp)。 5.同样的,在 View 的左边和父布局的左边缘创建一个约束。 最终的效果应该如图4所示。 ![图4_constraint-textbox_2x][figure_constraint-textbox_2x] **图 4.** 文本框与父布局顶部和左边形成约束 ## 添加一个按钮 1.同样的,在 **Palette** 面板左侧部分选中 **Widgets** 分类,然后拖出 **Button** 并放到编辑器中靠近父布局右上角的地方。 2.在 Button 的左侧与 EditText 右侧建立一个约束。 3.针对可显示文字的 View ,我们可以通过在每个 View 的文字基线之间建立约束从而使得它们水平对齐。在编辑器中选中一个 View ,这个被选中的 View 下方会出现一个 **Baseline Constraint** ![layout-editor-action-baseline][figure_layout-editor-action-baseline]按钮。例如选中本例中的 Button ,Button 里面会出现一个线状的锚点,将这个锚点拖放到 EditText 中的基线锚点上。 现在可以看到的效果如图5所示。 ![图5_constraint-button_2x][figure_constraint-button_2x] **图 5.** Button 左侧和 EditText 右侧以及彼此的基线之间建立了约束 > **注意:** 我们也可以用 Button 顶部或底部的锚点建立约束从而达到水平对齐的目的,但是由于在 Button 内部是有一个 padding 值的,所以通过这种方式建立约束并不会真正实现水平对齐。 ## 改变 UI 中显示的字符串 点击工具栏中的 **Show Design** ![layout-editor-design][figure_layout-editor-design]按钮可以预览我们的 UI,可以看到 EditText 默认显示的字符串是 “Name”, Button 默认显示的字符串是 “Button”。接下来我们的目的就是修改这些字符串。 1.打开 **Project** 面板,然后打开文件 `app/res/values/strings.xml`。  `strings.xml`是一个[字符串资源文件],我们应该把 UI 布局中出现的字符串定义在这个文件中。相比于在布局或逻辑代码中硬编码,这样在一个文件集中管理所有的字符串更利于字符串的查找、修改甚至是本地化操作。 2.点击右上角的 **Open editor** 按钮可以打开 [**Translations Editor**],在这个编辑器中不仅可以增加、修改默认字符串,还能很好的管理所有字符串的翻译版本。 3.点击左上角 **Add Key** ![add-sign-green-icon][figure_add-sign-green-icon]按钮为 EditText 新增一个提示文字(hint text): 1.在 Key 那一栏填入 "edit_message" , 这就是这个字符串的 id。 2.在 Default Value 那一栏填入 "Enter a message" ,这就是字符串的内容,会显示到 UI 中。 3.点击 **OK**。 ![图6_add-string_2x][figure_add-string_2x] **图 6.** 新增字符串资源的对话框 4.新增另一个字符串资源, Key 为 "button_send" ,Default Value 为 "Send"。 现在可以通过点击标签栏的 **activity_main.xml** 返回布局文件通过以下步骤为每个 View 设置相应的字符串资源: 1.在布局编辑器中选中 EditText 对象,如果在窗口右侧没有出现 **Propreties** 面板的话可以点击右边侧边栏中的 **Properities** ![window-properties][figure_window-properties]按钮。 **Propreties** 面板会显示选中对象的属性。 2.在 **Propreties** 面板中找到 *hint* 属性,然后点击文本框右边的 **Pick a Resource** ![pick-resource][figure_pick-resource]按钮,在弹出的对话框中双击 **edit_message**。 3.同样在 EditText 的 **Propreties** 面板中删除 *text* 属性的值(当前值为 "Name")。 4.在布局编辑器中选中 Button 对象切换到 Button 对应的 **Propreties** 面板,将 Button 的 *text* 属性值更换成 id 为 "button_send" 的字符串资源。 ## 让文本输入框大小灵活 为了创建一个可以适应不同大小的屏幕,我们需要调整 EditText,使得它可以在计算完 Button 的宽度和 Margin 间距之后,自行伸展至占有所有的剩余宽度。 在继续之前,点击 **Show Blueprint** ![layout-editor-blueprint][figure_layout-editor-blueprint]按钮,我们依然在蓝图模式下工作。 1.选择所有的 View 对象(选中其中一个,按住 Shift 并选中另一个),鼠标右击其中一个 View 对象,从菜单中选择 **Center Horizontally**。  虽然我们的目标不是让所有的 View 对象水平居中,但是这种方法可以在这些 View 之间快速建立起一个*约束链*(constraint chain)。约束链是在两个或多个 View 对象之间形成的一个双向约束,它可以将这些 View 对象链接起来多为一个整体进行编排布局。不过这样会消除 View 对象之间水平方向的间距,所以后面需要手动更改。设置完约束链的效果如下图: ![图7_constraint-centered_2x][figure_constraint-centered_2x] 2.选中 Button 并打开相应的 **Propreties** 面板,将左右 margin 设置为16。 3.选中 EditText 并将 left margin 设置为16。 4.在 EditText 的 **Propreties** 面板中,点击图8中标示为1处的按钮(这是宽度指示符)直到出现 ![layout-width-match][figure_layout-width-match]为止,这表示我们已经将 EditText 的 *width* 属性设置为 **Match Constraints** 了。 "Match Constraints"的意思是 View 的宽度受水平方向的约束和间距影响。因此,EditText 的宽度会伸展至占用所有剩余的水平空间(在计算完 Button 的宽度和 Margin 间距之后)。 ![图8_properties-margin_2x][figure_properties-margin_2x] **图 8.** 设置 width 属性值为 "Match Constraints" 目前为止,我们已经完成了本节课程中布局的所有内容。最终效果应该如图9所示。 ![图9_constraint-chain_2x][figure_constraint-chain_2x] **图 9.** EditText 占有所有剩余空间 如果您的布局没有达到预期的效果,可以查看下面的完整代码进行对比(各属性出现的顺序不会影响布局的样式)。以下是完整代码: ```xml