├── ArchitectureComponents ├── Android架构组件-生成的绑定类.md ├── Android架构组件-布局和绑定表达式.md ├── Android架构组件-使用可观察的数据对象.md ├── ViewModel.md ├── .DS_Store ├── DataBindingLibrary │ ├── 数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md │ ├── 数据(Data Binding)绑定库——开始.md │ ├── 数据绑定库——概述.md │ ├── 数据(Data Binding)绑定库——生成的绑定类.md │ └── 数据(Data Binding)绑定库——使用可观察的数据对象.md ├── Android架构组件-给你的项目中添加组件.md └── Android架构组件-概述.md ├── .DS_Store ├── .book.json.swp ├── Activity ├── .DS_Store ├── AppWidgets │ ├── .DS_Store │ └── App小部件概述.md ├── Handling-Android-App-Links │ ├── .DS_Store │ ├── 概述.md │ ├── 为Instant-APPs创建App-Links.md │ └── 创建指向您内容的Deep-Links.md ├── Interacting-With-Other-Apps │ ├── 与其他APP交互——概述.md │ ├── 与其他APP交互——允许其他应用启动您的Activity.md │ └── 与其他APP交互——接收另一个Activity返回的结果.md ├── AppShortCuts │ ├── 概述.md │ ├── 快捷方式的最佳做法.md │ └── 管理快捷方式.md ├── Parcelable和Bundle.md ├── 进程和应用程序的生命周期.md ├── Activity的状态变化.md ├── Fragment │ ├── 创建Fragment.md │ ├── 测试Fragment.md │ ├── 构建灵活的UI.md │ └── 与其他Fragment通信.md ├── 测试你的Activity.md └── Activity介绍.md ├── images ├── alipay.jpeg └── wechat.jpeg ├── BestPractices ├── .DS_Store └── Performance │ └── 进程和线程——概述.md ├── BackGroundTasks ├── .DS_Store ├── SendingOperationsToMultipleThreads │ ├── .DS_Store │ ├── 将操作发送到多个线程——(1)概述.md │ ├── 将操作发送到多个线程——(2)指定线程中运行的代码.md │ └── 将操作发送到多个线程——(4)在线程池中的线程中运行代码.md └── Service │ ├── Service——向后台Service发送工作请求.md │ └── Service——创建后台服务.md ├── _book ├── ArchitectureComponents │ ├── ViewModel.md │ └── DataBindingLibrary │ │ ├── 数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md │ │ ├── 数据(Data Binding)绑定库——开始.md │ │ ├── 数据绑定库——概述.md │ │ ├── 数据(Data Binding)绑定库——生成的绑定类.md │ │ ├── 数据(Data Binding)绑定库——使用可观察的数据对象 2.md │ │ └── 数据(Data Binding)绑定库——使用可观察的数据对象.md ├── gitbook │ ├── images │ │ ├── favicon 2.ico │ │ ├── favicon.ico │ │ └── apple-touch-icon-precomposed-152.png │ ├── fonts │ │ └── fontawesome │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont 2.woff │ │ │ ├── fontawesome-webfont 2.woff2 │ │ │ └── fontawesome-webfont.woff2 │ ├── gitbook-plugin-livereload │ │ ├── plugin.js │ │ └── plugin 2.js │ ├── gitbook-plugin-search │ │ ├── search.css │ │ ├── search-engine.js │ │ ├── search.js │ │ └── search 2.js │ ├── gitbook-plugin-lunr │ │ ├── search-lunr 2.js │ │ └── search-lunr.js │ ├── gitbook-plugin-sharing │ │ ├── buttons.js │ │ └── buttons 2.js │ ├── gitbook-plugin-highlight │ │ └── ebook.css │ └── gitbook-plugin-fontsettings │ │ └── fontsettings.js ├── update 2.sh ├── update.sh ├── LICENSE └── LICENSE 2 ├── update.sh ├── LICENSE ├── SUMMARY.md └── README.md /ArchitectureComponents/Android架构组件-生成的绑定类.md: -------------------------------------------------------------------------------- 1 | # 生成的绑定类 2 | 3 | -------------------------------------------------------------------------------- /ArchitectureComponents/Android架构组件-布局和绑定表达式.md: -------------------------------------------------------------------------------- 1 | # 布局和绑定表达式 2 | 3 | -------------------------------------------------------------------------------- /ArchitectureComponents/Android架构组件-使用可观察的数据对象.md: -------------------------------------------------------------------------------- 1 | # 使用可观察的数据对象 2 | 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/.DS_Store -------------------------------------------------------------------------------- /.book.json.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/.book.json.swp -------------------------------------------------------------------------------- /Activity/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/Activity/.DS_Store -------------------------------------------------------------------------------- /images/alipay.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/images/alipay.jpeg -------------------------------------------------------------------------------- /images/wechat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/images/wechat.jpeg -------------------------------------------------------------------------------- /BestPractices/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/BestPractices/.DS_Store -------------------------------------------------------------------------------- /ArchitectureComponents/ViewModel.md: -------------------------------------------------------------------------------- 1 | # ViewModel 2 | 3 | ## ViewModel的生命周期 -------------------------------------------------------------------------------- /BackGroundTasks/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/BackGroundTasks/.DS_Store -------------------------------------------------------------------------------- /Activity/AppWidgets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/Activity/AppWidgets/.DS_Store -------------------------------------------------------------------------------- /_book/ArchitectureComponents/ViewModel.md: -------------------------------------------------------------------------------- 1 | # ViewModel 2 | 3 | ## ViewModel的生命周期 -------------------------------------------------------------------------------- /ArchitectureComponents/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/ArchitectureComponents/.DS_Store -------------------------------------------------------------------------------- /_book/gitbook/images/favicon 2.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/images/favicon 2.ico -------------------------------------------------------------------------------- /_book/gitbook/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/images/favicon.ico -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "git add." 3 | git add . 4 | echo "git commit -m $@:" 5 | git commit -m"$@" 6 | echo "git push:" 7 | git push 8 | -------------------------------------------------------------------------------- /_book/update 2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "git add." 3 | git add . 4 | echo "git commit -m $@:" 5 | git commit -m"$@" 6 | echo "git push:" 7 | git push 8 | -------------------------------------------------------------------------------- /_book/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "git add." 3 | git add . 4 | echo "git commit -m $@:" 5 | git commit -m"$@" 6 | echo "git push:" 7 | git push 8 | -------------------------------------------------------------------------------- /Activity/Handling-Android-App-Links/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/Activity/Handling-Android-App-Links/.DS_Store -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont 2.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/fonts/fontawesome/fontawesome-webfont 2.woff -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont 2.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/fonts/fontawesome/fontawesome-webfont 2.woff2 -------------------------------------------------------------------------------- /_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /_book/gitbook/images/apple-touch-icon-precomposed-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/_book/gitbook/images/apple-touch-icon-precomposed-152.png -------------------------------------------------------------------------------- /BackGroundTasks/SendingOperationsToMultipleThreads/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DmrfCoder/Android-Developers-Guide/HEAD/BackGroundTasks/SendingOperationsToMultipleThreads/.DS_Store -------------------------------------------------------------------------------- /ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——将布局视图绑定到体系结构组件 2 | 3 | ## 使用LiveData通知UI有关数据更改的信息 -------------------------------------------------------------------------------- /_book/ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——将布局视图绑定到体系结构组件 2 | 3 | ## 使用LiveData通知UI有关数据更改的信息 -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-livereload/plugin.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var newEl = document.createElement('script'), 3 | firstScriptTag = document.getElementsByTagName('script')[0]; 4 | 5 | if (firstScriptTag) { 6 | newEl.async = 1; 7 | newEl.src = '//' + window.location.hostname + ':35729/livereload.js'; 8 | firstScriptTag.parentNode.insertBefore(newEl, firstScriptTag); 9 | } 10 | 11 | })(); 12 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-livereload/plugin 2.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var newEl = document.createElement('script'), 3 | firstScriptTag = document.getElementsByTagName('script')[0]; 4 | 5 | if (firstScriptTag) { 6 | newEl.async = 1; 7 | newEl.src = '//' + window.location.hostname + ':35729/livereload.js'; 8 | firstScriptTag.parentNode.insertBefore(newEl, firstScriptTag); 9 | } 10 | 11 | })(); 12 | -------------------------------------------------------------------------------- /BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(1)概述.md: -------------------------------------------------------------------------------- 1 | # 将操作发送到多个线程——概述 2 | 3 | [原文(英文)地址](https://developer.android.com/training/multiple-threads) 4 | 5 | 当您将单个线程中长时间运行的数据密集型的重量级操作分解为在多个线程上运行的较轻量级的操作时,速度和效率通常会提高。在具有多个处理器(核心)的CPU的设备上,系统可以并行运行线程,而不是让每个子任务等待运行的机会。例如,当您在多个单独的线程上独立执行每个解码任务时,解码多个图像文件的速度会大大加快。 6 | 7 | 本指南介绍如何使用线程池对象在Android应用程序中设置和使用多个线程。您还将学习如何定义在线程上运行的代码以及如何让UI线程和在这些线程之一进行通信。 8 | 9 | ## 目录 10 | 11 | - 指定要在线程中运行的代码 12 | 13 | 通过定义实现Runnable接口的类,了解如何编写代码以在单独的线程上运行。 14 | 15 | - 为多个线程创建管理器 16 | 了解如何创建管理Thread对象池和Runnable对象队列的对象。该对象称为ThreadPoolExecutor。 17 | 18 | - 在线程池中的线程上运行代码 19 | 了解如何在线程池中的线程上运行Runnable任务。 20 | 21 | - 与UI线程通信 22 | 了解如何在线程池中的线程与UI线程之间进行通信。 23 | 24 | 25 | 26 | ## 更多资源 27 | 28 | 要了解更多Android多线程的内容,请参阅: 29 | 30 | - [Loaders](…/Activity/加载器.md) 31 | - [Services](../Service) 32 | - [Process and threads overview](https://developer.android.com/guide/components/processes-and-threads.html) 33 | 34 | ## 实例app 35 | 36 | [ThreadSample](https://developer.android.com/shareables/training/ThreadSample.zip) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 DmrfCoder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_book/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 DmrfCoder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_book/LICENSE 2: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 DmrfCoder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-search/search.css: -------------------------------------------------------------------------------- 1 | /* 2 | This CSS only styled the search results section, not the search input 3 | It defines the basic interraction to hide content when displaying results, etc 4 | */ 5 | #book-search-results .search-results { 6 | display: none; 7 | } 8 | #book-search-results .search-results ul.search-results-list { 9 | list-style-type: none; 10 | padding-left: 0; 11 | } 12 | #book-search-results .search-results ul.search-results-list li { 13 | margin-bottom: 1.5rem; 14 | padding-bottom: 0.5rem; 15 | /* Highlight results */ 16 | } 17 | #book-search-results .search-results ul.search-results-list li p em { 18 | background-color: rgba(255, 220, 0, 0.4); 19 | font-style: normal; 20 | } 21 | #book-search-results .search-results .no-results { 22 | display: none; 23 | } 24 | #book-search-results.open .search-results { 25 | display: block; 26 | } 27 | #book-search-results.open .search-noresults { 28 | display: none; 29 | } 30 | #book-search-results.no-results .search-results .has-results { 31 | display: none; 32 | } 33 | #book-search-results.no-results .search-results .no-results { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /Activity/Interacting-With-Other-Apps/与其他APP交互——概述.md: -------------------------------------------------------------------------------- 1 | # 与其他APP交互——概述 2 | 3 | [原文(英文)地址](https://developer.android.com/training/basics/intents) 4 | 5 | Android应用通常有几项Activity。每个Activity都显示一个用户界面,允许用户执行特定任务(例如查看地图或拍照)。要将用户从一个Activity带到另一个Activity,您的应用必须使用Intent来定义应用的“意图”以执行某些操作。使用startActivity()等方法将Intent传递给系统时,系统会使用Intent来识别并启动相应的app组件。使用意图甚至允许您的应用启动包含在单独应用中的Activity。 6 | 7 | Intent可以是显式的,以便启动特定组件(特定的Activity实例),也可以是隐式的,以启动任何可以处理预期操作的组件(例如“捕获照片”)。 8 | 9 | 本文档向您展示如何使用Intent与其他应用程序执行一些基本交互,例如启动另一个应用程序,从该应用程序接收结果,以及让您的应用程序能够响应来自其他应用程序的Intent。 10 | 11 | ## 主要内容 12 | 13 | - 将用户导航到另一个APP 14 | 15 | > 介绍如何使用隐式Intent启动其他应用程序(APP) 16 | 17 | - 接收另一个Activity的返回结果 18 | 19 | > 介绍如何启动另一个Activity并接收其返回的结果 20 | 21 | - 允许其他APP启动你的Activity 22 | 23 | > 介绍如何通过声明应用程序接受的隐式意图的intent filter让你的App中的Activity可以被其他APP启动 24 | 25 | 关于本文档的额外参考信息,请参阅: 26 | 27 | - [Sharing Simple Data](https://developer.android.com/training/sharing/index.html) 28 | - [Sharing Files](https://developer.android.com/training/secure-file-sharing/index.html) 29 | - [Integrating Application with Intents (blog post)](http://android-developers.blogspot.com/2009/11/integrating-application-with-intents.html) 30 | - [Intents and Intent Filters](https://developer.android.com/guide/components/intents-filters.html) -------------------------------------------------------------------------------- /Activity/AppShortCuts/概述.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | [原文(英文)地址](https://developer.android.com/guide/topics/ui/shortcuts) 4 | 5 | 作为开发人员,您可以定义快捷方式以在应用中执行特定操作。这些快捷方式可以显示在支持的启动器中,帮助用户快速启动应用中的常见的或推荐的任务。 6 | 7 | 本文档教您如何创建和管理应用程序快捷方式。此外,您将学习一些可以改善快捷方式效果的最佳实践。 8 | 9 | ## 快捷方式的类型 10 | 11 | 每个快捷方式引用一个或多个Intent,当用户选择快捷方式时,每个Intent都会在应用中启动特定操作。您为应用创建的快捷方式类型取决于应用的关键用例。下面是几个您可以表示为快捷方式的示例操作包括: 12 | 13 | - 在电子邮件应用中撰写新电子邮件。 14 | - 将用户导航到地图应用中的特定位置。 15 | - 在通信应用中向朋友发送消息。 16 | - 在媒体应用中播放电视节目的下一集。 17 | - 在游戏应用中加载最后一个保存点。 18 | 19 | > 注意:只有处理Intent.ACTION_MAIN操作和Intent.CATEGORY_LAUNCHER类别的Main Activity 才能有快捷方式。如果应用程序有多个main activity,则需要为每个Activity定义一组快捷方式。 20 | 21 | 您可以为您的应用发布以下类型的快捷方式: 22 | 23 | - 静态快捷方式:在打包到APK或应用程序包中的资源文件中定义。 24 | - 动态快捷方式:只有在运行时,您的应用才能发布,更新和删除。 25 | - 如果用户授予权限,则可以在运行时将固定快捷方式添加到受支持的启动器。 26 | 27 | > 注意:用户还可以通过将应用程序的静态和动态快捷方式复制到启动器上来创建固定快捷方式。 28 | 29 | ## 快捷方式的限制 30 | 31 | 虽然您可以一次为应用程序发布最多五个快捷方式(静态和动态快捷方式一共最多5个),但大多数启动器只能显示四个。 32 | 33 | 但是,用户为你的应用程序创建的快捷方式的数量没有限制。即使您的应用无法删除固定的快捷方式,它仍然可以禁用它们。 34 | 35 | > 注意:虽然其他应用无法访问快捷方式中的元数据(metadata),但启动器本身可以访问此数据。因此,这些元数据应隐藏敏感的用户信息。 36 | 37 | 要开始为您的应用创建快捷方式,请参阅以下页面: 38 | 39 | - [创建快捷方式](./创建快捷方式.md) 40 | - [管理快捷方式](./管理快捷方式.md) 41 | - [快捷方式的最佳做法](./快捷方式的最佳做法.md) 42 | 43 | 有关可以在快捷方式上执行的操作的更多详细信息,请参阅[ShortcutManager](https://developer.android.com/reference/android/content/pm/ShortcutManager.html) API。 -------------------------------------------------------------------------------- /Activity/AppShortCuts/快捷方式的最佳做法.md: -------------------------------------------------------------------------------- 1 | # 快捷方式的最佳做法 2 | 3 | 当你涉及并且创建爱你你的应用程序的快捷方式时,请遵循以下原则: 4 | 5 | ## 遵循涉及原则 6 | 7 | 为了保证你app的快捷方式在视觉上和系统的app的快捷方式保持一致,请遵循 [App Shortcuts Design Guidelines](https://developer.android.com/shareables/design/app-shortcuts-design-guidelines.pdf). 8 | 9 | ## 仅发布四个不同的快捷方式 10 | 11 | 虽然API目前支持在任何给定时间为您的应用程序组合最多五个静态和动态快捷方式,但我们建议您仅发布四个不同的快捷方式,以改善其在启动器中的视觉外观。 12 | 13 | ## 限制快捷方式描述长度 14 | 15 | 菜单中的空间有限,在启动器中显示应用程序的快捷方式。如果可能,将快捷方式的“简短描述”的长度限制为10个字符,并将“长描述”的长度限制为25个字符。 16 | 17 | 有关静态快捷方式标签的详细信息,请参阅[自定义属性值](./创建快捷方式.md#custom-attribute)。对于动态和固定快捷方式,请阅读[setLongLabel()](https://developer.android.com/reference/android/content/pm/ShortcutInfo.Builder#setLongLabel(java.lang.CharSequence))和[setShortLabel()](https://developer.android.com/reference/android/content/pm/ShortcutInfo.Builder#setShortLabel(java.lang.CharSequence))的参考文档。 18 | 19 | ## 维护快捷方式和操作使用的历史记录 20 | 21 | 对于您创建的每个快捷方式,请考虑用户可以在应用程序中直接完成相同任务的不同方式。请记住在每种情况下调用reportShortcutUsed(),以便启动器程序保持用户执行快捷方式表示的操作的频率的准确历史记录。 22 | 23 | ## 仅在保留其含义时更新快捷方式 24 | 25 | 更改动态和固定快捷方式时,仅在保留其含义的前提下使用updateShortcuts()更新快捷方式的信息。否则,您应该根据您要重新创建的快捷方式的类型使用以下方法之一: 26 | 27 | - 动态快捷方式:addDynamicShortcuts()或setDynamicShortcuts()。 28 | - 固定快捷方式:requestPinShortcut()。 29 | 30 | 例如,如果您创建了导航到超市的快捷方式,则在超市名称发生变化但位置保持不变的情况下更新快捷方式是合适的。但是,如果用户开始在不同的超市位置购物,则最好创建新的快捷方式。 31 | 32 | ## 启动应用时,请检查动态快捷方式 33 | 34 | 当用户将数据恢复到新设备时,不会保留动态快捷方式。因此,我们建议您每次启动应用程序时检查getDynamicShortcuts()返回的对象数,并根据需要重新发布动态快捷方式,如“[备份和还原](./管理快捷方式.md#backup-and-restore)”中的代码段所示。 -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-search/search-engine.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Global search objects 6 | var engine = null; 7 | var initialized = false; 8 | 9 | // Set a new search engine 10 | function setEngine(Engine, config) { 11 | initialized = false; 12 | engine = new Engine(config); 13 | 14 | init(config); 15 | } 16 | 17 | // Initialize search engine with config 18 | function init(config) { 19 | if (!engine) throw new Error('No engine set for research. Set an engine using gitbook.research.setEngine(Engine).'); 20 | 21 | return engine.init(config) 22 | .then(function() { 23 | initialized = true; 24 | gitbook.events.trigger('search.ready'); 25 | }); 26 | } 27 | 28 | // Launch search for query q 29 | function query(q, offset, length) { 30 | if (!initialized) throw new Error('Search has not been initialized'); 31 | return engine.search(q, offset, length); 32 | } 33 | 34 | // Get stats about search 35 | function getEngine() { 36 | return engine? engine.name : null; 37 | } 38 | 39 | function isInitialized() { 40 | return initialized; 41 | } 42 | 43 | // Initialize gitbook.search 44 | gitbook.search = { 45 | setEngine: setEngine, 46 | getEngine: getEngine, 47 | query: query, 48 | isInitialized: isInitialized 49 | }; 50 | }); -------------------------------------------------------------------------------- /ArchitectureComponents/Android架构组件-给你的项目中添加组件.md: -------------------------------------------------------------------------------- 1 | # Android架构组件——给你的项目中添加架构组件 2 | 3 | 在开始之前,我们建议您阅读[应用程序架构指南](../应用程序架构指南.md),该指南包含一些适用于所有Android应用程序的有用原则,并展示了如何将架构组件一起使用。 4 | 5 | 架构组件可从Google的Maven存储库获得。要使用它们,必须将存储库添加到项目中。 6 | 7 | 打开项目的build.gradle文件(不是您的应用或模块的文件)并添加google()存储库,如下所示: 8 | 9 | ```groovy 10 | allprojects { 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | } 16 | ``` 17 | 18 | ## 声明依赖 19 | 20 | 打开应用程序或模块的build.gradle文件,并添加所需的工件作为依赖项。您可以为所有体系结构组件添加依赖项,也可以选择子集。 21 | 22 | 请参阅发行说明中有关为每个Archiecture Component声明依赖关系的说明: 23 | 24 | - [Futures (found in androidx.concurrent)](https://developer.android.com/jetpack/androidx/releases/concurrent) 25 | - [Lifecycle components (including ViewModel)](https://developer.android.com/jetpack/androidx/releases/lifecycle) 26 | - [Navigation (including SafeArgs)](https://developer.android.com/jetpack/androidx/releases/navigation) 27 | - [Paging](https://developer.android.com/jetpack/androidx/releases/paging) 28 | - [Room](https://developer.android.com/jetpack/androidx/releases/room) 29 | - [WorkManager](https://developer.android.com/jetpack/androidx/releases/work) 30 | 31 | 有关AndroidX重构的更多信息,以及它如何影响这些类包和模块ID,请参阅AndroidX重构文档([refactor documentation](https://developer.android.com/topic/libraries/support-library/refactor))。 32 | 33 | ## Kotlin 34 | 35 | 几个AndroidX依赖项支持Kotlin扩展模块。这些模块的名称后面附加了后缀“-ktx”。例如: 36 | 37 | ``` 38 | implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" 39 | ``` 40 | 41 | 变为: 42 | 43 | ``` 44 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 45 | ``` 46 | 47 | 更多信息包括在Kotlin扩展的文档,可以在[ktx documentation](https://developer.android.com/kotlin/ktx)查看。 48 | 49 | > 注意:对于基于Kotlin的应用程序,请确保使用kapt而不是annotationProcessor。你还应该添加kotlin-kapt插件。 -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-lunr/search-lunr 2.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Define global search engine 6 | function LunrSearchEngine() { 7 | this.index = null; 8 | this.store = {}; 9 | this.name = 'LunrSearchEngine'; 10 | } 11 | 12 | // Initialize lunr by fetching the search index 13 | LunrSearchEngine.prototype.init = function() { 14 | var that = this; 15 | var d = $.Deferred(); 16 | 17 | $.getJSON(gitbook.state.basePath+'/search_index.json') 18 | .then(function(data) { 19 | // eslint-disable-next-line no-undef 20 | that.index = lunr.Index.load(data.index); 21 | that.store = data.store; 22 | d.resolve(); 23 | }); 24 | 25 | return d.promise(); 26 | }; 27 | 28 | // Search for a term and return results 29 | LunrSearchEngine.prototype.search = function(q, offset, length) { 30 | var that = this; 31 | var results = []; 32 | 33 | if (this.index) { 34 | results = $.map(this.index.search(q), function(result) { 35 | var doc = that.store[result.ref]; 36 | 37 | return { 38 | title: doc.title, 39 | url: doc.url, 40 | body: doc.summary || doc.body 41 | }; 42 | }); 43 | } 44 | 45 | return $.Deferred().resolve({ 46 | query: q, 47 | results: results.slice(0, length), 48 | count: results.length 49 | }).promise(); 50 | }; 51 | 52 | // Set gitbook research 53 | gitbook.events.bind('start', function(e, config) { 54 | var engine = gitbook.search.getEngine(); 55 | if (!engine) { 56 | gitbook.search.setEngine(LunrSearchEngine, config); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-lunr/search-lunr.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | // Define global search engine 6 | function LunrSearchEngine() { 7 | this.index = null; 8 | this.store = {}; 9 | this.name = 'LunrSearchEngine'; 10 | } 11 | 12 | // Initialize lunr by fetching the search index 13 | LunrSearchEngine.prototype.init = function() { 14 | var that = this; 15 | var d = $.Deferred(); 16 | 17 | $.getJSON(gitbook.state.basePath+'/search_index.json') 18 | .then(function(data) { 19 | // eslint-disable-next-line no-undef 20 | that.index = lunr.Index.load(data.index); 21 | that.store = data.store; 22 | d.resolve(); 23 | }); 24 | 25 | return d.promise(); 26 | }; 27 | 28 | // Search for a term and return results 29 | LunrSearchEngine.prototype.search = function(q, offset, length) { 30 | var that = this; 31 | var results = []; 32 | 33 | if (this.index) { 34 | results = $.map(this.index.search(q), function(result) { 35 | var doc = that.store[result.ref]; 36 | 37 | return { 38 | title: doc.title, 39 | url: doc.url, 40 | body: doc.summary || doc.body 41 | }; 42 | }); 43 | } 44 | 45 | return $.Deferred().resolve({ 46 | query: q, 47 | results: results.slice(0, length), 48 | count: results.length 49 | }).promise(); 50 | }; 51 | 52 | // Set gitbook research 53 | gitbook.events.bind('start', function(e, config) { 54 | var engine = gitbook.search.getEngine(); 55 | if (!engine) { 56 | gitbook.search.setEngine(LunrSearchEngine, config); 57 | } 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /Activity/Handling-Android-App-Links/概述.md: -------------------------------------------------------------------------------- 1 | # 处理Android APP Links——概述 2 | 3 | 遵循设备链接的用户有一个目标:获取他们想要查看的内容。作为开发人员,您可以设置Android App Links,以便用户直接在应用中访问链接的特定内容,从而绕过应用选择对话框,也称为消除歧义对话框。由于Android App Links利用HTTP网址和与网站的关联,因此未安装应用的用户可直接访问您网站上的内容。 4 | 5 | ## Deep Linking和Android App Links 6 | 7 | 在深入了解如何实现之前,了解您可以在Android应用中创建的不同类型的链接非常重要:Deep Linking(Deep linking)和Android App Links(App Links)。 8 | 9 | Deep Linking是将用户直接带到App中特定内容的URLs。在Android中,您可以通过添加Intent filter并从传入的Intent中提取数据来设置Deep Linking,从而将用户导向正确的Activity。 10 | 11 | 但是,如果用户设备上安装的其他应用程序可以处理相同的Intent,则用户可能无法直接访问您的应用程序。例如,单击来自银行的电子邮件中的URL可能会导致出现一个对话框,询问用户是使用浏览器还是银行自己的应用程序来打开链接。 12 | 13 | Android 6.0(API级别23)及更高版本上的Android App Links允许应用程序将自己指定为给定类型链接的默认处理程序。如果用户不希望应用程序成为默认处理程序,则他们可以从其设备的系统设置中覆盖此行为。 14 | 15 | Android App Links具有以下优势: 16 | 17 | - 安全且具体:Android App Links使用链接到您的网站域的HTTP URL,因此没有其他应用可以使用您的链接。 Android App Links的一个要求是您通过我们的网站关联方法(website association methods)验证您域名的所有权。 18 | - 无缝的用户体验:由于Android App Links对您网站和应用中的相同内容使用单个HTTP URL,因此未安装该应用的用户只需访问您的网站而不是应用 - 没有404,没有错误。 19 | - Android Instant Apps支持:使用Android Instant Apps,您的用户无需安装即可运行您的Android应用。要为您的Android应用添加即时应用支持,请设置Android App Links并访问[g.co/InstantApps](https://g.co/InstantApps).。 20 | - 通过Google搜索吸引用户:用户可以通过在移动浏览器, [Google Search app](https://developer.android.com/training/app-indexing/index.html?hl=zh-cn),Android屏幕搜索或Google智能助理中点击Google中的网址,直接在应用中打开特定内容。 21 | 22 | ## 添加Android App Links 23 | 24 | 创建Android App Links的一般步骤如下: 25 | 26 | - 创建应用中特定内容的Deep Links:在您的manifest文件中,为您的网站的URI(website URIs)创建Intent filter,并配置您的应用以使用Intent中的data将用户导航到您应用中的正确内容。在 [创建指向您内容的Deep Links](./创建指向您内容的Deep-Links.md)中了解更多信息。 27 | 28 | - 添加深层链接验证:配置您的应用以请求验证应用链接。然后,在您的网站上发布数字资产链接JSON文件,以通过 [Google Search Console](https://support.google.com/webmasters/answer/6212023?hl=zh-cn)验证所有权。在 [Verify App Links](https://developer.android.com/training/app-links/verify-site-associations.html?hl=zh-cn).中了解更多信息 29 | 30 | 31 | 32 | 作为上述链接文档的替代方案, [Android App Links Assistant](https://developer.android.com/studio/write/app-link-indexing.html?hl=zh-cn)是Android Studio中的一个工具,可指导您完成创建Android App Links所需的每个步骤。 33 | 34 | 有关其他信息,请参阅以下资源: 35 | 36 | - [Add Android App Links in Android Studio](https://developer.android.com/studio/write/app-link-indexing.html?hl=zh-cn) 37 | - [Creating a Statement List](https://developers.google.com/digital-asset-links/v1/create-statement?hl=zh-cn) -------------------------------------------------------------------------------- /BackGroundTasks/Service/Service——向后台Service发送工作请求.md: -------------------------------------------------------------------------------- 1 | # Service——向后台Service发送工作请求 2 | 3 | [原文(英文)地址](https://developer.android.com/training/run-background-service/send-request#java) 4 | 5 | [上一篇文章](./Service——创建后台服务.md)介绍了如何创建一个[`JobIntentService`](https://developer.android.com/reference/android/support/v4/app/JobIntentService)类.这篇文章将介绍如何通过一个`Intent`来触发`JobIntentService`去执行一个操作,这个`Intent`中也可以用于`JobintentService`执行任务(操作)的一些数据。 6 | 7 | ## 创建工作请求并将其发送到`JobIntentService` 8 | 9 | 为了创建一个工作请求并将其发送给`JobIntentService`,我们需要创建一个`Intent`并`JobIntentService`通过调用`enqueueWork()`将其加入工作队列。你还可以向`Intent`中添加一些数据以提供给`JobIntentService`去执行操作。您还可以选择将数据添加到`Intent`(以`Intent extras`的形式)以供`JobIntentService`进行任务处理。有关创建Intent的更多信息,请阅读[Intent和IntentFilters——概述](../Intent-And-Intent-Filter/Intent和IntentFilters——概述.md)中的[构建Intent](../Intent-And-Intent-Filter/Intent和IntentFilters——概述.md#Building-an-intent)部分。 10 | 11 | 下面的代码段演示了这个过程: 12 | 13 | 1:为名为`RSSPullService`的`JobIntentService`类创建一个`Intent`: 14 | 15 | - kotlin 16 | 17 | ```java 18 | /* 19 | * Creates a new Intent to start the RSSPullService 20 | * JobIntentService. Passes a URI in the 21 | * Intent's "data" field. 22 | */ 23 | serviceIntent = Intent().apply { 24 | putExtra("download_url", dataUrl) 25 | } 26 | ``` 27 | 28 | - java 29 | 30 | ```java 31 | /* 32 | * Creates a new Intent to start the RSSPullService 33 | * JobIntentService. Passes a URI in the 34 | * Intent's "data" field. 35 | */ 36 | serviceIntent = new Intent(); 37 | serviceIntent.putExtra("download_url", dataUrl)); 38 | ``` 39 | 40 | 2:调用`enqueueWork()` 41 | 42 | - kotlin 43 | 44 | ```java 45 | private const val RSS_JOB_ID = 1000 46 | RSSPullService.enqueueWork(context, RSSPullService::class.java, RSS_JOB_ID, serviceIntent) 47 | ``` 48 | 49 | - java 50 | 51 | ```java 52 | // Starts the JobIntentService 53 | private static final int RSS_JOB_ID = 1000; 54 | RSSPullService.enqueueWork(getContext(), RSSPullService.class, RSS_JOB_ID, serviceIntent); 55 | ``` 56 | 57 | 注意你可以在`Activity`或者`Fragment`的任何地方发送这个请求(`Request`)。比如,如果你需要先获取用户的输入,你可以在`button`点击的回调事件中发起这个请求(`Request`)。 58 | 59 | 当你调用了`enqueueWork()`之后,`JobIntentService`将会执行定义在它`onHandleWork()`方法中的任务,执行完成之后`JobIntentService`会自己停止自己(`stop itself`)。 60 | 61 | 下一步就是将任务执行的结果返回给源的`Activity`或者`Fragment`,[下个文档](./Service——报告工作状态.md)将会介绍如何使用`BroadCastReceiver`去实现这个目的。 -------------------------------------------------------------------------------- /Activity/Parcelable和Bundle.md: -------------------------------------------------------------------------------- 1 | # Parcelable和Bundle 2 | 3 | [原文(英文)地址](https://developer.android.com/guide/components/activities/parcelables-and-bundles#java) 4 | 5 | Parcelable和Bundle对象旨在跨进程使用,例如IPC / Binder事务,具有Intent的Activity之间,以及跨配置更改存储瞬态。此文档介绍有关使用Parcelable和Bundle对象的建议和最佳实践。 6 | 7 | > 注意:Parcel不是通用的序列化机制,您绝不应将任何Parcel数据存储在磁盘上或通过网络发送。 8 | 9 | ## 不同Activity之间发送数据 10 | 11 | 当app创建一个Intent对象以通过startActivity(android.content.Intent)去启动一个新的Activity时,app可以通过putExtra(java.lang.String,java.lang.String)传递参数给新的Activity。 12 | 13 | 以下代码演示了如何进行该操作: 14 | 15 | - kotlin 16 | 17 | ```kotlin 18 | val intent = Intent(this, MyActivity::class.java).apply { 19 | putExtra("media_id", "a1b2c3") 20 | // ... 21 | } 22 | startActivity(intent) 23 | ``` 24 | 25 | - java 26 | 27 | ```java 28 | Intent intent = new Intent(this, MyActivity.class); 29 | intent.putExtra("media_id", "a1b2c3"); 30 | // ... 31 | startActivity(intent); 32 | ``` 33 | 34 | 系统包裹(编码,parcels)Intent的底层Bundle。然后,操作系统创建新Activity,之后解包(解码,un-parcels)Bundle,并将Intent传递给新Activity。 35 | 36 | 我们建议您使用Bundle类来传递操作系统已知的原语(primitives)对象。 Bundle类针对使用parcel的编码和解码进行了高度优化。 37 | 38 | 在某些情况下,您可能需要一种机制来跨Activity发送复合对象或复杂对象。在这种情况下,自定义类应该实现Parcelable,并提供正确的writeToParcel(android.os.Parcel,int)方法。它还必须提供一个名为CREATOR的非空字段,该字段实现Parcelable.Creator接口,其createFromParcel()方法用于将Parcel转换回当前对象。有关更多信息,请参阅[Parcelable](https://developer.android.com/reference/android/os/Parcelable.html)对象的参考文档。 39 | 40 | 通过intent发送数据时,应注意将数据大小限制为几KB。发送过大数据可能导致系统抛出TransactionTooLargeException异常。 41 | 42 | ## 不同进程间发送数据 43 | 44 | 在进程之间发送数据和在Activity之间执行此操作类似。但是,在进程之间发送时,我们建议您不要使用自定义parcelables。如果您将自定义Parcelable对象从一个应用程序发送到另一个应用程序,则需要确保发送和接收应用程序上都存在完全相同的自定义类版本。通常,这可能是跨两个应用程序使用的公共库。如果您的应用尝试向系统发送自定义parcelable,则可能会发生错误,因为系统无法解码(unmarshal)它不知道的类。 45 | 46 | 例如,应用程序可能使用AlarmManager类设置alarm(警报),并在alarm Intent上使用自定义Parcelable。当闹钟响起时,系统会修改Intent的的Extras中的Bundle以添加重复计数。此修改可能导致系统从Extras中解析自定义的Parcelable对象。反过来,这种解析可能导致应用程序在收到修改后的alarm Intent时崩溃,因为应用程序希望接收不再存在的Extra数据。 47 | 48 | Binder事务缓冲区具有有限的固定大小,目前是1MB,由进程正在进行的所有事务共享。由于此限制是在进程级别而不是在每个Activity级别,因此这些事务包括应用程序中的所有绑定事务,例如onSaveInstanceState,startActivity以及与系统的任何交互。超出大小限制时,将抛出TransactionTooLargeException。 49 | 50 | 对于savedInstanceState的特定情况,数据量应保持较小,因为只要用户可以导航回该Activity(即使Activity的进程被终止),系统进程就需要保持提供的数据。我们建议您将保存的状态保存在少于50k的数据中。 51 | 52 | > 注意:在Android 7.0(API 24)及之后,系统会抛出TransactionTooLargeException异常,在之前版本的Android系统中,系统紧紧会在logcat打印一条警告。 -------------------------------------------------------------------------------- /BackGroundTasks/Service/Service——创建后台服务.md: -------------------------------------------------------------------------------- 1 | # Service——创建后台服务 2 | 3 | [原文(英文)地址](https://developer.android.com/training/run-background-service/create-service) 4 | 5 | IntentService类提供了简单结构用于在单个后台线程运行操作。这使得它能够处理长时间运行的操作,而不会影响用户界面的响应能力。此外,IntentService不受大多数用户界面生命周期事件的影响,因此它会在关闭AsyncTask的情况下继续运行。 6 | 7 | IntentService有以下几点限制: 8 | 9 | - 他不能直接和你的用户界面通信,要将其执行结果显示在UI上,你必须先将结果发送到Activity。 10 | - 工作任务需要串行执行,如果IntentService中已经有一个操作在执行了,这时你又发送了一个新的任务,它会在前一个任务执行完成之后才执行第二个任务。 11 | - IntentService中运行的任务不能被中断。 12 | 13 | 虽然有上述限制,IntentService依然是简单后台任务的最佳实现方式。 14 | 15 | 本篇文章向你介绍如何创建你自己的IntentService子类,以及如何创建onHandleIntent()回调方法,最后像你介绍如何在manifest文件中定义IntentService。 16 | 17 | ## 处理传入的Intent 18 | 19 | 要为你的app创建IntentService组件,你应该创建一个继承自IntentService的子类,在该子类中你应该重写onHandleIntent()方法,就像下面这样: 20 | 21 | - kotlin 22 | 23 | - ```kotlin 24 | class RSSPullService : IntentService(RSSPullService::class.simpleName) 25 | 26 | override fun onHandleIntent(workIntent: Intent) { 27 | // Gets data from the incoming Intent 28 | val dataString = workIntent.dataString 29 | ... 30 | // Do work here, based on the contents of dataString 31 | ... 32 | } 33 | } 34 | ``` 35 | 36 | - java 37 | 38 | - ```java 39 | public class RSSPullService extends IntentService { 40 | @Override 41 | protected void onHandleIntent(Intent workIntent) { 42 | // Gets data from the incoming Intent 43 | String dataString = workIntent.getDataString(); 44 | ... 45 | // Do work here, based on the contents of dataString 46 | ... 47 | } 48 | } 49 | ``` 50 | 51 | 注意,其他Service的常见回调方法,比如onStartCommand()会被IntentService自动调用,在IntentService类中,你应该避免重写此类回调方法。 52 | 53 | 要了解创建IntentService的详细内容,请参考[拓展IntentService类](./Service——概述.md#extending-the-intentService-class) 54 | 55 | ## 在manifest文件中定义IntentService 56 | 57 | IntentService同样也需要在你应用的manifest文件中定义,你应该像Service那样为在下定义IntentService条目(entry): 58 | 59 | ```xml 60 | 63 | ... 64 | 68 | 71 | ... 72 | 73 | ``` 74 | 75 | android:name属性唯一标识了该IntentService的类名。 76 | 77 | 注意元素不含有intent filter,发送工作给IntentService执行的Activity需要使用显示Intent,所以没必要定义Intent filter。这也意味着只有同一个app中的组件或者其他含有相同user ID的应用中的组件才有权限访问该Service。 78 | 79 | 现在你已经有了基本的IntentService类,你可以使用Intent对象向其发送工作请求。[下篇文章](./Service——向后台Service发送工作请求.md)将介绍如何构建这些Intent对象并将它们发送到IntentService。 -------------------------------------------------------------------------------- /Activity/Handling-Android-App-Links/为Instant-APPs创建App-Links.md: -------------------------------------------------------------------------------- 1 | # 处理Android APP Links——为Instant APPs创建App Links 2 | 3 | [原文(英文)地址](https://developer.android.com/training/app-links/instant-app-links?hl=zh-cn) 4 | 5 | Android Instant APP是您的应用程序的小版本(small version),无需安装即可运行。用户只需点击网址即可启动您的应用,而不是安装APK。因此,所有Instant APP都需要通过使用Android App Links声明的URL进行访问。本页介绍了如何为 [Android Instant Apps](https://developer.android.com/topic/instant-apps/index.html?hl=zh-cn)使用Android App Links。 6 | 7 | > 注意:如果您没有Instant APP,那么您不需要阅读本指南,您应该通过阅读 [创建指向您内容的Deep Links](./创建指向您内容的Deep-Links.md) 来为您的可安装应用程序创建应用程序链接。 8 | 9 | ## App link概述 10 | 11 | 首先,这里是您应该了解的app links.的摘要: 12 | 13 | - 当您为应用中的Activity创建一个intent filter,允许用户使用URL链接直接跳转到应用中的特定屏幕时,这称为“Deep Link”。但是,其他应用程序可以声明类似的URL Intent filter,因此系统可能会询问用户打开哪个应用程序。要创建这些Deep Link,请阅读 [创建指向您内容的Deep Links](./创建指向您内容的Deep-Links.md) 。 14 | - 当您在与应用程序的HTTP Deep Link对应的网站上发布assetlinks.json文件时,您就可以验证您的应用是否是这些URL的真正所有者。因此,您已将Deep Link转换为Android App Link,这可确保您的应用在用户点击此类网址时立即打开。要创建App Link,请阅读 [验证App Links](./验证App-Links.md)。 15 | 因此,Android App Links只是您的网站经过验证的HTTP深层链接,因此用户无需选择要打开的应用程序。有关更具体的说明,请参阅深层链接和应用链接之间的差异。 16 | 17 | 但是,在这两种情况下,用户必须已安装您的应用程序。如果用户单击您的某个网站链接并且他们没有安装您的应用程序(并且没有其他应用程序处理该URL Intent),则会在Web浏览器中打开该URL。因此,创建Instant Apps可解决此问题 ,它允许用户通过简单地单击URL来打开您的应用程序,即使他们没有安装您的应用程序。 18 | 19 | 当最终用户对您的应用执行Google搜索时,Google搜索会显示带有“即时”徽章的网址。 20 | 21 | ## Instant App的App Link有何不同 22 | 23 | 如果你已经阅读了 [创建指向您内容的Deep Links](./创建指向您内容的Deep-Links.md) 和 [验证App Links](./验证App-Links.md),那么你已经完成了使app link与你的Instant App一起工作所需的大部分工作。使用Instant App的App link时,还有一些额外的规定: 24 | 25 | - 在您的Instant App中用作App link的所有Intent filter必须同时支持HTTP和HTTPS。例如: 26 | 27 | ```xml 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ``` 36 | 37 | 请注意,您不需要在第二个元素中包含host,因为在每个元素中,每个属性的所有组合都被视为有效(因此此intent过滤器也会解析`https:/ /www.example.com`)。 38 | 39 | - 每个网站域(domain)只能声明一个Instant app。 (这与为可安装应用程序创建App link时不同,后者允许您将网站与多个应用程序相关联。) 40 | 41 | ## 创建App link时的其他提醒 42 | 43 | - 您的Instant App中的所有HTTP URL Intent filter都应包含在您的可安装应用中。这很重要,因为一旦用户安装完整的应用程序,点击URL应始终打开已安装的应用程序,而不是Instant App 44 | - 您必须在即时和可安装应用程序中的至少一个intent过滤器中设置autoVerify =“true”。 (了解 [enable automatic verification](https://developer.android.com/training/app-links/verify-site-associations.html?hl=zh-cn#config-verify)。) 45 | - 您必须为每个域(domain)使用HTTPS协议(以及App link支持的子域)发布一个assetlinks.json。(请参阅 [验证App Links](./验证App-Links.md)中支持多个主机(host)的app links部分)。 46 | - assetlinks.json文件必须是有效的JSON,无需重定向即可提供,并且可供机器人访问(您的robots.txt必须允许抓取/.well-known/assetlinks.json)。 47 | - 建议不要在intent filter的host属性中使用通配符。 (了解 [验证App Links](./验证App-Links.md)中创建支持多个子域(subdomains)的App links部分) 48 | - 应使用单独的intent filter声明自定义host/scheme URL。 49 | - 确保您的App Link网址占据关键字词的热门搜索结果。 -------------------------------------------------------------------------------- /Activity/进程和应用程序的生命周期.md: -------------------------------------------------------------------------------- 1 | # 进程和应用程序的生命周期 2 | 3 | [原文(英文)地址](https://developer.android.com/guide/components/activities/process-lifecycle) 4 | 5 | 在大多数情况下,每一个Android应用程序都是一个独立的Linux进程,这个进程在一些代码需要被运行时被创建,一直存活到该进程不再被需要且系统需要清理该进程以腾出更多的内存给其他应用程序使用。 6 | 7 | Android应用程序一个基本的、不同一般程序的特性是一个Android应用程序的生命周期不能被该应用程序直接控制,相反的,它的生命周期由系统决定,系统根据系统知道正在运行的应用程序部分的组合,这些应用程序对用户的重要性以及系统中可用的总体内存来确定。 8 | 9 | 开发人员应该了解不同应用组件(特别是Activity、Service、BroadCastReceiver)对应用进程生命周期的影响。不正确地使用这些组件可能会导致系统杀死正在执行重要工作的应用进程。 10 | 11 | 关于进程生命周期导致bug的一个很常见的例子是BroadCastReceiver,当它在自己的onReceive()方法中接收到Intent并启动一个线程后return,一旦return,系统会认为BroadCastReceiver不再处于活动状态,因此不再需要托管该进程(除非其他应用程序组件依然处于活动状态),系统随时可能会终止进程以回收内存,这样就会终止之前在进程中onReceive()启动的线程,会破坏该线程中原本应该执行的工作。这个文艺的解决方案通常是从BroadCastReceiver中使用一个JobService,这样系统就可以知道该进程中仍有未完成的工作。 12 | 13 | 为了确定哪些进程在内存紧张的时候应该被销毁,Android根据进程中正在运行的组件以及这些组件的状态将进程划分为不同的"重要等级",按照重要程度依次有以下等级: 14 | 15 | ## 前台进程(foreground process) 16 | 17 | 前台进程是用户当前正在执行的操作所必需的进程。各种应用程序组件可以使其宿主进程以不同的方式被视为前台进程。如果满足以下任何条件,则认为进程处于前台: 18 | 19 | - 进程中有一个用户正在关注的Activity在运行(该Activity的onResume()方法已经被调用) 20 | - 进程中有一个BroadCastReceiver正在被运行(该BroadCastReceiver的onReceive()方法正在被执行) 21 | - 进程中有一个Service且其某一个回调方法正在被执行(Service.onCreate(),Service.onStart(),Service.onDestroy()) 22 | 23 | 系统中只会有数量很少的这样的进程,如果内存太低甚至这些进程都不能继续运行,这些只会作为最后的进程被杀死。通常,此时,设备已达到内存分页状态,因此需要杀死这些进程以保持用户界面正常响应。 24 | 25 | ## 可见进程(visible process) 26 | 27 | 可见进程正在进行用户当前意识到的工作,因此杀死它会对用户体验产生明显的负面影响。符合以下条件的进程即为可见进程: 28 | 29 | - 进程中有Activity对用户可见但是不在前台(对应Activity的onPause()已经被调用)。比如Activity被DIalog部分覆盖。 30 | - 进程中通过Service.startForground()启动了在前台运行的Service(这要求系统将服务视为用户可意识到的或基本上对用户可见的东西) 31 | - 进程正在托管系统用于用户知道的特定功能的服务,例如动态壁纸,输入法服务等。 32 | 33 | 在系统中运行的可见进程的数量比前台进程更少,但仍然是相对受控的。这些进程被认为是非常重要的,除非为了保持所有前台进程运行,否则不会被杀死这些可见进程。 34 | 35 | ## 服务进程(service process) 36 | 37 | 服务进程是一个持有使用startService()方法启动的服务的进程。虽然用户无法直接看到这些进程,但它们通常是用户关心的事情(例如后台网络数据上传或下载),因此系统将始终保持此类进程运行,除非没有足够的内存来保留所有前台进程和可见进程。 38 | 已经运行了很长时间(例如30分钟或更长时间)的Service可能会降级,以允许其进程被添加到下面描述的缓存LRU列表。这有助于避免出现内存泄漏或其他原因比如长时间运行服务占用大量RAM而导致系统无法有效使用缓存进程的情况。 39 | 40 | ## 缓存进程(cached process) 41 | 42 | 缓存进程是当前不需要的进程,因此当其他地方需要内存时,系统可以根据需要自由地终止缓存进程。在正常运行的系统中,这些是内存管理中涉及的唯一进程:运行良好的系统将始终具有多个缓存进程(用于在应用程序之间进行更有效的切换),并根据需要定期终止最旧的进程。只有在非常关键(且不可取)的情况下,系统才会杀死所有缓存进程,并且必须开始终止服务进程。 43 | 这些进程通常包含一个或多个当前对用户不可见的Activity实例(已调用并返回onStop()方法)。如果他们正确地实现了他们的活动生命周期(请参阅[Activity](https://developer.android.com/reference/android/app/Activity.html)以获取更多详细信息),当系统杀死此类进程时,它不会影响用户返回该应用程序时的体验:它可以在重新创建关联Activity时恢复以前保存的状态一个新的进程。 44 | 45 | 这些进程保存在伪LRU列表(pseudo-LRU list)中,列表中的最后一个进程是第一个被回收内存的进程。在此列表上排序的确切策略是平台的实现细节,但通常它会尝试在其他类型的进程之前保留更多有用的进程(一个托管用户的主应用程序,他们看到的最后一个Activity等)。还可以应用其他用于终止进程的策略:对允许的进程数量的硬限制,对进程可以持续缓存的时间量的限制等。 46 | 47 | ## 总结 48 | 49 | 在决定如何对进程进行分类时,系统将根据进程中当前起作用(活动)的所有组件中找到的最重要级别做出决策。有关每个组件如何对流程的整个生命周期做出贡献的更多详细信息,请参阅[Activity](https://developer.android.com/reference/android/app/Activity.html),[Service](https://developer.android.com/reference/android/app/Service.html)和 `BroadcastReceiver`文档。每个类的文档都更详细地描述了它们如何影响其应用程序的整个生命周期。 50 | 51 | 还可以基于进程对其具有的其他依赖性来增加进程的优先级。例如,如果进程A已使用Context.BIND_AUTO_CREATE标志绑定到Service,或者正在进程B中使用ContentProvider,则进程B的分类将始终至少与进程A一样重要。 -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-sharing/buttons.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-sharing/buttons 2.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | var SITES = { 3 | 'facebook': { 4 | 'label': 'Facebook', 5 | 'icon': 'fa fa-facebook', 6 | 'onClick': function(e) { 7 | e.preventDefault(); 8 | window.open('http://www.facebook.com/sharer/sharer.php?s=100&p[url]='+encodeURIComponent(location.href)); 9 | } 10 | }, 11 | 'twitter': { 12 | 'label': 'Twitter', 13 | 'icon': 'fa fa-twitter', 14 | 'onClick': function(e) { 15 | e.preventDefault(); 16 | window.open('http://twitter.com/home?status='+encodeURIComponent(document.title+' '+location.href)); 17 | } 18 | }, 19 | 'google': { 20 | 'label': 'Google+', 21 | 'icon': 'fa fa-google-plus', 22 | 'onClick': function(e) { 23 | e.preventDefault(); 24 | window.open('https://plus.google.com/share?url='+encodeURIComponent(location.href)); 25 | } 26 | }, 27 | 'weibo': { 28 | 'label': 'Weibo', 29 | 'icon': 'fa fa-weibo', 30 | 'onClick': function(e) { 31 | e.preventDefault(); 32 | window.open('http://service.weibo.com/share/share.php?content=utf-8&url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)); 33 | } 34 | }, 35 | 'instapaper': { 36 | 'label': 'Instapaper', 37 | 'icon': 'fa fa-instapaper', 38 | 'onClick': function(e) { 39 | e.preventDefault(); 40 | window.open('http://www.instapaper.com/text?u='+encodeURIComponent(location.href)); 41 | } 42 | }, 43 | 'vk': { 44 | 'label': 'VK', 45 | 'icon': 'fa fa-vk', 46 | 'onClick': function(e) { 47 | e.preventDefault(); 48 | window.open('http://vkontakte.ru/share.php?url='+encodeURIComponent(location.href)); 49 | } 50 | } 51 | }; 52 | 53 | 54 | 55 | gitbook.events.bind('start', function(e, config) { 56 | var opts = config.sharing; 57 | 58 | // Create dropdown menu 59 | var menu = $.map(opts.all, function(id) { 60 | var site = SITES[id]; 61 | 62 | return { 63 | text: site.label, 64 | onClick: site.onClick 65 | }; 66 | }); 67 | 68 | // Create main button with dropdown 69 | if (menu.length > 0) { 70 | gitbook.toolbar.createButton({ 71 | icon: 'fa fa-share-alt', 72 | label: 'Share', 73 | position: 'right', 74 | dropdown: [menu] 75 | }); 76 | } 77 | 78 | // Direct actions to share 79 | $.each(SITES, function(sideId, site) { 80 | if (!opts[sideId]) return; 81 | 82 | gitbook.toolbar.createButton({ 83 | icon: site.icon, 84 | label: site.text, 85 | position: 'right', 86 | onClick: site.onClick 87 | }); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——开始.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——开始 2 | 3 | 了解如何使您的开发环境随时可以使用数据绑定库( Data Binding Library),包括在Android Studio中支持数据绑定的代码。 4 | 5 | 数据绑定库提供了灵活性和广泛的兼容性 - 它是一个支持库,因此您可以将其用于运行Android 4.0(API级别14)或更高版本的设备。 6 | 7 | 建议在项目中使用最新的Gradle Android插件。但是,1.5.0及更高版本支持数据绑定。有关更多信息,请参阅如何更新Gradle的Android插件([update the Android Plugin for Gradle](https://developer.android.com/studio/releases/gradle-plugin.html#updating-plugin))。 8 | 9 | ## 构建环境 10 | 11 | 为了使用数据绑定库,请从Android Sdk manager的Support Repository中下载库文件,关于更多信息请参考[Update the IDE and SDK Tools](https://developer.android.com/studio/intro/update.html). 12 | 13 | 为了配置你的app可以使用数据绑定库,请添加"dataBinding"元素到你app module的build.gradle文件中,就像下面这样: 14 | 15 | ```xml 16 | android { 17 | ... 18 | dataBinding { 19 | enabled = true 20 | } 21 | } 22 | ``` 23 | 24 | > 注意:您必须为依赖于使用数据绑定的库的应用程序模块配置数据绑定,即使应用程序模块不直接使用数据绑定也是如此。 25 | 26 | ## Android Studio对于数据绑定的支持 27 | 28 | Android Studio支持许多用于数据绑定代码的编辑功能。例如,它支持数据绑定表达式的以下功能: 29 | 30 | - 语法突出显示 31 | - 标记表达式语言语法错误 32 | - XML代码自动完成 33 | - 参考,包括导航([navigation](https://www.jetbrains.com/help/idea/2017.1/navigation-in-source-code.html))(例如导航到声明)和快速文档([quick documentation](https://www.jetbrains.com/help/idea/2017.1/viewing-inline-documentation.html)) 34 | 35 | 36 | 37 | > 警告:数组和泛型类型(如[`Observable`](https://developer.android.com/reference/android/databinding/Observable.html)类)显示的错误可能是不正确的。 38 | 39 | 布局编辑器中的“预览(preview)”窗格显示数据绑定表达式的默认值(如果提供)。例如,“预览”窗格在以下示例中声明的TextView小部件上显示my_default值: 40 | 41 | ```xml 42 | 45 | ``` 46 | 47 | 如果只需要在项目的设计阶段显示默认值,则可以使用"tools"属性而不是默认表达式值,如“[Tools Attributes Reference](https://developer.android.com/studio/write/tool-attributes.html)”中所述。 48 | 49 | ## 用于绑定类的新数据绑定编译器 50 | 51 | Android Gradle插件版本3.1.0-alpha06包含一个生成绑定类的新的数据绑定编译器。新编译器会逐步创建绑定类,这在大多数情况下会加快构建过程。要了解有关绑定类的更多信息,请[生成的绑定类](./数据(Data Binding)绑定库——生成的绑定类.md)。 52 | 53 | 以前版本的数据绑定编译器在编译托管代码的同时生成绑定类,如果您的托管代码无法编译,您可能会收到多个报告未找到绑定类的错误,新的数据绑定编译器通过在托管编译器构建应用程序之前生成绑定类来防止这些错误。 54 | 55 | 要启用新的数据绑定编译器,请将以下选项添加到gradle.properties文件中: 56 | 57 | ```groovy 58 | android.databinding.enableV2=true 59 | ``` 60 | 61 | 您还可以通过在gradle命令中添加以下参数启用新编译器: 62 | 63 | ``` 64 | -Pandroid.databinding.enableV2=true 65 | ``` 66 | 67 | > 注意:Android插件版本3.1中的新数据绑定编译器不向后兼容。您需要生成所有绑定类,并启用此功能以利用增量编译( incremental compilation)。但是,Android插件版本3.2中的新编译器与先前版本生成的绑定类兼容。默认情况下会启用版本3.2中的新编译器。 68 | 69 | 启用新数据绑定编译器时,默认会遵守以下行为: 70 | 71 | - 在编译托管代码之前,Android Gradle插件会为您的布局生成绑定类。 72 | - 如果布局(layout)被多个目标资源配置(resource configuration)包含,则数据绑定库使用android.view.View作为共享相同资源ID的所有视图(View)的默认视图类型,而不是作为所有类型相同的视图默认视图类型。 73 | - 编译库模块( library modules)的绑定类会被编译并打包到相应的Android Archive(AAR)文件中。依赖于这些库模块的应用程序模块不再需要重新生成绑定类。有关AAR文件的更多信息,请参阅[Create an Android Library](https://developer.android.com/studio/projects/android-library)。 74 | - 模块(module)的绑定适配器不能更改模块依赖项的适配器的行为。绑定适配器仅影响其自己的模块中的代码和模块的使用者。 75 | 76 | ## 其他资源 77 | 78 | 想了解关于数据绑定的更多信息,请参阅: 79 | 80 | Samples 81 | 82 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 83 | 84 | Codelabs 85 | 86 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 87 | 88 | Blog posts 89 | 90 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /_book/ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——开始.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——开始 2 | 3 | 了解如何使您的开发环境随时可以使用数据绑定库( Data Binding Library),包括在Android Studio中支持数据绑定的代码。 4 | 5 | 数据绑定库提供了灵活性和广泛的兼容性 - 它是一个支持库,因此您可以将其用于运行Android 4.0(API级别14)或更高版本的设备。 6 | 7 | 建议在项目中使用最新的Gradle Android插件。但是,1.5.0及更高版本支持数据绑定。有关更多信息,请参阅如何更新Gradle的Android插件([update the Android Plugin for Gradle](https://developer.android.com/studio/releases/gradle-plugin.html#updating-plugin))。 8 | 9 | ## 构建环境 10 | 11 | 为了使用数据绑定库,请从Android Sdk manager的Support Repository中下载库文件,关于更多信息请参考[Update the IDE and SDK Tools](https://developer.android.com/studio/intro/update.html). 12 | 13 | 为了配置你的app可以使用数据绑定库,请添加"dataBinding"元素到你app module的build.gradle文件中,就像下面这样: 14 | 15 | ```xml 16 | android { 17 | ... 18 | dataBinding { 19 | enabled = true 20 | } 21 | } 22 | ``` 23 | 24 | > 注意:您必须为依赖于使用数据绑定的库的应用程序模块配置数据绑定,即使应用程序模块不直接使用数据绑定也是如此。 25 | 26 | ## Android Studio对于数据绑定的支持 27 | 28 | Android Studio支持许多用于数据绑定代码的编辑功能。例如,它支持数据绑定表达式的以下功能: 29 | 30 | - 语法突出显示 31 | - 标记表达式语言语法错误 32 | - XML代码自动完成 33 | - 参考,包括导航([navigation](https://www.jetbrains.com/help/idea/2017.1/navigation-in-source-code.html))(例如导航到声明)和快速文档([quick documentation](https://www.jetbrains.com/help/idea/2017.1/viewing-inline-documentation.html)) 34 | 35 | 36 | 37 | > 警告:数组和泛型类型(如[`Observable`](https://developer.android.com/reference/android/databinding/Observable.html)类)显示的错误可能是不正确的。 38 | 39 | 布局编辑器中的“预览(preview)”窗格显示数据绑定表达式的默认值(如果提供)。例如,“预览”窗格在以下示例中声明的TextView小部件上显示my_default值: 40 | 41 | ```xml 42 | 45 | ``` 46 | 47 | 如果只需要在项目的设计阶段显示默认值,则可以使用"tools"属性而不是默认表达式值,如“[Tools Attributes Reference](https://developer.android.com/studio/write/tool-attributes.html)”中所述。 48 | 49 | ## 用于绑定类的新数据绑定编译器 50 | 51 | Android Gradle插件版本3.1.0-alpha06包含一个生成绑定类的新的数据绑定编译器。新编译器会逐步创建绑定类,这在大多数情况下会加快构建过程。要了解有关绑定类的更多信息,请[生成的绑定类](./数据(Data Binding)绑定库——生成的绑定类.md)。 52 | 53 | 以前版本的数据绑定编译器在编译托管代码的同时生成绑定类,如果您的托管代码无法编译,您可能会收到多个报告未找到绑定类的错误,新的数据绑定编译器通过在托管编译器构建应用程序之前生成绑定类来防止这些错误。 54 | 55 | 要启用新的数据绑定编译器,请将以下选项添加到gradle.properties文件中: 56 | 57 | ```groovy 58 | android.databinding.enableV2=true 59 | ``` 60 | 61 | 您还可以通过在gradle命令中添加以下参数启用新编译器: 62 | 63 | ``` 64 | -Pandroid.databinding.enableV2=true 65 | ``` 66 | 67 | > 注意:Android插件版本3.1中的新数据绑定编译器不向后兼容。您需要生成所有绑定类,并启用此功能以利用增量编译( incremental compilation)。但是,Android插件版本3.2中的新编译器与先前版本生成的绑定类兼容。默认情况下会启用版本3.2中的新编译器。 68 | 69 | 启用新数据绑定编译器时,默认会遵守以下行为: 70 | 71 | - 在编译托管代码之前,Android Gradle插件会为您的布局生成绑定类。 72 | - 如果布局(layout)被多个目标资源配置(resource configuration)包含,则数据绑定库使用android.view.View作为共享相同资源ID的所有视图(View)的默认视图类型,而不是作为所有类型相同的视图默认视图类型。 73 | - 编译库模块( library modules)的绑定类会被编译并打包到相应的Android Archive(AAR)文件中。依赖于这些库模块的应用程序模块不再需要重新生成绑定类。有关AAR文件的更多信息,请参阅[Create an Android Library](https://developer.android.com/studio/projects/android-library)。 74 | - 模块(module)的绑定适配器不能更改模块依赖项的适配器的行为。绑定适配器仅影响其自己的模块中的代码和模块的使用者。 75 | 76 | ## 其他资源 77 | 78 | 想了解关于数据绑定的更多信息,请参阅: 79 | 80 | Samples 81 | 82 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 83 | 84 | Codelabs 85 | 86 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 87 | 88 | Blog posts 89 | 90 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /ArchitectureComponents/Android架构组件-概述.md: -------------------------------------------------------------------------------- 1 | # Android架构组件——概述 2 | 3 | Android体系结构组件是一组库(libraries),可帮助您设计健壮,可测试和可维护的应用程序。从用于管理UI组件生命周期和处理数据持久性的类开始。 4 | 5 | - 轻松管理应用程序的生命周期。[lifecycle-aware components](https://developer.android.com/topic/libraries/architecture/lifecycle)可帮助您管理Activity和Fragment生命周期。生存配置更改(Survive configuration changes,),避免内存泄漏并轻松将数据加载到UI中。 6 | - 使用[LiveData](https://developer.android.com/topic/libraries/architecture/livedata)构建数据对象,以便在基础数据库更改时通知视图(Vires)。 7 | - [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel)存储在应用程序切换(rotations)中未销毁的UI相关的数据。 8 | - [Room](https://developer.android.com/topic/libraries/architecture/room)是一个SQLite对象映射库。使用它来避免样板(boilerplate)代码并轻松地将SQLite表数据转换为Java对象。 Room提供SQLite语句的编译时检查,可以返回RxJava,Flowable和LiveData observable。 9 | 10 | ## 最新的视频和新闻 11 | 12 | ### 视频 13 | 14 | [Improve your App's Architecture](https://www.youtube.com/watch?v=7p22cSzniBM) 15 | 16 | ### 博客 17 | 18 | [7 Steps To Room](https://medium.com/google-developers/7-steps-to-room-27a5fe5f99b2) 19 | 20 | [ViewModels and LiveData: Patterns + AntiPatterns](https://medium.com/google-developers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54) 21 | 22 | [ViewModels : A Simple Example](https://medium.com/google-developers/viewmodels-a-simple-example-ed5ac416317e) 23 | 24 | [ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders](https://medium.com/google-developers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090) 25 | 26 | [Understanding migrations with Room](https://medium.com/google-developers/understanding-migrations-with-room-f01e04b07929) 27 | 28 | ## 其他资源 29 | 30 | 想学习更多Android架构组件的内容,请考虑查看以下资源: 31 | 32 | ### Samples 33 | 34 | - [Sunflower](https://github.com/googlesamples/android-sunflower):一个园艺应用程序,演示Android Jetpack的Android开发最佳实践。 35 | - [Android Architecture Components basic sample](https://github.com/googlesamples/android-architecture-components/tree/master/BasicSample) 36 | - [(more...)](https://developer.android.com/topic/libraries/architecture/additional-resources.html#samples) 37 | 38 | ### Codelabs 39 | 40 | - Android Room with a View [(Java)](https://codelabs.developers.google.com/codelabs/android-room-with-a-view) [(Kotlin)](https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin) 41 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 42 | - [(more...)](https://developer.android.com/topic/libraries/architecture/additional-resources.html#codelabs) 43 | 44 | ### Training 45 | 46 | - [Udacity: Developing Android Apps](https://www.udacity.com/course/new-android-fundamentals--ud851) 47 | 48 | ### Blog posts 49 | 50 | - [Announcing Architecture Components 1.0 Stable](https://android-developers.googleblog.com/2017/11/announcing-architecture-components-10.html) 51 | - [Android and Architecture](https://android-developers.googleblog.com/2017/05/android-and-architecture.html) 52 | - [(more...)](https://developer.android.com/topic/libraries/architecture/additional-resources.html#blogs) 53 | 54 | ### Videos 55 | 56 | - [Android Jetpack: what's new in Architecture Components (Google I/O '18)](https://www.youtube.com/watch?v=pErTyQpA390) 57 | - [Android Jetpack: easy background processing with WorkManager (Google I/O '18)](https://www.youtube.com/watch?v=IrKoBFLwTN0) 58 | - [(more...)](https://developer.android.com/topic/libraries/architecture/additional-resources.html#videos) -------------------------------------------------------------------------------- /Activity/Activity的状态变化.md: -------------------------------------------------------------------------------- 1 | # Activity的状态变化 2 | 3 | [原文(英文)地址](https://developer.android.com/guide/components/activities/state-changes) 4 | 5 | 不同的事件(一些是用户触发的,一些是系统触发的),可以导致Activity从一个状态转换到另一个状态。本节描述了发生此类转换的一些常见情况,以及如何处理这些转换。 6 | 7 | 有关Activity状态的更多信息,请参阅[理解Activity的生命周期](.Activity/Activity的生命周期.md)。要了解ViewModel类如何帮助您管理活动生命周期,请参阅[Understand the ViewModel Class](https://developer.android.com/topic/libraries/architecture/viewmodel.html). 8 | 9 | ## 发生配置更改(Configuration change occurs) 10 | 11 | 有很多事件的发生会导致配置被更改,最常见的例子就比如屏幕在横屏和竖屏之间切换,还有其他的一些事件比如语言发生改变或者输入设备发生改变等也会导致配置被更改。 12 | 13 | 当配置被更改时,Activity会被销毁并且重建,原来(旧)的Activity实例将会触发onPause()、onStop()、onDestroy()的调用,而新Activity实例将会被创建并且调用onCreate()、onStart()以及onResume()回调方法。 14 | 15 | 结合使用ViewModels、onSaveInstanceState()方法,以及/或本地持久化存储可以跨配置(across configuration changes.)保留Activity的UI状态。你应该根据你UI数据的复杂性、检索速度、内存的占用情况去决定如何结合这些可选的方法去实现你Activity的UI持久化。想要了解更多的信息请参阅 [Saving UI States](https://developer.android.com/topic/libraries/architecture/saving-states.html). 16 | 17 | ### 处理多窗口的情况 18 | 19 | 当一个app处于多窗口模式时(该模式可存在于系统版本高于等于Android7.0即ApI为24的设备上),如果配置发生改变,系统会通知当前正在运行的Activity,这样就可以执行上述配置发生改变时Activity的不同生命周期。不同于普通模式,处于多窗口模式的应用调整窗口大小后也会发生配置的更改,你可以在你的Activity中自行处理配置更改的情况,也可以允许系统销毁原有Activity后重建Activity。 20 | 21 | 关于更多的Activity在多窗口模式下的生命周期,请参阅Multi-Window Support](https://developer.android.com/guide/topics/ui/multi-window.html) 页面中的 [Multi-Window Lifecycle](https://developer.android.com/guide/topics/ui/multi-window.html#lifecycle)节。 22 | 23 | 在多窗口模式下,尽管有两个app同时对用户可见,但是在同一时间用户只会关注一个前台的app而且只有该一个app占有焦点,该Activity处于Resumed状态,而其他的处于前台但是没有占有焦点的Activity处于Paused状态。 24 | 25 | 当用户从app A切换到app B,系统会调用app A中当前(前台)Activity的onPause()方法,同时调用app B中当前(前台)Activity的onResume()方法。每次用户切换app时都会分别调用两个app中前台Activity的onPause()和onResume()方法。 26 | 27 | 关于更多多窗口模式的内容,请参阅[Multi-Window Support](https://developer.android.com/guide/topics/ui/multi-window.html). 28 | 29 | ## Activity或者Dialog出现在前台 30 | 31 | 如果一个新的Activity或者Dialog出现在前台并且获取焦点,同时部分地覆盖了之前的Activity,那么那个被部分覆盖的Activity会失去焦点同时进入Paused 状态,接着系统会调用该Activity的onPause()方法。 32 | 33 | 当被部分覆盖的Activity重新回到前台并获取到焦点时,系统会调用他的onResume()方法。 34 | 35 | 如果一个新的Activity或者Dialog出现在前台并获取到焦点,同时完全覆盖了之前的Activity,那么系统会快速连续地调用那个被完全覆盖的Activity的onPause()和onStop()方法。 36 | 37 | 当之前那个被完全覆盖的Activity 的实例重新回到前台时,系统会调用该Activity的OnRestart()、onStart()以及onResume()方法。 38 | 39 | 如果是之前被覆盖的Activity的新的实例从后台切换到前台,则只会调用它的onStart()和onResume()方法,不会调用onRestart()。 40 | 41 | > 注意:当用户按了home键后,系统所做对之前处于前台的Activity所做的操作和该Activity被完全覆盖时所做的操作一样。 42 | 43 | ## 用户点击了Back按键 44 | 45 | 如果一个Activity处于前台并且用户点击了Back按键,系统会调用该Activity的onPause()、onStop()、onDestroy()方法,当Activity被Destroy后,该Activity也会被从回退栈中移除。 46 | 47 | 必须记住的很重要的一点是默认情况下onSateInstanceState()方法在此种情况下不会被调用。This behavior is based on the assumption that the user tapped the **Back** button with no expectation of returning to the same instance of the activity. 48 | 49 | 然而,你可以重写onBackPressed()方法去实现一些自己的需求,比如弹出“确认退出”提示框。 50 | 51 | 在你重写了onBackPressed()方法的同时我们强烈建议你在你重写的方法体中调用super.onBackPressed()方法,否则后悔按钮可能会刺激到?( jarring to)用户。 52 | 53 | ## 系统杀死了APP进程 54 | 55 | 如果系统为了处于前台的app需要清理内存,那么处于后台的app可以被杀死以释放更多的内存。想要了解更多关于系统是如何决定是否杀死一个app的,可以参阅 [Activity state and ejection from memory](https://developer.android.com/guide/components/activities/activity-lifecycle.html#asem) 和 [Processes and Application Lifecycle](https://developer.android.com/guide/components/activities/process-lifecycle.html). 56 | 57 | 想要了解更多如何在系统杀死你的app的同时保存你Activity的UI状态,请参阅[Saving and restoring activity state](https://developer.android.com/guide/components/activities/activity-lifecycle.html#saras). 58 | 59 | -------------------------------------------------------------------------------- /BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(2)指定线程中运行的代码.md: -------------------------------------------------------------------------------- 1 | # 将操作发送到多个线程——指定线程中运行的代码 2 | 3 | [原文(英文)地址](https://developer.android.com/training/multiple-threads/define-runnable#java) 4 | 5 | 本文介绍如何实现[Runnable](https://developer.android.com/reference/java/lang/Runnable.html)类,该类在单独的线程上运行其[Runnable.run()](https://developer.android.com/reference/java/lang/Runnable.html#run())方法中的代码。您还可以将`Runnable`传递给另一个对象,然后将该对象附加到线程中并运行它。执行特定操作的一个或多个`Runnable`对象有时称为任务。 6 | 7 | `Thread`和`Runnable`都是基类,它们本身只有有限的功能。但是,它们是很多强大的`Android`类的基类,例如`HandlerThread`,`AsyncTask`和`IntentService`。 `Thread`和`Runnable`也是`ThreadPoolExecutor`类的基类。该类自动管理线程和任务队列,甚至可以并行运行多个线程。 8 | 9 | ## 定义实现Runnable的类 10 | 11 | 定义一个类并实现`Runnable`接口很简单,比如: 12 | 13 | - kotlin 14 | 15 | - ```kotlin 16 | class PhotoDecodeRunnable : Runnable { 17 | ... 18 | override fun run() { 19 | /* 20 | * Code you want to run on the thread goes here 21 | */ 22 | ... 23 | } 24 | ... 25 | } 26 | ``` 27 | 28 | - java 29 | 30 | - ```java 31 | public class PhotoDecodeRunnable implements Runnable { 32 | ... 33 | @Override 34 | public void run() { 35 | /* 36 | * Code you want to run on the thread goes here 37 | */ 38 | ... 39 | } 40 | ... 41 | } 42 | ``` 43 | 44 | ## 实现run()方法 45 | 46 | 在实现了`Runnable`接口的类中,`Runnable.run()`方法包含要在子线程执行的代码。通常,`Runnable`中允许执行任何内容。但请记住,`Runnable`不会在`UI`线程上运行,因此它无法直接更新`UI`(如更新`View`)。要与`UI`线程通信,您必须使用[与UI线程通信](./将操作发送到多个线程——与UI线程通信.md)中描述的技术。 47 | 48 | 在`run()`方法的开头,通过调用`Process.setThreadPriority()`并传入使用`THREAD_PRIORITY_BACKGROUND`,设置线程可以使用后台优先级。这种方法减少了`Runnable`对象的线程和`UI`线程之间的资源竞争。 49 | 50 | 您还应该通过调用`Thread.currentThread()`在`Runnable`中存储对`Runnable`对象对应的`Thread`的引用。 51 | 52 | 以下代码段显示了如何设置`run()`方法: 53 | 54 | - kotlin 55 | 56 | - ```kotlin 57 | class PhotoDecodeRunnable : Runnable { 58 | ... 59 | /* 60 | * Defines the code to run for this task. 61 | */ 62 | override fun run() { 63 | // Moves the current Thread into the background 64 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND) 65 | ... 66 | /* 67 | * Stores the current Thread in the PhotoTask instance, 68 | * so that the instance 69 | * can interrupt the Thread. 70 | */ 71 | photoTask.setImageDecodeThread(Thread.currentThread()) 72 | ... 73 | } 74 | ... 75 | } 76 | ``` 77 | 78 | - java 79 | 80 | - ```java 81 | class PhotoDecodeRunnable implements Runnable { 82 | ... 83 | /* 84 | * Defines the code to run for this task. 85 | */ 86 | @Override 87 | public void run() { 88 | // Moves the current Thread into the background 89 | android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); 90 | ... 91 | /* 92 | * Stores the current Thread in the PhotoTask instance, 93 | * so that the instance 94 | * can interrupt the Thread. 95 | */ 96 | photoTask.setImageDecodeThread(Thread.currentThread()); 97 | ... 98 | } 99 | ... 100 | } 101 | ``` 102 | 103 | ## 更多内容 104 | 105 | 关于Android中多线程的更多内容,请参阅[进程和线程——概述](…/BestPractices/Performance/进程和线程——概述.md) 106 | 107 | ## 示例app 108 | 109 | [ThreadSample](https://developer.android.com/shareables/training/ThreadSample.zip) -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-highlight/ebook.css: -------------------------------------------------------------------------------- 1 | pre, 2 | code { 3 | /* http://jmblog.github.io/color-themes-for-highlightjs */ 4 | /* Tomorrow Comment */ 5 | /* Tomorrow Red */ 6 | /* Tomorrow Orange */ 7 | /* Tomorrow Yellow */ 8 | /* Tomorrow Green */ 9 | /* Tomorrow Aqua */ 10 | /* Tomorrow Blue */ 11 | /* Tomorrow Purple */ 12 | } 13 | pre .hljs-comment, 14 | code .hljs-comment, 15 | pre .hljs-title, 16 | code .hljs-title { 17 | color: #8e908c; 18 | } 19 | pre .hljs-variable, 20 | code .hljs-variable, 21 | pre .hljs-attribute, 22 | code .hljs-attribute, 23 | pre .hljs-tag, 24 | code .hljs-tag, 25 | pre .hljs-regexp, 26 | code .hljs-regexp, 27 | pre .hljs-deletion, 28 | code .hljs-deletion, 29 | pre .ruby .hljs-constant, 30 | code .ruby .hljs-constant, 31 | pre .xml .hljs-tag .hljs-title, 32 | code .xml .hljs-tag .hljs-title, 33 | pre .xml .hljs-pi, 34 | code .xml .hljs-pi, 35 | pre .xml .hljs-doctype, 36 | code .xml .hljs-doctype, 37 | pre .html .hljs-doctype, 38 | code .html .hljs-doctype, 39 | pre .css .hljs-id, 40 | code .css .hljs-id, 41 | pre .css .hljs-class, 42 | code .css .hljs-class, 43 | pre .css .hljs-pseudo, 44 | code .css .hljs-pseudo { 45 | color: #c82829; 46 | } 47 | pre .hljs-number, 48 | code .hljs-number, 49 | pre .hljs-preprocessor, 50 | code .hljs-preprocessor, 51 | pre .hljs-pragma, 52 | code .hljs-pragma, 53 | pre .hljs-built_in, 54 | code .hljs-built_in, 55 | pre .hljs-literal, 56 | code .hljs-literal, 57 | pre .hljs-params, 58 | code .hljs-params, 59 | pre .hljs-constant, 60 | code .hljs-constant { 61 | color: #f5871f; 62 | } 63 | pre .ruby .hljs-class .hljs-title, 64 | code .ruby .hljs-class .hljs-title, 65 | pre .css .hljs-rules .hljs-attribute, 66 | code .css .hljs-rules .hljs-attribute { 67 | color: #eab700; 68 | } 69 | pre .hljs-string, 70 | code .hljs-string, 71 | pre .hljs-value, 72 | code .hljs-value, 73 | pre .hljs-inheritance, 74 | code .hljs-inheritance, 75 | pre .hljs-header, 76 | code .hljs-header, 77 | pre .hljs-addition, 78 | code .hljs-addition, 79 | pre .ruby .hljs-symbol, 80 | code .ruby .hljs-symbol, 81 | pre .xml .hljs-cdata, 82 | code .xml .hljs-cdata { 83 | color: #718c00; 84 | } 85 | pre .css .hljs-hexcolor, 86 | code .css .hljs-hexcolor { 87 | color: #3e999f; 88 | } 89 | pre .hljs-function, 90 | code .hljs-function, 91 | pre .python .hljs-decorator, 92 | code .python .hljs-decorator, 93 | pre .python .hljs-title, 94 | code .python .hljs-title, 95 | pre .ruby .hljs-function .hljs-title, 96 | code .ruby .hljs-function .hljs-title, 97 | pre .ruby .hljs-title .hljs-keyword, 98 | code .ruby .hljs-title .hljs-keyword, 99 | pre .perl .hljs-sub, 100 | code .perl .hljs-sub, 101 | pre .javascript .hljs-title, 102 | code .javascript .hljs-title, 103 | pre .coffeescript .hljs-title, 104 | code .coffeescript .hljs-title { 105 | color: #4271ae; 106 | } 107 | pre .hljs-keyword, 108 | code .hljs-keyword, 109 | pre .javascript .hljs-function, 110 | code .javascript .hljs-function { 111 | color: #8959a8; 112 | } 113 | pre .hljs, 114 | code .hljs { 115 | display: block; 116 | background: white; 117 | color: #4d4d4c; 118 | padding: 0.5em; 119 | } 120 | pre .coffeescript .javascript, 121 | code .coffeescript .javascript, 122 | pre .javascript .xml, 123 | code .javascript .xml, 124 | pre .tex .hljs-formula, 125 | code .tex .hljs-formula, 126 | pre .xml .javascript, 127 | code .xml .javascript, 128 | pre .xml .vbscript, 129 | code .xml .vbscript, 130 | pre .xml .css, 131 | code .xml .css, 132 | pre .xml .hljs-cdata, 133 | code .xml .hljs-cdata { 134 | opacity: 0.5; 135 | } 136 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [介绍](README.md) 4 | * Activity 5 | * [Activity介绍](./Activity/Activity介绍.md) 6 | * [Activity的生命周期](./Activity/Activity的生命周期.md) 7 | * [Activity的状态变化](./Activity/Activity的状态变化.md) 8 | * [测试你的Activity](./Activity/测试你的Activity.md) 9 | * [理解Task和回退栈](./Activity/理解Task和回退栈.md) 10 | * [进程和应用程序的生命周期](./Activity/进程和应用程序的生命周期.md) 11 | * [Parcelable和Bundle](./Activity/Parcelable和Bundle.md) 12 | * Fragment 13 | * [Fragment(概述)](./Activity/Fragment/Fragment概述.md) 14 | * [创建Fragment](./Activity/Fragment/创建Fragment.md) 15 | * [测试Fragment](./Activity/Fragment/测试Fragment.md) 16 | * [构建灵活的UI](./Activity/Fragment/构建灵活的UI.md) 17 | * [与其他Fragment通信](./Activity/Fragment/与其他Fragment通信.md) 18 | * 与其他应用交互 19 | * [概述](./Activity/Interacting-With-Other-Apps/与其他APP交互——概述.md) 20 | * [将用户导航到另一个APP](Activity/Interacting-With-Other-Apps/与其他APP交互——将用户导航到另一个APP.md) 21 | * [接收另一个Activity返回的结果](./Activity/Interacting-With-Other-Apps/与其他APP交互——接收另一个Activity返回的结果.md) 22 | * [允许其他应用启动您的Activity](./Activity/Interacting-With-Other-Apps/与其他APP交互——允许其他应用启动您的Activity.md) 23 | * 处理Android APP Link 24 | * [概述](./Activity/Handling-Android-App-Links/概述.md) 25 | * [创建指向您内容的DeepLinks](./Activity/Handling-Android-App-Links/创建指向您内容的Deep-Links.md) 26 | * [验证App Links](./Activity/Handling-Android-App-Links/验证App-Links.md) 27 | * [为Instant APPs创建App Links](./Activity/Handling-Android-App-Links/为Instant-APPs创建App-Links.md) 28 | * [加载器](./Activity/加载器.md) 29 | * [最近任务界面](./Activity/最近任务界面.md) 30 | * [多窗口支持-分屏](./Activity/多窗口支持(分屏).md) 31 | * App快捷启动方式 32 | * [概述](./Activity/AppShortCuts/概述.md) 33 | * [创建快捷方式](./Activity/AppShortCuts/创建快捷方式.md) 34 | * [管理快捷方式](./Activity/AppShortCuts/管理快捷方式.md) 35 | * [快捷方式的最佳做法](./Activity/AppShortCuts/快捷方式的最佳做法.md) 36 | * 应用小部件 37 | * [概述](./Activity/AppWidgets/App小部件概述.md) 38 | * [构建应用程序小部件](./Activity/AppWidgets/构建应用程序小部件.md) 39 | * [构建应用程序小部件主机](./Activity/AppWidgets/构建应用程序小部件主机(Host).md) 40 | * [应用程序架构指南](./应用程序架构指南.md) 41 | * 架构组件 42 | * [概述](./ArchitectureComponents/Android架构组件-概述.md) 43 | * [给你的项目中添加架构组件](./ArchitectureComponents/Android架构组件-给你的项目中添加组件.md) 44 | * [布局和绑定表达式](./ArchitectureComponents/Android架构组件-布局和绑定表达式.md) 45 | * [使用可观察的数据对象](./ArchitectureComponents/Android架构组件-使用可观察的数据对象.md) 46 | * [生成的绑定类](./ArchitectureComponents/Android架构组件-生成的绑定类.md) 47 | * Intent和Intent过滤器 48 | * [概述](./Intent-And-Intent-Filter/Intent和IntentFilters——概述.md) 49 | * [通用Intent](./Intent-And-Intent-Filter/Intent和IntentFilters——通用Intent.md) 50 | * 用户界面 51 | * [使用RecyclerView创建列表](./UserInterface/使用RecyclerView创建列表.md) 52 | * 后台任务 53 | * Service 54 | * [概述](./BackGroundTasks/Service/Service——概述.md) 55 | * [创建后台Service](./BackGroundTasks/Service/Service——创建后台服务.md) 56 | * [向后台Service发送工作请求](./BackGroundTasks/Service/Service——向后台Service发送工作请求.md) 57 | * [绑定Service](./BackGroundTasks/Service/Service——绑定(bound)Service.md) 58 | * [AIDL](./BackGroundTasks/Service/Service——AIDL.md) 59 | * [Broadcast](./BackGroundTasks/BroadCast.md) 60 | * 将操作分配到多个线程执行 61 | * [概述](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(1)概述.md) 62 | * [指定线程中运行的代码](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(2)指定线程中运行的代码.md) 63 | * [为多线程创建管理者](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(3)为多线程创建管理者(manager).md) 64 | * [在线程池中运行代码](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(4)在线程池中的线程中运行代码.md) 65 | * [与UI线程进行通信](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(5)与UI线程通信.md) 66 | * 最佳实践 67 | * Performance 68 | * [进程和线程——概述](./BestPractices/Performance/进程和线程——概述.md) 69 | 70 | -------------------------------------------------------------------------------- /ArchitectureComponents/DataBindingLibrary/数据绑定库——概述.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——概述 2 | 3 | 数据绑定(Data Binding)库是一个支持库,允许您使用声明性(declarative)格式而不是以编程方式将布局中的UI组件绑定到应用程序中的数据源。 4 | 5 | 布局通常在具有调用UI框架方法的代码的Activity中定义。例如,下面的代码调用findViewById()来查找TextView小部件并将其绑定到viewModel变量的userName属性: 6 | 7 | - kotlin 8 | 9 | ```kotlin 10 | findViewById(R.id.sample_text).apply { 11 | text = viewModel.userName 12 | } 13 | ``` 14 | 15 | - java 16 | 17 | ```java 18 | TextView textView = findViewById(R.id.sample_text); 19 | textView.setText(viewModel.getUserName()); 20 | ``` 21 | 22 | 以下示例说明如何使用数据绑定Data Binding)库直接在布局文件中将文本分配给窗口小部件,这消除了调用上面显示的任何Java代码的需要。请注意在赋值表达式中使用@ {}语法: 23 | 24 | ```xml 25 | 27 | ``` 28 | 29 | 布局文件中的绑定组件允许您在Activity中删除许多UI框架调用,使其更简单,更易于维护。这还可以提高应用程序的性能,并有助于防止内存泄漏和空指针异常。 30 | 31 | ## 使用数据(Data Binding)绑定库 32 | 33 | 以下文档帮助你学习如何在你的Android应用程序中使用数据绑定(Data Binding)库。 34 | 35 | ### [开始](./数据(Data Binding)绑定库——开始.md) 36 | 37 | 了解如何使您的开发环境可以使用数据绑定库,包括支持Android Studio中的数据绑定代码。 38 | 39 | ### [布局和绑定表达式](./数据(Data Binding)绑定库——布局和绑定表达式.md) 40 | 41 | 表达式语言允许您编写*将变量连接到布局中视图* 的表达式。数据绑定库自动生成将布局中的视图与数据对象绑定所需的类。该库提供了可在布局中使用的导入,变量和包含(imports, variables, includes )等功能。 42 | 43 | 库的这些功能与您现有的布局无缝共存。例如,可以在表达式中使用的绑定变量( binding variables)在数据元素(`data` element )内定义,该数据元素(`data` element )是UI布局的根元素的兄弟。这两个元素都包含在布局标记(`layout`tag)中,如以下示例所示: 44 | 45 | ```xml 46 | 48 | 49 | 52 | 53 | 54 | 55 | ``` 56 | 57 | ### [使用可观察的数据对象](./数据(Data Binding)绑定库——使用可观察的数据对象.md) 58 | 59 | 数据绑定库提供了类和方法,可以轻松地观察数据以进行更改。您不必担心在基础数据源(underlying data source)发生更改时刷新UI。您可以观察变量或其属性。该库允许您使对象,字段或集合(objects, fields, or collections)可观察。 60 | 61 | ### [生成的绑定类](./数据(Data Binding)绑定库——生成的绑定类.md) 62 | 63 | 数据绑定库生成用于访问布局的变量和视图的绑定类。此文档显示如何使用和自定义生成的绑定类。 64 | 65 | ### [绑定适配器(binding adapter)](./数据(Data Binding)绑定库——绑定适配器.md) 66 | 67 | 对于每个布局表达式,都有一个绑定适配器(binding adapter),该绑定适配器会进行框架调用以设置相应的属性或侦听器(listeners)。例如,绑定适配器可以负责调用setText()方法来设置text属性,或者调用setOnClickListener()方法来为click事件添加一个监听器。最常见的绑定适配器,例如本页示例中使用的android:text属性的适配器,可供您在android.databinding.adapters包中使用。有关常用绑定适配器的列表,请参阅适配器([adapters](https://android.googlesource.com/platform/frameworks/data-binding/+/studio-master-dev/extensions/baseAdapters/src/main/java/androidx/databinding/adapters))。您还可以创建自定义适配器,如以下示例所示: 68 | 69 | - kotlin 70 | 71 | ```kotlin 72 | @BindingAdapter("app:goneUnless") 73 | fun goneUnless(view: View, visible: Boolean) { 74 | view.visibility = if (visible) View.VISIBLE else View.GONE 75 | } 76 | ``` 77 | 78 | - java 79 | 80 | ```java 81 | @BindingAdapter("app:goneUnless") 82 | public static void goneUnless(View view, Boolean visible) { 83 | view.visibility = visible ? View.VISIBLE : View.GONE; 84 | } 85 | ``` 86 | 87 | ### [将布局视图绑定到体系结构组件](./数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md) 88 | 89 | Android支持库包含[架构组件](../ArchitectureComponents/Android架构组件-概述.md),您可以使用它来设计健壮,可测试和可维护的应用程序。您可以将架构组件与数据绑定库一起使用,以进一步简化UI的开发。 90 | 91 | ### [双向数据绑定](./数据(Data Binding)绑定库——双向数据绑定.md) 92 | 93 | 数据绑定库支持双向数据绑定。用于此类绑定的表示法支持接收对属性的数据更改并同时侦听对该属性的用户更新的能力。 94 | 95 | ## 其他资源 96 | 97 | 要了解有关数据绑定的更多信息,请参阅以下资源。 98 | 99 | Samples 100 | 101 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 102 | 103 | Codelabs 104 | 105 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 106 | 107 | Blog posts 108 | 109 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /_book/ArchitectureComponents/DataBindingLibrary/数据绑定库——概述.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——概述 2 | 3 | 数据绑定(Data Binding)库是一个支持库,允许您使用声明性(declarative)格式而不是以编程方式将布局中的UI组件绑定到应用程序中的数据源。 4 | 5 | 布局通常在具有调用UI框架方法的代码的Activity中定义。例如,下面的代码调用findViewById()来查找TextView小部件并将其绑定到viewModel变量的userName属性: 6 | 7 | - kotlin 8 | 9 | ```kotlin 10 | findViewById(R.id.sample_text).apply { 11 | text = viewModel.userName 12 | } 13 | ``` 14 | 15 | - java 16 | 17 | ```java 18 | TextView textView = findViewById(R.id.sample_text); 19 | textView.setText(viewModel.getUserName()); 20 | ``` 21 | 22 | 以下示例说明如何使用数据绑定Data Binding)库直接在布局文件中将文本分配给窗口小部件,这消除了调用上面显示的任何Java代码的需要。请注意在赋值表达式中使用@ {}语法: 23 | 24 | ```xml 25 | 27 | ``` 28 | 29 | 布局文件中的绑定组件允许您在Activity中删除许多UI框架调用,使其更简单,更易于维护。这还可以提高应用程序的性能,并有助于防止内存泄漏和空指针异常。 30 | 31 | ## 使用数据(Data Binding)绑定库 32 | 33 | 以下文档帮助你学习如何在你的Android应用程序中使用数据绑定(Data Binding)库。 34 | 35 | ### [开始](./数据(Data Binding)绑定库——开始.md) 36 | 37 | 了解如何使您的开发环境可以使用数据绑定库,包括支持Android Studio中的数据绑定代码。 38 | 39 | ### [布局和绑定表达式](./数据(Data Binding)绑定库——布局和绑定表达式.md) 40 | 41 | 表达式语言允许您编写*将变量连接到布局中视图* 的表达式。数据绑定库自动生成将布局中的视图与数据对象绑定所需的类。该库提供了可在布局中使用的导入,变量和包含(imports, variables, includes )等功能。 42 | 43 | 库的这些功能与您现有的布局无缝共存。例如,可以在表达式中使用的绑定变量( binding variables)在数据元素(`data` element )内定义,该数据元素(`data` element )是UI布局的根元素的兄弟。这两个元素都包含在布局标记(`layout`tag)中,如以下示例所示: 44 | 45 | ```xml 46 | 48 | 49 | 52 | 53 | 54 | 55 | ``` 56 | 57 | ### [使用可观察的数据对象](./数据(Data Binding)绑定库——使用可观察的数据对象.md) 58 | 59 | 数据绑定库提供了类和方法,可以轻松地观察数据以进行更改。您不必担心在基础数据源(underlying data source)发生更改时刷新UI。您可以观察变量或其属性。该库允许您使对象,字段或集合(objects, fields, or collections)可观察。 60 | 61 | ### [生成的绑定类](./数据(Data Binding)绑定库——生成的绑定类.md) 62 | 63 | 数据绑定库生成用于访问布局的变量和视图的绑定类。此文档显示如何使用和自定义生成的绑定类。 64 | 65 | ### [绑定适配器(binding adapter)](./数据(Data Binding)绑定库——绑定适配器.md) 66 | 67 | 对于每个布局表达式,都有一个绑定适配器(binding adapter),该绑定适配器会进行框架调用以设置相应的属性或侦听器(listeners)。例如,绑定适配器可以负责调用setText()方法来设置text属性,或者调用setOnClickListener()方法来为click事件添加一个监听器。最常见的绑定适配器,例如本页示例中使用的android:text属性的适配器,可供您在android.databinding.adapters包中使用。有关常用绑定适配器的列表,请参阅适配器([adapters](https://android.googlesource.com/platform/frameworks/data-binding/+/studio-master-dev/extensions/baseAdapters/src/main/java/androidx/databinding/adapters))。您还可以创建自定义适配器,如以下示例所示: 68 | 69 | - kotlin 70 | 71 | ```kotlin 72 | @BindingAdapter("app:goneUnless") 73 | fun goneUnless(view: View, visible: Boolean) { 74 | view.visibility = if (visible) View.VISIBLE else View.GONE 75 | } 76 | ``` 77 | 78 | - java 79 | 80 | ```java 81 | @BindingAdapter("app:goneUnless") 82 | public static void goneUnless(View view, Boolean visible) { 83 | view.visibility = visible ? View.VISIBLE : View.GONE; 84 | } 85 | ``` 86 | 87 | ### [将布局视图绑定到体系结构组件](./数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md) 88 | 89 | Android支持库包含[架构组件](../ArchitectureComponents/Android架构组件-概述.md),您可以使用它来设计健壮,可测试和可维护的应用程序。您可以将架构组件与数据绑定库一起使用,以进一步简化UI的开发。 90 | 91 | ### [双向数据绑定](./数据(Data Binding)绑定库——双向数据绑定.md) 92 | 93 | 数据绑定库支持双向数据绑定。用于此类绑定的表示法支持接收对属性的数据更改并同时侦听对该属性的用户更新的能力。 94 | 95 | ## 其他资源 96 | 97 | 要了解有关数据绑定的更多信息,请参阅以下资源。 98 | 99 | Samples 100 | 101 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 102 | 103 | Codelabs 104 | 105 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 106 | 107 | Blog posts 108 | 109 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /Activity/Fragment/创建Fragment.md: -------------------------------------------------------------------------------- 1 | # 创建一个Fragment 2 | 3 | [原文(英文)地址](https://developer.android.com/training/basics/fragments/creating) 4 | 5 | 您可以将Fragment视为Activity的模块化部分,它具有自己的生命周期,接收自己的输入事件,并且可以在Activity运行时添加或删除(有点像“子Activity”,您可以在不同的Activity中重用)。本文档介绍如何使用 [Support Library](https://developer.android.com/tools/support-library/index.html)继承Fragment类,以便您的应用程序与运行系统版本低至Android 1.6的设备保持兼容。 6 | 7 | 您应该创建一个生命周期感知组件(lifecycle-aware component),而不是在Fragment的生命周期方法中设置依赖组件(dependent components)。当Fragment在其生命周期中移动时,该组件可以监听到你Fragment生命周期过程中发生的任何改变。然后,可以在其他Fragments和Activities中重用生命周期感知组件,以避免代码重复,并减少在Fragments / Activities本身中需要进行的设置。有关更多信息,请阅读 [Handling Lifecycles with Lifecycle-Aware Components](https://developer.android.com/topic/libraries/architecture/lifecycle.html). 8 | 9 | 在开始之前,您必须设置Android项目以使用Support Library.。如果您之前未使用过Support Library.,请按照 [Support Library Setup](https://developer.android.com/tools/support-library/setup.html)将项目设置为使用v4库。您也可以在Activity中包含[app bar](https://developer.android.com/training/appbar/index.html),而不是使用与Android 2.1(API级别7)兼容的v7 appcompat库,还包括Fragment API。 10 | 11 | 有关实现Fragment的更多信息,请参阅[Fragment](./Activity/Fragment)。您还可以通过浏览相关的[sample app](http://developer.android.com/shareables/training/FragmentBasics.zip)了解更多信息。 12 | 13 | ## 创建一个Fragment类 14 | 15 | 为了创建一个Fragment,你应该继承Fragment类,重写其生命周期的回调方法,这和你使用Activity的方法类似。 16 | 17 | 你应该在onCreateView()中创建你Fragment的layout,事实上这是保证Fragment可以运行的基本回调方法,例如下面是一个指定Layout的示例代码: 18 | 19 | ```java 20 | import android.os.Bundle; 21 | import android.support.v4.app.Fragment; 22 | import android.view.LayoutInflater; 23 | import android.view.ViewGroup; 24 | 25 | public class ArticleFragment extends Fragment { 26 | @Override 27 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 28 | Bundle savedInstanceState) { 29 | // Inflate the layout for this fragment 30 | return inflater.inflate(R.layout.article_view, container, false); 31 | } 32 | } 33 | ``` 34 | 35 | 就像Activity一样,Fragment应该实现其他生命周期回调,这样就可以允许您管理Fragment的状态(当该Fragment被添加到Activity或被从Activity中移除的时候,或者在Activity生命周期变化的时候)。例如,当调用activity的onPause()方法时,Activity中的任何Fragment也会接收对onPause()的调用。 36 | 37 | 有关Fragment生命周期和回调方法的更多信息,请参阅[Fragment](./Activity/Fragment)。 38 | 39 | ## 使用XML文件将Fragment添加到Activity 40 | 41 | 虽然Fragment是可重用的,模块化UI组件,但Fragment类的每个实例都必须与父FragmentActivity相关联。您可以通过定义Activity布局XML文件中的Fragment来实现此关联。 42 | 43 | > 注意:FragmentActivity是支持库( Support Library )中提供的一个特殊Activity,用于处理早于API 11的系统版本上的Fragment。如果您支持的最低系统版本是API级别11或更高级别,则可以使用常规Activity。 44 | 45 | 下面是一个示例布局文件,当设备屏幕被视为“大”(由目录名中的大限定符指定)时,它会向Activity添加两个Fragment。 46 | 47 | res/layout-large/news_articles.xml: 48 | 49 | ```xml 50 | 54 | 55 | 60 | 61 | 66 | 67 | 68 | ``` 69 | 70 | > 提示:关于更多如何根据屏幕不同大小创建布局文件的内容,请参阅 [Supporting Different Screen Sizes](https://developer.android.com/training/multiscreen/screensizes.html). 71 | 72 | 然后将该layout文件应用在你的Activity中: 73 | 74 | ```java 75 | import android.os.Bundle; 76 | import android.support.v4.app.FragmentActivity; 77 | 78 | public class MainActivity extends FragmentActivity { 79 | @Override 80 | public void onCreate(Bundle savedInstanceState) { 81 | super.onCreate(savedInstanceState); 82 | setContentView(R.layout.news_articles); 83 | } 84 | } 85 | ``` 86 | 87 | 如果你在使用 [v7 appcompat library](https://developer.android.com/tools/support-library/features.html#v7-appcompat), 你的Activity应该继承自 `AppCompatActivity`,它是 FragmentActivity`的子类. 了解更多的信息请参考 [Adding the App Bar](https://developer.android.com/training/appbar/index.html)。 88 | 89 | > 注意:通过在布局XML文件中定义Fragment将Fragment添加到Activity布局时,无法在运行时删除Fragment。如果您计划在用户交互期间替换Fragment,则必须在Activity首次启动时将Fragment添加到Activity中,如[构建灵活的UI](./构建灵活的UI.md)中所示。 -------------------------------------------------------------------------------- /Activity/测试你的Activity.md: -------------------------------------------------------------------------------- 1 | # 测试你的Activity 2 | 3 | [原文(英文)地址](https://developer.android.com/guide/components/activities/testing) 4 | 5 | Activity作为应用程序与用户交互的容器,因此在发生设备级事件时测试Activity的不同行为尤为重要,比如: 6 | 7 | - 其他APP(比如设备来电APP)中断了你的Activity 8 | - 设备销毁并重建了你的Activity 9 | - 用户将你的Activity放置到了一个新的窗口环境,比如画中画(picture-in-picture ,PIP )或者多窗口模式。 10 | 11 | 尤为重要的一点是保证你的Activity在响应文章 [Activity的生命周期 ](./Activity/Activity的生命周期.md ) 中描述的事件时依然可以表现正常。 12 | 13 | 这篇文章主要描述如何评估应用程序维护数据完整性的能力以及如何评估良好的用户体验,因为您应用程序的Activity在其生命中的不同阶段会处于不同的状态。 14 | 15 | ## 驱动(Drive)一个Activity的状态 16 | 17 | 测试应用程序Activity的一个关键点是将Activity置于特定的状态,请使用ActivityScenario的实例,它是AndroidX Test库的一部分。通过使用此类,您可以将Activity置于模拟本文章开头所描述的设备级事件的状态。 18 | 19 | ActivityScenario是一个跨平台的API,您可以在本地单元测试(local unit tests )和设备集成测试(on-device integration tests)中使用它们。假设你测试程序在运行在线程A,被测试的程序运行在线程B,无论是在真机还是虚拟机上,ActivityScenario都可以保证线程安全即可以在A线程和B线程之间同步事件。该API还特别适用于评估被测试的Activity在销毁或创建时的行为方式。 20 | 21 | 本节介绍与此API相关的常见用法。 22 | 23 | ### 创建一个Activity 24 | 25 | 为了创建一个运行在测试环境下的Activity,你应该添加以下代码: 26 | 27 | ```kotlin 28 | @RunWith(AndroidJUnit4::class) 29 | class MyTestSuite { 30 | @Test fun testEvent() { 31 | val scenario = launchActivity() 32 | } 33 | } 34 | ``` 35 | 36 | 创建完Activity之后ActivityScenario会将Activity过渡到Resumed状态,该状态表名你的Activity正在运行且对用户可见,在这个状态下,你可以自由地使用 [Espresso UI tests](https://developer.android.com/training/testing/espresso)与你Activity上的View元素进行交互。 37 | 38 | 另外,你也可以使用ActivityScenarioRule在每一个test之前自动地调用ActivityScenario.launch,在测试结束的时候调用ActivityScenario.close。下面这个例子延时了如何定义一个规则(rule)并且获取scenario 的一个实例: 39 | 40 | ```kotlin 41 | @RunWith(AndroidJUnit4::class) 42 | class MyTestSuite { 43 | @get:Rule var activityScenarioRule = activityScenarioRule() 44 | 45 | @Test fun testEvent() { 46 | val scenario = activityScenarioRule.scenario 47 | } 48 | } 49 | ``` 50 | 51 | 52 | 53 | ### 将Activity驱动到一个新的状态 54 | 55 | 为了将Activity转换到比如Created或者Started这类状态,你应该调用moveToState()方法。该方法可以模拟您的Activity因为被另一个应用程序或者系统终端而被停止或暂停的情况。 56 | 57 | 下面是一个moveToState()的使用示例: 58 | 59 | ```kotlin 60 | @RunWith(AndroidJUnit4::class) 61 | class MyTestSuite { 62 | @Test fun testEvent() { 63 | val scenario = launchActivity() 64 | scenario.moveToState(State.CREATED) 65 | } 66 | } 67 | ``` 68 | 69 | > 警告:如果你尝试将被测试中的Activity转换为其当前的状态(也就是Activity当前的状态时A,你又在此时调用了moveToState(A)),则ActivityScenario将会忽略此请求,而不是抛出异常。 70 | 71 | ### 重建Activity 72 | 73 | 如果一个设备资源不足,系统可能会销毁Activity,当用户重新进入你的Activity时将会重建Activity,为了模拟这个场景,你应该调用recreate(): 74 | 75 | ```kotlin 76 | @RunWith(AndroidJUnit4::class) 77 | class MyTestSuite { 78 | @Test fun testEvent() { 79 | val scenario = launchActivity() 80 | scenario.recreate() 81 | } 82 | } 83 | ``` 84 | 85 | ActivityScenario类会维护已经保存的Activity的实例的状态和使用@NonConfigurationInstance注释的任何对象,这些对象将加载到你正在测试的Activity的心实例中。 86 | 87 | ### 接收Activity的返回结果 88 | 89 | 在你运行并且完成一个Activity之后,你可以使用以下代码接收到该Activity的返回结果: 90 | 91 | ```kotlin 92 | @RunWith(AndroidJUnit4::class) 93 | class MyTestSuite { 94 | @Test fun testResult() { 95 | val scenario = launchActivity() 96 | onView(withId(R.id.finish)).perform(click()) 97 | val resultCode = scenario.result.resultCode 98 | val resultData = scenario.result.resultData 99 | } 100 | } 101 | ``` 102 | 103 | 104 | 105 | ### 触发Activity中的操作 106 | 107 | ActivityScenario中的所有方法都是阻塞调用,因此API需要您在测试线程中运行他们。 108 | 109 | 为了触发你被测试Activity中的操作,使用Espresso视图匹配器与视图中的元素进行交互: 110 | 111 | ```kotlin 112 | @RunWith(AndroidJUnit4::class) 113 | class MyTestSuite { 114 | @Test fun testEvent() { 115 | val scenario = launchActivity() 116 | onView(withId(R.id.refresh)).perform(click()) 117 | } 118 | } 119 | ``` 120 | 121 | 如果需要调用Activity本身的方法,你可以通过实现ActivityAction安全地达到这个目的: 122 | 123 | ```kotlin 124 | @RunWith(AndroidJUnit4::class) 125 | class MyTestSuite { 126 | @Test fun testEvent() { 127 | val scenario = launchActivity() 128 | scenario.onActivity { activity -> 129 | activity.handleSwipeToRefresh() 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | > 注意:在测试类中,不要保留传递给onActivity()的对象的引用。因为这些引用会消耗系统资源,并且引用本身可能是陈旧的(失效的),因为框架可以重新创建传递给回调方法的Activity。 136 | 137 | 想要了解更多关于线程如何在Android test中工作的内容,参考 [Understand threads in tests](https://developer.android.com/training/testing/fundamentals#threads). 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /Activity/AppWidgets/App小部件概述.md: -------------------------------------------------------------------------------- 1 | # App小部件概述 2 | 3 | 小部件是主屏幕定制的重要方面。您可以将它们想象为用户可以直接在主屏幕上访问的应用程序中最重要的数据和功能的“一目了然的”视图。用户可以在其主屏幕面板上移动小部件,如果支持,还可以调整它们的大小以根据用户的偏好定制小部件中的信息。 4 | 5 | 此文档介绍了您可能要创建的不同类型的小部件以及要遵循的一些设计原则。要开始构建应用小部件,请阅读[构建应用小部件](./AppWidgets/构建应用小部件.md)。 6 | 7 | ## 小部件类型 8 | 9 | 在开始规划小部件时,请考虑您尝试构建的小部件的类型。小部件通常有以下类别: 10 | 11 | ### 信息小部件 12 | 13 | 信息小部件通常显示一些对用户很重要的关键信息,并跟踪信息随时间的变化情况。信息小部件的好例子是天气小部件,时钟小部件或体育比分跟踪器。触摸信息窗口小部件通常会启动关联的应用程序并打开窗口小部件信息的详细视图。 14 | 15 | ![widgets_info](https://ws4.sinaimg.cn/large/006tNc79gy1g1v9w6q8l0j30c704djrj.jpg) 16 | 17 | ### 集合(Collection)小部件 18 | 19 | 顾名思义,集合小部件专门用于显示相同类型的众多元素,例如来自图库应用程序的图片集合,来自新闻应用程序的文章集合或来自通信应用程序的电子邮件/消息集合。集合小部件通常关注两个用例:浏览集合、将集合的元素打开到其详细视图以供使用。集合小部件可以垂直滚动。 20 | 21 | ![widgets_collection_gmail](https://ws3.sinaimg.cn/large/006tNc79gy1g1v9yw6ytmj305306i3yv.jpg) 22 | 23 |
Listview 小部件
24 | 25 | ![widgets_collection_bookmarks](https://ws4.sinaimg.cn/large/006tNc79gy1g1v9z3k67nj306w06hdg6.jpg) 26 | 27 |
GridView小部件
28 | 29 | ### 控制小部件 30 | 31 | 控制小部件的主要目的是显示用户可以直接从主屏幕触发的常用功能,而无需先打开应用程序,它们被视为应用程序的远程控制。控件小部件的典型示例是音乐应用小部件,其允许用户在音乐应用之外播放,暂停或跳过音乐曲目。 32 | 33 | 与控件小部件的交互是否会延伸到关联的详细信息视图取决于控制小部件是否生成数据集。 34 | 35 | ![widgets_control](https://ws3.sinaimg.cn/large/006tNc79gy1g1vaso6beaj30c7042aa0.jpg) 36 | 37 | ### 混合小部件 38 | 39 | 虽然所有小部件倾向于倾向于上述三种类型中的一种,但实际上许多小部件是组合不同类型的元素的混合小部件。 40 | 41 | 出于规划小部件窗口的目的,将窗口小部件围绕其中一个基本类型居中,并根据需要添加其他类型的元素。 42 | 43 | 音乐播放器小部件主要是控件小部件,但也使用户了解当前正在播放的音轨。它基本上将控件小部件与信息小部件类型的元素组合在了一起。 44 | 45 | ![widgets_hybrid](https://ws4.sinaimg.cn/large/006tNc79gy1g1vasukn8kj30c703yq2t.jpg) 46 | 47 | ## 小部件限制 48 | 49 | 尽管小部件可以被当做"迷你app",但是在你开始设计你的小部件之前,还是需要了解一些重要的小部件限制: 50 | 51 | ### 手势 52 | 53 | 由于小部件位于主屏幕上,因此它们必须与已经建立主屏幕上的导航共存。与全屏应用程序相比,这限制了窗口小部件中可支持的手势。例如虽然某些应用程序可以支持用户横向滑动以在不同屏幕之间切换,但是该手势已经在主屏幕上应用以便在主屏幕之间导航,所以就不能应用在小部件上。 54 | 55 | 唯一可用于小部件的手势是: 56 | 57 | - 触摸 58 | - 垂直滑动 59 | 60 | ![widgets_gestures](https://ws1.sinaimg.cn/large/006tNc79gy1g1vb2bg0jlj30gs0ev46g.jpg) 61 | 62 | ### 元素 63 | 64 | 鉴于上述交互方式的限制,依赖于受限制的手势的一些UI构建块( UI building blocks)也不可用于窗口小部件。有关支持的构建块的完整列表以及有关布局限制的更多信息,请参阅构建应用程序小部件中[创建应用程序布局](./构建应用程序小部件.md#Creating-the-App-Widget-Layout)部分。 65 | 66 | ## 设计原则 67 | 68 | ### 小部件内容 69 | 70 | 窗口小部件是一种可以通过"宣传"您app中有趣的内容和新内容来很好的吸引用户到你的app的方式。 71 | 72 | 就像报纸头版上的teasers一样,小部件应该整合并集中应用程序的信息,然后在应用程序中提供更丰富的细节。或者换句话说:小部件是“小吃”信息,而应用程序是“用餐”。作为底线,始终确保您的应用程序更显示详细信息,而不是窗口小部件已显示的信息。 73 | 74 | ### 小部件导航 75 | 76 | 除了单纯的信息内容,您还应该考虑通过提供应用程序常用区域的导航链接来完善您的小部件产品。这使用户可以更快地完成工作,并将应用程序的功能范围扩展到主屏幕。 77 | 78 | 适用存在于小部件中的典型导航链接有: 79 | 80 | - 生成功能:这些功能允许用户为应用创建新内容,例如创建新文档或新消息。 81 | - 在顶层打开应用程序:点击信息元素通常会将用户导航到较低级别的详细信息界面。提供对应用程序顶层的访问可提高导航的灵活性,还可以替换用于从主屏幕导航到应用程序的专用应用程序的快捷方式。使应用程序图标作为可用的还可以为您的窗口小部件提供清晰的标识,以防您显示的数据不明确。 82 | 83 | ### resize小部件 84 | 85 | ![widgets_resizing01](https://ws1.sinaimg.cn/large/006tNc79gy1g1vbk94aq0j30fm0c5aha.jpg) 86 | 87 | 长按之后松开可以将可调整大小的小部件设置为调整大小模式。用户可以使用拖动手柄(就是上图上的四个点)或小部件角来设置所需的大小。 88 | 89 | 调整大小允许用户在主面板放置网格的约束内调整窗口小部件的高度和/或宽度。您可以决定您的窗口小部件是否可以自由调整大小,或者是否受限于水平或垂直大小更改。如果您小部件本身是固定大小的,则不必支持调整大小。 90 | 91 | 允许用户调整窗口小部件的大小具有重要的好处: 92 | 93 | - 他们可以微调他们想要在每个小部件上看到多少信息。 94 | - 它们可以更好地影响其主页面板上的小部件和快捷方式的布局。 95 | 96 | 规划窗口小部件的调整大小策略取决于您正在创建的窗口小部件的类型。列表或基于网格的集合小部件通常很简单,因为调整小部件的大小只会扩展或收缩垂直滚动区域。无论窗口小部件的大小如何,用户仍可以将所有信息元素滚动到视图中。但是,信息小部件需要更多的手动规划,因为它们不可滚动且所有内容必须适应给定大小。您必须动态调整窗口小部件的内容和布局,使其达到用户通过调整大小操作定义的大小。 97 | 98 | ![widgets_resizing02](https://ws1.sinaimg.cn/large/006tNc79gy1g1vbpf8esgj30s902tglj.jpg) 99 | 100 | 在这个简单的示例中,用户可以分4个步骤水平调整天气小部件的大小,并在小部件大小增长时在当前位置显示更丰富的天气的信息。 101 | 102 | 对于每个小部件大小,确定应该显示的应用程序信息量。对于较小的尺寸,请将更关注于必要的信息,然后在窗口小部件水平和垂直增长时添加更多信息。 103 | 104 | ### 考虑布局 105 | 106 | 根据您目标设备的放置网格( placement grid)的尺寸来布置窗口小部件很有诱惑力,在布局窗口小部件时,这可能是一个有用的近似初始值,但请记住以下几点: 107 | 108 | - grid cells的数量、大小、边距会因设备而异,因此,保证您的控件非常灵活,可以容纳比预期更多或更少的空间是非常重要的。 109 | - 实际上,当用户调整窗口小部件的大小时,系统将以dp级别的大小进行响应,您的窗口小部件可以在此基础上重绘自己。规划小部件调整大小策略跨“size buckets"而不是可变的网格维度(grid dimensions)将为您提供最可靠的结果。 110 | 111 | ### 小部件配置 112 | 113 | 有时,小部件需要先设置才能发挥作用。例如,可以考虑一个电子邮件小部件,您需要在显示收件箱之前提供帐户,或者是静态照片小部件,其中用户必须设置要从图片库中显示的图片。 114 | 115 | Android小部件在小部件被放到主面板后立即显示其配置选项。保证窗口小部件易于配置,不要提供超过2-3个配置元素的小部件。使用Dialog而不是full-screen样式的Activity来呈现配置选项并保留用户位置的上下文(user's context of place),即使这样做需要使用多个对话框。 116 | 117 | 设置完成后,通常没有太多理由重新访问设置。因此,Android小部件不显示“设置”或“配置”按钮。 118 | 119 | ![widgets_config](https://ws4.sinaimg.cn/large/006tNc79gy1g1vc2rmh34j307p06odg9.jpg) 120 | 121 |
将Play小部件添加到主面板后,小部件会要求用户指定小部件应显示的媒体类型。
122 | 123 | ## 总结 124 | 125 | - 专注于您的小部件上的一小部分可浏览信息,在应用中展开详细信息。 126 | - 为您的需求选择正确的小部件类型。 127 | - 对于可调整大小的小部件,请规划小部件的内容应如何适应不同的大小。 128 | - 通过确保布局能够拉伸和收缩,使您的窗口小部件独立于方向和设备。 -------------------------------------------------------------------------------- /Activity/Fragment/测试Fragment.md: -------------------------------------------------------------------------------- 1 | # 测试Fragment 2 | 3 | [原文(英文)地址](https://developer.android.com/training/basics/fragments/testing#drive-fragment-new-state) 4 | 5 | Fragment在您的应用程序中充当可重用容器,允许您在各种Activity和布局配置中呈现相同的用户界面布局。鉴于这些Fragment的多功能性,测试它们提供一致且资源有效的体验非常重要: 6 | 7 | - 您的Fragment外观应该在布局配置中保持一致,包括支持更大屏幕尺寸或横向设备方向的配置。 8 | - 除非Fragment对用户可见,否则不要创建Fragment的视图层次结构。 9 | 10 | 本文档描述了如何在测试Fragment时使用框架提供的API。 11 | 12 | ## 驱动Fragment的状态 13 | 14 | 为了帮助设置执行这些测试的条件,AndroidX提供了一个FragmentScenario库来创建Fragment并更改其状态。 15 | 16 | > 注意:要成功运行包含FragmentScenario对象的测试,请在测试线程中运行API的方法。要了解有关Android测试中如何使用不同线程的更多信息,请参阅[Understand threads in tests](https://developer.android.com/training/testing/fundamentals#threads). 17 | 18 | ### 配置测试工件位置 19 | 20 | 为了使用FragmentScenario,在应用程序的测试APK中定义Fragment测试工件,如以下代码片段所示: 21 | 22 | ``` 23 | dependencies { 24 | // ... 25 | debugImplementation 'androidx.fragment:fragment-testing:1.1.0-alpha01' 26 | } 27 | ``` 28 | 29 | ### 创建Fragment 30 | 31 | FragmentScenario包含用于启动以下类型的Fragment的方法: 32 | 33 | 该方法还支持以下类型的Fragment: 34 | 35 | - 图形Fragment(*Graphical*),包含用户界面。要启动此类型的Fragment,请调用launchFragmentInContainer()。 FragmentScenario将Fragment附加到Activity的根视图控制器。This containing activity is otherwise empty. 36 | - 非图形Fragment(*Non-graphical*)(有时称为无头Fragment- *headless fragments*),用于存储或执行对多个Activity中包含的信息的短期处理( short-term processing )。要启动此类型的Fragment,请调用launchFragment()。 FragmentScenario将这种类型的Fragment附加到一个完全空的Activity,一个没有根视图的Activity。 37 | 38 | 在启动其中一个Fragment类型后,FragmentScenario将测试中的Fragment驱动到RESUMED状态。此状态表示Fragment正在运行。如果您正在测试图形Fragment,那么用户也可以看到它,因此您可以使用[Espresso UI tests](https://developer.android.com/training/testing/espresso)评估有关其UI元素的信息。 39 | 40 | 以下代码段显示了如何启动每种类型的Fragment: 41 | 42 | #### 图形Fragment 43 | 44 | ```kotlin 45 | @RunWith(AndroidJUnit4::class) 46 | class MyTestSuite { 47 | @Test fun testEventFragment() { 48 | // The "state" and "factory" arguments are optional. 49 | val fragmentArgs = Bundle().apply { 50 | putInt("selectedListItem", 0) 51 | } 52 | val factory = MyFragmentFactory() 53 | val scenario = launchFragmentInContainer( 54 | fragmentArgs, factory) 55 | onView(withId(R.id.text)).check(matches(withText("Hello World!"))) 56 | } 57 | } 58 | ``` 59 | 60 | #### 非图形Fragment 61 | 62 | ```kotlin 63 | @RunWith(AndroidJUnit4::class) 64 | class MyTestSuite { 65 | @Test fun testEventFragment() { 66 | // The "state" and "factory" arguments are optional. 67 | val fragmentArgs = Bundle().apply { 68 | putInt("numElements", 0) 69 | } 70 | val factory = MyFragmentFactory() 71 | val scenario = launchFragment(fragmentArgs, factory) 72 | } 73 | } 74 | ``` 75 | 76 | ### 重建Fragment 77 | 78 | 如果设备的资源不足,系统可能会销毁宿主Activity,当用户重新返回app时需要你的app重建Fragment,为了模拟这个场景,调用recreate(): 79 | 80 | ```kotlin 81 | @RunWith(AndroidJUnit4::class) 82 | class MyTestSuite { 83 | @Test fun testEventFragment() { 84 | val scenario = launchFragmentInContainer() 85 | scenario.recreate() 86 | } 87 | } 88 | ``` 89 | 90 | 当FragmentScenario类重新创建被测试的Fragment时,Fragment将返回到重新创建之前所处的生命周期状态。 91 | 92 | ### 驱动Fragment到新的状态 93 | 94 | 在您的应用程序的UI测试中,通常只需启动并重新创建测试中的Fragment即可。但是,在细粒度单元测试中,您还可以评估Fragment在从一个生命周期状态转换到另一个生命周期状态时的行为。 95 | 96 | 要将Fragment驱动到不同的生命周期状态,请调用moveToState()。此方法支持以下状态作为参数:CREATED,STARTED,RESUMED和DESTROYED。此操作模拟包含您Fragment的宿主Activity因为它被另一个应用程序或系统操作中断而更改其状态的情况。 97 | 98 | 注意:如果将Fragment转换为DESTROYED状态,则无法将Fragment驱动到其他状态,也无法将Fragment附加到其他Activity。 99 | moveToState()的示例用法显示在以下代码段中: 100 | 101 | ```kotlin 102 | @RunWith(AndroidJUnit4::class) 103 | class MyTestSuite { 104 | @Test fun testEventFragment() { 105 | val scenario = launchFragmentInContainer() 106 | scenario.moveToState(State.CREATED) 107 | } 108 | } 109 | ``` 110 | 111 | > 警告:如果您尝试将测试中的Fragment转换为其当前状态,FragmentScenario会将此请求视为无操作,而不是抛出异常。特别注意,API允许您连续多次将片段转换为DESTROYED状态。 112 | 113 | ### 触发Fragment的行为 114 | 115 | 要在测试中的Fragment中触发某个行为,请使用Espresso视图匹配器与视图中的元素进行交互: 116 | 117 | ```kotlin 118 | @RunWith(AndroidJUnit4::class) 119 | class MyTestSuite { 120 | @Test fun testEventFragment() { 121 | val scenario = launchFragmentInContainer() 122 | onView(withId(R.id.refresh)) 123 | .perform(click()) 124 | } 125 | } 126 | ``` 127 | 128 | 如果需要在Fragment本身上调用方法,例如响应选项菜单中的选择,则可以通过实现FragmentAction安全地执行此操作: 129 | 130 | ```kotlin 131 | @RunWith(AndroidJUnit4::class) 132 | class MyTestSuite { 133 | @Test fun testEventFragment() { 134 | val scenario = launchFragmentInContainer() 135 | scenario.onFragment(fragment -> 136 | fragment.onOptionsItemSelected(clickedItem) { 137 | // Update fragment's state based on selected item. 138 | } 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | 145 | 146 | > 注意:在测试类中,不要保留传递给onFragment()的对象的引用。这些引用消耗系统资源,并且引用本身可能是陈旧的,因为框架可以重新创建传递给回调方法的片段。 147 | 148 | -------------------------------------------------------------------------------- /Activity/Fragment/构建灵活的UI.md: -------------------------------------------------------------------------------- 1 | # 构建灵活的UI 2 | 3 | [原文(英文)地址](https://developer.android.com/training/basics/fragments/fragment-ui) 4 | 5 | 在设计应用程序以支持各种屏幕尺寸时,您可以在不同的布局配置中重复使用Fragment,以根据可用的屏幕空间优化用户体验。 6 | 7 | 例如,在手机设备上,一次只显示一个Fragment用于单窗格用户界面可能是合适的。相反,您可能希望在具有更宽屏幕尺寸的平板电脑上并排设置Fragment,以向用户显示更多信息。 8 | 9 | ![fragments-screen-mock](https://ws1.sinaimg.cn/large/006tNc79gy1g1sxcmsejmj30ga06dq3h.jpg) 10 | 11 |
图一:两个Fragment,以不同的配置显示,用于不同屏幕尺寸的相同Activity。在大屏幕上,两个Fragment并排放置,但在手机设备上,一次只能放入一个Fragment,因此Fragment必须在用户导航时互相替换。
12 | 13 | FragmentManager提供的方法允许你在Activity运行时添加、移除、替换Fragment以创造动态的体验。 14 | 15 | 关于Fragment的更多实现信息请参阅一下资源: 16 | 17 | - [Fragment](./) 18 | - [Supporting Tablets and Handsets](https://developer.android.com/guide/practices/tablets-and-handsets.html) 19 | 20 | ## 在Activity运行时添加Fragment 21 | 22 | 相比于上一个文档[创建Fragment](./创建Fragment.md)中所述的在布局文件中定义Activity的Fragment,您可以在Activity运行时期间向Activity添加Fragment。如果您计划在Activity期间更改Fragment,则必须采用此种方法(在Activity运行时期间向Activity添加Fragment)。 23 | 24 | 要执行诸如添加或删除Fragment之类的事务,必须使用FragmentManager创建FragmentTransaction,它提供API以添加,删除,替换和执行其他Fragment事务。 25 | 26 | 如果您的Activity允许删除和替换Fragment,则应在Activity的onCreate()方法中将初始Fragment添加到Activity中。 27 | 28 | 处理Fragment时的一个重要规则 (特别是在运行时添加Fragment时): 您的Activity布局必须包含一个容器视图,您可以在其中插入Fragment。 29 | 30 | 以下布局是上一个文档中显示的布局的替代方案,一次只显示一个Fragment。为了将一个Fragment替换为另一个Fragment,Activity的布局包括一个空的FrameLayout,它充当Fragment容器。 31 | 32 | 请注意,文件名与上一课中的布局文件相同,但布局目录没有"large"限定符,因此当设备屏幕小于"大(large)"时,将使用此布局,因为屏幕不适合同时放置两个Fragment。 33 | 34 | res/layout/news_articles.xml: 35 | 36 | ```xml 37 | 41 | ``` 42 | 43 | 在您的Activity中,调用getSupportFragmentManager()以使用Support Library APIs获取FragmentManager。然后调用beginTransaction()创建FragmentTransaction并调用add()来添加Fragment。 44 | 45 | 您可以使用相同的FragmentTransaction为Activity执行多个Fragment事务,准备好进行更改后,必须调用commit()。 46 | 47 | 例如,以下是如何将Fragment添加到先前的布局: 48 | 49 | ```java 50 | import android.os.Bundle; 51 | import android.support.v4.app.FragmentActivity; 52 | 53 | public class MainActivity extends FragmentActivity { 54 | @Override 55 | public void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | setContentView(R.layout.news_articles); 58 | 59 | // Check that the activity is using the layout version with 60 | // the fragment_container FrameLayout 61 | if (findViewById(R.id.fragment_container) != null) { 62 | 63 | // However, if we're being restored from a previous state, 64 | // then we don't need to do anything and should return or else 65 | // we could end up with overlapping fragments. 66 | if (savedInstanceState != null) { 67 | return; 68 | } 69 | 70 | // Create a new Fragment to be placed in the activity layout 71 | HeadlinesFragment firstFragment = new HeadlinesFragment(); 72 | 73 | // In case this activity was started with special instructions from an 74 | // Intent, pass the Intent's extras to the fragment as arguments 75 | firstFragment.setArguments(getIntent().getExtras()); 76 | 77 | // Add the fragment to the 'fragment_container' FrameLayout 78 | getSupportFragmentManager().beginTransaction() 79 | .add(R.id.fragment_container, firstFragment).commit(); 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | 因为Fragment已在运行时添加到FrameLayout容器中(而不是使用元素在Activity的布局中定义它) ,则该Activity可以删除Fragment并将其替换为另一个Fragment。 86 | 87 | ## 用另一个Fragment替换当前Fragment 88 | 89 | 替换Fragment的过程类似于添加Fragment,但需要使用replace()方法而不是add()。 90 | 91 | 请记住,当您执行Fragment事务(例如替换或删除Fragment事务)时,通常允许用户向后导航并“撤消”更改。要允许用户在Fragment事务中向后导航,必须在提交FragmentTransaction之前调用addToBackStack()。 92 | 93 | > 注意:删除或替换Fragment并将事务添加到后台堆栈时,被移除的Fragment将会Stopped而不是Destroyed。如果用户导航回还原Fragment,则会重新启动。如果不将事务添加到后台堆栈,则在删除或替换时Destroy该Fragment。 94 | 95 | 用另一个Fragment替换一个Fragment的示例: 96 | 97 | ```java 98 | // Create fragment and give it an argument specifying the article it should show 99 | ArticleFragment newFragment = new ArticleFragment(); 100 | Bundle args = new Bundle(); 101 | args.putInt(ArticleFragment.ARG_POSITION, position); 102 | newFragment.setArguments(args); 103 | 104 | FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 105 | 106 | // Replace whatever is in the fragment_container view with this fragment, 107 | // and add the transaction to the back stack so the user can navigate back 108 | transaction.replace(R.id.fragment_container, newFragment); 109 | transaction.addToBackStack(null); 110 | 111 | // Commit the transaction 112 | transaction.commit(); 113 | ``` 114 | 115 | addToBackStack()方法采用可选的字符串参数,该参数指定事务的唯一名称。除非您计划使用FragmentManager.BackStackEntry API执行高级Fragment操作,否则不需要该名称。 116 | 117 | > 注意:ragment add replace 区别 118 | > 119 | > 1. replace 先删除容器中的内容,再添加 120 | > 2. add直接添加,可以配合hide适用 -------------------------------------------------------------------------------- /Activity/Fragment/与其他Fragment通信.md: -------------------------------------------------------------------------------- 1 | # 与其他Fragment通信 2 | 3 | [原文(英文)地址](https://developer.android.com/training/basics/fragments/communicating) 4 | 5 | 为了重用Fragment UI组件,您应该将每个组件构建为一个完全独立的模块化组件,以定义自己的布局和行为。一旦定义了这些可重用的Fragment,就可以将它们与Activity关联,并将它们与应用程序逻辑相连接,以实现整个复合UI。 6 | 7 | 通常,您会希望一个Fragment与另一个Fragment进行通信,例如根据用户事件更改内容。所有Fragment-to-Fragment通信都应该通过共享的ViewModel或通过关联的Activity完成。两个Fragment永远不应该直接通信。 8 | 9 | Fragment之间通信的推荐方法是创建共享的ViewModel对象。两个Fragment都可以通过其宿主Activity访问ViewModel。 Fragments可以更新ViewModel中的数据,如果使用LiveData公开数据,只要从ViewModel观察LiveData,新状态就会被推送到另一个Fragment。要了解如何实现此类通信,请阅读[ViewModel guide](https://developer.android.com/topic/libraries/architecture/viewmodel.html).中的“在Fragment之间共享数据”部分。 10 | 11 | 如果您无法使用共享ViewModel在Fragments之间进行通信,则可以使用接口手动实现通信流。然而,这最终需要更多的工作来实现,并且不容易在其他Fragment中重用。 12 | 13 | ## 定义接口 14 | 15 | 要允许Fragment与其宿主Activity进行通信,您可以在Fragment类中定义接口并在Activity中实现它。 Fragment在其onAttach()生命周期方法中捕获(attach)接口实现,然后可以调用Interface方法以与Activity通信。 16 | 17 | 以下是Fragment to Activity通信的示例: 18 | 19 | - HeadlinesFragment 20 | 21 | ```java 22 | public class HeadlinesFragment extends ListFragment { 23 | OnHeadlineSelectedListener callback; 24 | 25 | public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener callback) { 26 | this.callback = callback; 27 | } 28 | 29 | // This interface can be implemented by the Activity, parent Fragment, 30 | // or a separate test implementation. 31 | public interface OnHeadlineSelectedListener { 32 | public void onArticleSelected(int position); 33 | } 34 | 35 | // ... 36 | } 37 | ``` 38 | 39 | - MainActivity 40 | 41 | ```java 42 | public static class MainActivity extends Activity 43 | implements HeadlinesFragment.OnHeadlineSelectedListener{ 44 | // ... 45 | 46 | @Override 47 | public void onAttachFragment(Fragment fragment) { 48 | if (fragment instanceof HeadlinesFragment) { 49 | HeadlinesFragment headlinesFragment = (HeadlinesFragment) fragment; 50 | headlinesFragment.setOnHeadlineSelectedListener(this); 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | 现在,Fragment可以通过使用OnHeadlineSelectedListener接口的mCallback实例调用onArticleSelected()方法(或接口中的其他方法)来向Activity传递消息。 57 | 58 | 例如,当用户单击列表项时,将调用Fragment中的以下方法。该Fragment使用回调接口将事件传递给父Activity。 59 | 60 | ```java 61 | @Override 62 | public void onListItemClick(ListView l, View v, int position, long id) { 63 | // Send the event to the host activity 64 | callback.onArticleSelected(position); 65 | } 66 | ``` 67 | 68 | ## 实现接口 69 | 70 | 为了从Fragment接收事件回调,托管它的Activity必须实现Fragment类中定义的接口。 71 | 72 | 例如,以下Activity实现了上面示例中的接口: 73 | 74 | ```java 75 | public static class MainActivity extends Activity 76 | implements HeadlinesFragment.OnHeadlineSelectedListener{ 77 | ... 78 | 79 | public void onArticleSelected(int position) { 80 | // The user selected the headline of an article from the HeadlinesFragment 81 | // Do something here to display that article 82 | } 83 | } 84 | ``` 85 | 86 | ## 发送Message到其他Fragment 87 | 88 | 宿主Activity可以通过使用findFragmentById()捕获Fragment实例,然后直接调用Fragment的public方法来将消息传递给Fragment。 89 | 90 | 例如,假设上面显示的Activity可能包含另一个Fragment,该Fragment用于显示由上述回调方法返回的数据指定的项目。在这种情况下,Activity可以将回调方法中收到的信息传递给将显示该项目的另一个Fragment: 91 | 92 | ```java 93 | public static class MainActivity extends Activity 94 | implements HeadlinesFragment.OnHeadlineSelectedListener{ 95 | ... 96 | 97 | public void onArticleSelected(int position) { 98 | // The user selected the headline of an article from the HeadlinesFragment 99 | // Do something here to display that article 100 | 101 | ArticleFragment articleFrag = (ArticleFragment) 102 | getSupportFragmentManager().findFragmentById(R.id.article_fragment); 103 | 104 | if (articleFrag != null) { 105 | // If article frag is available, we're in two-pane layout... 106 | 107 | // Call a method in the ArticleFragment to update its content 108 | articleFrag.updateArticleView(position); 109 | } else { 110 | // Otherwise, we're in the one-pane layout and must swap frags... 111 | 112 | // Create fragment and give it an argument for the selected article 113 | ArticleFragment newFragment = new ArticleFragment(); 114 | Bundle args = new Bundle(); 115 | args.putInt(ArticleFragment.ARG_POSITION, position); 116 | newFragment.setArguments(args); 117 | 118 | FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 119 | 120 | // Replace whatever is in the fragment_container view with this fragment, 121 | // and add the transaction to the back stack so the user can navigate back 122 | transaction.replace(R.id.fragment_container, newFragment); 123 | transaction.addToBackStack(null); 124 | 125 | // Commit the transaction 126 | transaction.commit(); 127 | } 128 | } 129 | } 130 | ``` 131 | 132 | 关于更多Fragment的实现,请参阅[Fragment](./),你也可以在 [relevant sample app](http://developer.android.com/shareables/training/FragmentBasics.zip)中了解更多信息。 -------------------------------------------------------------------------------- /Activity/Handling-Android-App-Links/创建指向您内容的Deep-Links.md: -------------------------------------------------------------------------------- 1 | # 处理Android APP Links——创建指向您内容的Deep Links 2 | 3 | [原文(英文)地址](https://developer.android.com/training/app-links/deep-linking) 4 | 5 | 当单击链接或编程请求调用Web URI Intent时,Android系统将按顺序尝试以下每个操作,直到请求成功为止: 6 | 7 | - 打开用户首选的可以处理URI的应用程序(如果已指定)。 8 | - 打开唯一可以处理URI的可用应用程序。 9 | - 允许用户从对话框中选择应用程序。 10 | 11 | 请按照以下步骤创建和测试指向您的内容的链接。您还可以使用Android Studio中的 [App Links Assistant](https://developer.android.com/studio/write/app-link-indexing.html?hl=zh-cn) 添加Android App Links。 12 | 13 | ## 为传入的链接添加Intent filter 14 | 15 | 要创建一个导向你app内容的链接,你需要在你的manifest中添加intent filter并且设置如下三个属性: 16 | 17 | - 18 | 19 | > 指定ACTION_VIEW Intent操作,以便可以从Google搜索访问Intent filter 20 | 21 | - 22 | 23 | > 添加一个或多个标记,每个标记代表一种解析为Activity的URI格式。标记必须至少包含android:scheme属性。 24 | > 您可以添加更多属性以进一步优化Activity接受的URI类型。例如,您可能有多个Activity接受类似的URI,但这些Activity仅根据路径名称而有所不同。在这种情况下,使用android:path属性或其pathPattern或pathPrefix变体来区分系统应为不同的URI路径打开哪个Activity。 25 | 26 | - 27 | 28 | > 包括BROWSABLE category。为了从Web浏览器访问intent filter而需要它。没有它,单击浏览器中的链接无法解析为您的应用程序。 29 | > 还包括DEFAULT category。这允许您的应用响应隐式Intent。如果没有这个,只有在intent指定您的应用程序组件名称时才能启动Activity。 30 | 31 | 以下XML代码段显示了如何在manifest中为Deep Links指定intent filter。“example://gizmos”` 和“http://www.example.com/gizmos”这两个URI均会指向次Activity: 32 | 33 | ```xml 34 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | ``` 57 | 58 | 请注意,两个intent filter仅因元素而不同。虽然可以在同一个过滤器中包含多个元素,但是当您打算声明唯一的URL(例如scheme和host的特定组合)时,创建单独的过滤器非常重要,因为实际上在同一个Intent filter下的多个元素会合并在一起,以考虑其组合属性的所有变体。例如,请考虑以下代码: 59 | 60 | ```xml 61 | 62 | ... 63 | 64 | 65 | 66 | ``` 67 | 68 | 看起来该Intent filter仅仅会支持 `https://www.example.com` 和`app://open.my.app`,但是实际上他还另外支持这两个:`app://www.example.com` 和 `https://open.my.app`.(也就是说会被组合) 69 | 70 | ## 从传入的Intent中读取数据 71 | 72 | 一旦系统通过Intent filter启动您的Activity,您就可以使用Intent提供的数据来确定您需要呈现的内容。调用getData()和getAction()方法来取出与传入的Intent关联的数据和操作。您可以在Activity的生命周期中随时调用这些方法,但通常应该在早期回调期间执行此操作,例如onCreate()或onStart()。 73 | 74 | 这里展示了如何从Intent中取到数据: 75 | 76 | - kotlin 77 | 78 | ```kotlin 79 | override fun onCreate(savedInstanceState: Bundle?) { 80 | super.onCreate(savedInstanceState) 81 | setContentView(R.layout.main) 82 | 83 | val action: String? = intent?.action 84 | val data: Uri? = intent?.data 85 | } 86 | ``` 87 | 88 | - java 89 | 90 | ```java 91 | @Override 92 | public void onCreate(Bundle savedInstanceState) { 93 | super.onCreate(savedInstanceState); 94 | setContentView(R.layout.main); 95 | 96 | Intent intent = getIntent(); 97 | String action = intent.getAction(); 98 | Uri data = intent.getData(); 99 | } 100 | ``` 101 | 102 | 请遵循以下最佳做法以改善用户体验: 103 | 104 | - deep link应该将用户直接带到内容(content),而无需任何提示、插页式页面,或者登录。即使用户之前从未打开过该应用程序也要确保用户可以直接查看应用程序内容。可以在后续交互时或从Launcher打开应用程序时提示用户。这与网站第一次免费体验的原则相同。 105 | - 遵守“[Navigation with Back and Up](https://developer.android.com/design/patterns/navigation.html?hl=zh-cn)”中所述的设计指南,以便您的应用在用户通过Deep link进入应用后满足用户向后导航的期望。 106 | 107 | ## 测试你的Deep Links 108 | 109 | 您可以将 [Android Debug Bridge](https://developer.android.com/tools/help/adb.html?hl=zh-cn) 与activity manager(am)工具结合起来测试您为Deep Links指定的intent filter 的URI是否解析为正确的app Activity。您可以对设备或模拟器运行adb命令。 110 | 111 | 使用adb测试 Intent filter URI的一般语法是: 112 | 113 | ```shell 114 | $ adb shell am start 115 | -W -a android.intent.action.VIEW 116 | -d 117 | ``` 118 | 119 | 例如,以下命令尝试查看与指定URI关联的目标应用程序的Activity。 120 | 121 | ```shell 122 | $ adb shell am start 123 | -W -a android.intent.action.VIEW 124 | -d "example://gizmos" com.example.android 125 | ``` 126 | 127 | 您在上面设置的manifest和Intent处理程序定义了您的应用程序和网站之间的连接以及如何处理传入链接。但是,为了让系统将您的应用视为一组URI的默认处理程序,您还必须请求系统验证此连接。下一节将介绍如何实施此验证。 128 | 129 | 想了解更多关于Intent和app links的内容,请参考: 130 | 131 | - [Intents and Intent Filters](https://developer.android.com/guide/components/intents-filters.html?hl=zh-cn) 132 | - [Allow Other Apps to Start Your Activity](https://developer.android.com/training/basics/intents/filters.html?hl=zh-cn) 133 | - [Add Android App Links with Android Studio](https://developer.android.com/studio/write/app-link-indexing.html?hl=zh-cn) -------------------------------------------------------------------------------- /Activity/Interacting-With-Other-Apps/与其他APP交互——允许其他应用启动您的Activity.md: -------------------------------------------------------------------------------- 1 | # 与其他APP交互——允许其他应用启动您的 Activity 2 | 3 | [原文(英文)地址](https://developer.android.com/training/basics/intents/filters?#AddIntentFilter) 4 | 5 | 如果另一个应用想要通过您的应用执行某些操作,您的应用应准备好响应来自其他应用的操作请求。 例如,如果您构建一款可与用户的好友分享消息或照片的社交应用,您最关注的是支持 `ACTION_SEND` Intent 以便用户可以从另一应用发起“分享”操作并且启动您的应用执行该操作。 6 | 7 | 要允许其他应用启动您的 Activity,您需要在清单文件中为对应的 元素添加一个 元素。 8 | 9 | 当您的应用安装在设备上时,系统会识别您的 Intent filter 并添加信息至所有已安装应用支持的 Intent 内部目录。当应用通过隐含 Intent 调用 `startActivity()` 或 `startActivityForResult()` 时,系统会找到可以响应该 Intent 的 Activity。 10 | 11 | ## 添加 Intent filter 12 | 13 | 为了正确定义您 Activity 可处理的 Intent,您添加的每个 Intent filter在操作类型和 Activity 接受的数据方面应尽可能具体。 14 | 15 | 如果 Activity 具有满足以下 `Intent` 对象条件的 Intent filter,系统可能向 Activity 发送给定的 `Intent`: 16 | 17 | - 操作 18 | 19 | 一个描述要执行操作的字符串。通常是一个平台(platform-defined)定义的值,比如 `ACTION_SEND` 或 `ACTION_VIEW`。使用元素在您的 Intent finlter中指定此值。您在此元素中指定的值必须是操作的完整字符串名称,而不是 API 常量(请参阅以下示例)。 20 | 21 | - 数据 22 | 23 | 与 Intent 关联的数据的一条描述。用元素在您的 Intent filter中指定此内容。使用此元素中的一个或多个属性,您可以单独指定 MIME 类型(MIME type)、URI 前缀(URI prefix)、URI 架构(URI scheme)或这些的组合,以及其他表名所接受数据类型的项。 24 | 25 | > **注意**:如果您无需声明关于数据的具体信息 `Uri`(比如,您的 Activity 处理其他类型的“额外”数据而不是 URI 时),您应只指定 `android:mimeType` 属性声明您的 Activity 处理的数据类型,比如 `text/plain` 或 `image/jpeg`。 26 | 27 | - 类别 28 | 29 | 提供另外一种表征处理 Intent 的 Activity 的方法,通常与用户手势或 Activity 启动的位置有关。 系统支持多种不同的类别,但大多数都很少使用。 但是,所有隐含 Intent 默认使用 `CATEGORY_DEFAULT` 进行定义。用元素在您的 Intent filter中指定此内容。 30 | 31 | 在您的 Intent filter中,您可以通过声明嵌套在元素中的具有相应 XML 元素的各项,来声明您的 Activity 接受的条件。 32 | 33 | 例如,此处有一个 Activity 与在数据类型为文本或图像时处理 `ACTION_SEND` Intent 的 Intent filter: 34 | 35 | ```xml 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | 46 | 每个传入的 Intent 仅指定一项操作和一个数据类型,但可以在每个 中声明 ,,元素的多个实例。 47 | 48 | 如果任何两对操作和数据的行为相斥,您应创建单独的 Intent filter指定与哪种数据类型配对时哪些操作可接受。 49 | 50 | 比如,假定您的 Activity 同时处理 `ACTION_SEND` 和 `ACTION_SENDTO` Intent 的文本和图像。在这种情况下,您必须为两个操作定义两种不同的 Intent filter,因为 `ACTION_SENDTO` Intent 必须使用数据 `Uri` 指定使用 `send` 或 `sendto` URI 架构的收件人地址。例如: 51 | 52 | ```xml 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | ``` 70 | 71 | > **注意**:为了接收隐式 Intent,您必须在 Intent filter中包含 `CATEGORY_DEFAULT` 类别。方法 `startActivity()` 和`startActivityForResult()` 将按照已声明 `CATEGORY_DEFAULT` 类别的方式处理所有 Intent。如果您不在 Intent filter中声明它,则没有隐含 Intent 分解为您的 Activity。 72 | 73 | 如需了解有关发送和接收 `ACTION_SEND` 执行社交共享行为的 Intent 的详细信息,请参阅有关[Receiving simple data from other apps](https://developer.android.com/training/sharing/receive.html?hl=zh-cn)的文档。 74 | 75 | ## 处理您的 Activity 中的 Intent 76 | 77 | 为了决定在您的 Activity 执行哪种操作,您可读取用于启动 Activity 的 `Intent`。 78 | 79 | 当您的 Activity 启动时,调用 `getIntent()` 检索启动 Activity 的 `Intent`。您可以在 Activity 生命周期的任何时间执行此操作,但您通常应在早期回调时(比如,`onCreate()` 或 `onStart()`)执行。 80 | 81 | 例如: 82 | 83 | ```java 84 | @Override 85 | protected void onCreate(Bundle savedInstanceState) { 86 | super.onCreate(savedInstanceState); 87 | 88 | setContentView(R.layout.main); 89 | 90 | // Get the intent that started this activity 91 | Intent intent = getIntent(); 92 | Uri data = intent.getData(); 93 | 94 | // Figure out what to do based on the intent type 95 | if (intent.getType().indexOf("image/") != -1) { 96 | // Handle intents with image data ... 97 | } else if (intent.getType().equals("text/plain")) { 98 | // Handle intents with text ... 99 | } 100 | } 101 | ``` 102 | 103 | ### 返回结果 104 | 105 | 如果您想要向调用您的 Activity 的 Activity 返回结果,只需调用 `setResult()` 指定结果代码和结果 `Intent`。当您的操作完成且用户应返回原来的 Activity 时,调用 `finish()` 关闭(和销毁)您的 Activity。 例如: 106 | 107 | ```java 108 | // Create intent to deliver some kind of result data 109 | Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri")); 110 | setResult(Activity.RESULT_OK, result); 111 | finish(); 112 | ``` 113 | 114 | 您必须始终为结果指定结束代码。通常,它是 `RESULT_OK` 或 `RESULT_CANCELED`。您之后可以根据需要为 `Intent` 提供额外的数据。 115 | 116 | > **注意**:默认情况下,结果设置为 `RESULT_CANCELED`。因此,如果用户在完成操作动作或设置结果之前按了*返回*按钮,原始 Activity 会收到“已取消”的结果。 117 | 118 | 如果您只需返回指示若干结果选项之一的整数,您可以将结果代码设置为大于 0 的任何值。如果您使用结果代码传递整数,且无需包括 `Intent`,则可调用 `setResult()` 且仅传递结果代码。例如: 119 | 120 | ```java 121 | setResult(RESULT_COLOR_RED); 122 | finish(); 123 | ``` 124 | 125 | 在这种情况下,只有几个可能的结果,因此结果代码是一个本地定义的整数(大于 0)。 当您向自己应用中的 Activity 返回结果时,这将非常有效,因为接收结果的 Activity 可引用公共常数来确定结果代码的值。 126 | 127 | > **注意**:无需检查您的 Activity 是使用 `startActivity()` 还是 `startActivityForResult()` 启动的。如果启动您 Activity 的 Intent 可能需要结果,只需调用 `setResult()`。如果原始 Activity 已调用 `startActivityForResult()`,则系统将向其传递您提供给 `setResult()` 的结果;否则,会忽略结果。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Developer Guide翻译项目,主要翻译[Android Developer Guide官方文档](https://developer.android.com/guide),会不断更新。同时由于本人个人时间精力有限,欢迎大家一起参与这个项目,对于已经翻译完成的部分,如果有不妥之处,欢迎大家即使反馈。目前已翻译完成的文档有: 2 | 3 | - [Activities](./Activity) 4 | 5 | - [Introduction to activities(Activity介绍)](./Activity/Activity介绍.md) 6 | - [The activity lifecycle(Activity的生命周期)](./Activity/Activity的生命周期.md) 7 | - [Activity state changes(Activity的状态变化)](./Activity/Activity的状态变化.md) 8 | - [Test your app's activities(测试你的Activity)](./Activity/测试你的Activity.md) 9 | - [Understand Tasks And Back Stack(理解Task和回退栈)](./Activity/理解Task和回退栈.md) 10 | - [Process and Application Lifecycle(进程和应用程序的生命周期)](./Activity/进程和应用程序的生命周期.md) 11 | - [Parcelables and Bundles(Parcelable和Bundle)](./Activity/Parcelable和Bundle.md) 12 | - [Fragment](./Activity/Fragment) 13 | - [Overview(Fragment(概述)](./Activity/Fragment/Fragment概述.md) 14 | - [Create a Fragment(创建Fragment)](./Activity/Fragment/创建Fragment.md) 15 | - [Test your fragments(测试Fragment)](./Activity/Fragment/测试Fragment.md) 16 | - [Build a flexible UI(构建灵活的UI)](./Activity/Fragment/构建灵活的UI.md) 17 | - [Communicate with other fragments(与其他Fragment通信)](./Activity/Fragment/与其他Fragment通信.md) 18 | - [Interact with other apps(与其他应用交互)](./Activity/Interacting-With-Other-Apps) 19 | - [OverView(概述)](./Activity/Interacting-With-Other-Apps/与其他APP交互——概述.md) 20 | - [Sending the user to another app(将用户导航到另一个APP)](/Activity/Interacting-With-Other-Apps/与其他APP交互——将用户导航到另一个APP.md) 21 | - [Getting a result from an activity(接收另一个Activity返回的结果)](./Activity/Interacting-With-Other-Apps/与其他APP交互——接收另一个Activity返回的结果.md) 22 | - [Allowing other apps to start your activity(允许其他应用启动您的Activity)](./Activity/Interacting-With-Other-Apps/与其他APP交互——允许其他应用启动您的Activity.md) 23 | - [Handling app links(处理Android APP Link)](./Activity/Handling-Android-App-Links) 24 | - [OverView(概述)](./Activity/Handling-Android-App-Links/概述.md) 25 | - [Enabling links to app content(创建指向您内容的DeepLinks)](./Activity/Handling-Android-App-Links/创建指向您内容的Deep-Links.md) 26 | - [Verify app Links(验证App Links)](./Activity/Handling-Android-App-Links/验证App-Links.md) 27 | - [Create App links for instant app(为Instant APPs创建App Links)](./Activity/Handling-Android-App-Links/为Instant-APPs创建App-Links.md) 28 | - [Loaders(加载器)](./Activity/加载器.md) 29 | - [Recents Screen(最近任务界面)](./Activity/最近任务界面.md) 30 | - [Multi-Window Support(多窗口支持-分屏)](./Activity/多窗口支持(分屏).md) 31 | - [App Short Cuts(App快捷启动方式)](./Activity/AppShortCuts) 32 | 33 | - [OverView(概述)](./Activity/AppShortCuts/概述.md) 34 | - [Create shortcuts(创建快捷方式)](./Activity/AppShortCuts/创建快捷方式.md) 35 | - [Manage shortcuts(管理快捷方式)](./Activity/AppShortCuts/管理快捷方式.md) 36 | - [Best practices for shortcuts(快捷方式的最佳做法)](./Activity/AppShortCuts/快捷方式的最佳做法.md) 37 | - [App Widgets(应用小部件)](./Activity/AppWidgets) 38 | 39 | - [OverView(概述)](./Activity/AppWidgets/App小部件概述.md) 40 | - [Build an App Widget(构建应用程序小部件)](./Activity/AppWidgets/构建应用程序小部件.md) 41 | - [Build an App Widget Host(构建应用程序小部件主机)](./Activity/AppWidgets/构建应用程序小部件主机(Host).md) 42 | - [Guide to app architecture(应用程序架构指南)](./应用程序架构指南.md) 43 | 44 | - [Architecture Components(架构组件)](./ArchitectureComponents) 45 | 46 | - [OverView(概述)](./ArchitectureComponents/Android架构组件-概述.md) 47 | - [Adding Components to your Project(给你的项目中添加架构组件)](./ArchitectureComponents/Android架构组件-给你的项目中添加组件.md) 48 | - [Layouts and binding expressions(布局和绑定表达式)](./ArchitectureComponents/Android架构组件-布局和绑定表达式.md) 49 | - [Work with observable data objects(使用可观察的数据对象)](./ArchitectureComponents/Android架构组件-使用可观察的数据对象.md) 50 | - [Generated binding classes(生成的绑定类)](./ArchitectureComponents/Android架构组件-生成的绑定类.md) 51 | 52 | - [Intent and intent filters(Intent和Intent过滤器)](./Intent-And-Intent-Filter) 53 | 54 | - [OverView(概述)](./Intent-And-Intent-Filter/Intent和IntentFilters——概述.md) 55 | - [Common intents(通用Intent)](./Intent-And-Intent-Filter/Intent和IntentFilters——通用Intent.md) 56 | 57 | - [User Interface(用户界面)](./UserInterface) 58 | 59 | - [Create a List with RecyclerView(使用RecyclerView创建列表)](./UserInterface/使用RecyclerView创建列表.md) 60 | 61 | - [BackGround Tasks(后台任务)](./BackGroundTasks) 62 | 63 | - [Service(服务)](./BackGroundTasks/Service) 64 | - [OverView(概述)](./BackGroundTasks/Service/Service——概述.md) 65 | 66 | - [Create a background service(创建后台Service)](./BackGroundTasks/Service/Service——创建后台服务.md) 67 | - [Send work requests to the background service(向后台Service发送工作请求)](./BackGroundTasks/Service/Service——向后台Service发送工作请求.md) 68 | - [Bound Service(绑定Service)](./BackGroundTasks/Service/Service——绑定(bound)Service.md) 69 | - [Android Interface Definition Language (AIDL)](./BackGroundTasks/Service/Service——AIDL.md) 70 | - [Broadcast](./BackGroundTasks/BroadCast.md) 71 | - [Sending Operations To Multiple Threads(将操作发送到多个线程)](./BackGroundTasks/SendingOperationsToMultipleThreads) 72 | 73 | - [OverView(概述)](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(1)概述.md) 74 | - [Specify the code to run on a thread(指定线程中运行的代码)](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(2)指定线程中运行的代码.md) 75 | - [Create a manager for multiple threads(为多线程创建管理者)](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(3)为多线程创建管理者(manager).md) 76 | - [Run code on a thread pool thread(在线程池中运行代码)](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(4)在线程池中的线程中运行代码.md) 77 | - [Communicate with the UI thread(与UI线程进行通信)](./BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(5)与UI线程通信.md) 78 | 79 | - [Best Practices](./BestPractices) 80 | 81 | - [Performance](./BestPractices/Performance) 82 | - [进程和线程——概述](./BestPractices/Performance/进程和线程——概述.md) 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /BestPractices/Performance/进程和线程——概述.md: -------------------------------------------------------------------------------- 1 | # 进程和线程——概述 2 | 3 | [原文(英文)地址](https://developer.android.com/guide/components/processes-and-threads) 4 | 5 | 当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为主线程)中运行。 如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。 但是,您可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。 6 | 7 | 本文档介绍进程和线程在 Android 应用中的工作方式。 8 | 9 | ## 进程 10 | 11 | 默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。 但是,如果您发现需要控制某个组件所属的进程,则可在`manifest`文件中执行此操作。 12 | 13 | 各类组件元素的manifest文件条目——[`activity`](https://developer.android.com/guide/topics/manifest/activity-element.html)[``](https://developer.android.com/guide/topics/manifest/activity-element.html?hl=zh-cn)、[`service`](https://developer.android.com/guide/topics/manifest/service-element.html?hl=zh-cn)、[`receiver`](https://developer.android.com/guide/topics/manifest/receiver-element.html?hl=zh-cn) 和 [`provider`](https://developer.android.com/guide/topics/manifest/provider-element.html?hl=zh-cn)均支持 `android:process`属性,此属性可以指定该组件应在哪个进程运行。您可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外,您还可以设置 `android:process`,使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。 14 | 15 | 此外,[`application`](https://developer.android.com/guide/topics/manifest/application-element.html?hl=zh-cn) 元素也支持 `android:process` 属性,以设置适用于所有组件的默认值。 16 | 17 | 如果内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。 当这些组件需要再次运行时,系统将为它们重启进程。 18 | 19 | 决定终止哪个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,系统更有可能关闭托管屏幕上不再可见的 Activity 的进程。 因此,是否终止某个进程取决于该进程中所运行组件的状态。 有关进程生命周期及其与应用程序状态的关系的更多内容,请参阅[进程和应用程序的生命周期](…/Activity/进程和应用程序的生命周期.md) 20 | 21 | ## 线程 22 | 23 | 应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 此线程非常重要,因为它负责将事件分派给相应的用户界面中的小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 `android.widget` 和 `android.view`软件包的组件)进行交互的线程。因此,主线程有时也称为 UI 线程。 24 | 25 | 系统*不会*为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操作的 `onKeyDown()` 或生命周期回调方法)始终在进程的 UI 线程中运行。 26 | 27 | 例如,当用户触摸屏幕上的按钮时,应用的 UI 线程会将触摸事件分派给小部件,而小部件反过来又设置其按下状态,并将失效请求发布到事件队列中。 UI 线程从队列中取消该请求并通知小部件应该重绘自身。 28 | 29 | 在应用执行繁重的任务以响应用户交互时,除非正确实现应用,否则这种单线程模式可能会导致性能低下。 具体地讲,如果 UI 线程需要处理所有任务,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个 UI。 一旦线程被阻塞,将无法分派任何事件,包括绘图事件。 从用户的角度来看,应用显示为挂起。 更糟糕的是,如果 UI 线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),用户就会看到一个让人厌烦的“[应用无响应](http://developer.android.com/guide/practices/responsiveness.html?hl=zh-cn)”(ANR) 对话框。如果引起用户不满,他们可能就会决定退出并卸载此应用。 30 | 31 | 此外,Android UI 工具包*并非*线程安全工具包。因此,您不得通过工作线程操纵 UI,而只能通过 UI 线程操纵用户界面。 因此,Android 的单线程模式必须遵守两条规则: 32 | 33 | 1. 不要阻塞 UI 线程 34 | 2. 不要在 UI 线程之外访问 Android UI 工具包 35 | 36 | ## 工作线程 37 | 38 | 根据上述单线程模式,要保证应用 UI 的响应能力,关键是不能阻塞 UI 线程。 如果您要执行的操作不是即时的,则应确保在单独的线程(“后台”或“工作线程”线程)中执行它们。 39 | 40 | 但是,请注意,您无法从UI线程或“主”线程以外的任何线程更新UI。 41 | 42 | 为了解决这个问题,Android提供了几种从其他线程访问UI线程的方法。以下列出了可以提供帮助的方法: 43 | 44 | - `Activity.runOnUiThread(Runnable)` 45 | - `View.post(Runnable)` 46 | - `View.postDelayed(Runnable, long)` 47 | 48 | 示例代码: 49 | 50 | - kotlin 51 | 52 | - ```kotlin 53 | fun onClick(v: View) { 54 | Thread(Runnable { 55 | // a potentially time consuming task 56 | val bitmap = processBitMap("image.png") 57 | imageView.post { 58 | imageView.setImageBitmap(bitmap) 59 | } 60 | }).start() 61 | } 62 | ``` 63 | 64 | - java 65 | 66 | - ```java 67 | public void onClick(View v) { 68 | new Thread(new Runnable() { 69 | public void run() { 70 | // a potentially time consuming task 71 | final Bitmap bitmap = 72 | processBitMap("image.png"); 73 | imageView.post(new Runnable() { 74 | public void run() { 75 | imageView.setImageBitmap(bitmap); 76 | } 77 | }); 78 | } 79 | }).start(); 80 | } 81 | ``` 82 | 83 | 例如,以下代码演示了一个点击侦听器从单独的线程下载图像并将其显示在 `ImageView` 中: 84 | 85 | ```java 86 | public void onClick(View v) { 87 | new Thread(new Runnable() { 88 | public void run() { 89 | Bitmap b = loadImageFromNetwork("http://example.com/image.png"); 90 | mImageView.setImageBitmap(b); 91 | } 92 | }).start(); 93 | } 94 | ``` 95 | 96 | 此实现是线程安全的:后台操作是在单独的线程完成的,而ImageView始终是在UI线程操作的。 97 | 98 | 但是,随着操作复杂性的增加,这种代码变得复杂且难以维护。要处理与工作线程的更复杂的交互,您可以考虑在工作线程中使用Handler来处理从UI线程传递的消息。也许最好的解决方案是继承AsyncTask类,它简化了需要与UI交互的工作线程任务的执行。 99 | 100 | ## 使用 AsyncTask 101 | 102 | AsyncTask允许您在用户界面上执行异步工作。它在工作线程中执行阻塞操作,然后在UI线程上发布结果,而不需要您自己处理线程和/或处理程序。 103 | 104 | 要使用它,必须创建 `AsyncTask` 的子类并实现 `doInBackground()` 回调方法,该方法将在后台线程池中运行。 要更新 UI,应该实现 `onPostExecute()` 以传递 `doInBackground()` 返回的结果并在 UI 线程中运行,以便您安全地更新 UI。 之后,您可以通过从 UI 线程调用 `execute()` 来运行任务。 105 | 106 | 您应该阅读[AsyncTask](https://developer.android.com/reference/android/os/AsyncTask.html)文档,以全面了解如何使用此类。 107 | 108 | ## 线程安全的方法 109 | 110 | 在某些情况下,您实现的方法可能会从多个线程调用,因此编写这些方法时必须确保其满足线程安全的要求。 111 | 112 | 这一点主要适用于可以远程调用的方法,如[绑定Service](https://developer.android.com/guide/components/bound-services.html?hl=zh-cn)中的方法。如果对 `IBinder` 中所实现方法的调用源自运行 `IBinder`的同一进程,则该方法在调用方的线程中执行。但是,如果调用源自其他进程,则该方法将在从线程池选择的某个线程中执行(而不是在进程的 UI 线程中执行),线程池由系统在与 `IBinder` 相同的进程中维护。 例如,即使Service的 `onBind()` 方法将从服务进程的 UI 线程调用,在 `onBind()` 返回的对象中实现的方法(例如,实现 RPC 方法的子类)仍会从线程池中的线程调用。 由于一个Service可以有多个客户端,因此可能会有多个池线程在同一时间使用同一 `IBinder` 方法。因此,`IBinder` 方法必须实现为线程安全方法。 113 | 114 | 同样,内容提供程序也可接收来自其他进程的数据请求。尽管 `ContentResolver` 和 `ContentProvider` 类隐藏了如何管理进程间通信的细节,但响应这些请求的 `ContentProvider` 方法(`query()`、`insert()`、`delete()`、`update()` 和 `getType()` 方法)将从内容提供程序所在进程的线程池中调用,而不是从进程的 UI 线程调用。 由于这些方法可能会同时从任意数量的线程调用,因此它们也必须实现为线程安全方法。 115 | 116 | ## 进程间通信 117 | 118 | Android 利用远程过程调用 (RPC) 提供了一种进程间通信 (IPC) 机制,通过这种机制,由 Activity 或其他应用组件调用的方法将(在其他进程中)远程执行,而所有结果将返回给调用方。 这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。 然后,返回值将沿相反方向传输回来。 Android 提供了执行这些 IPC 事务所需的全部代码,因此您只需集中精力定义和实现 RPC 编程接口即可。 119 | 120 | 要执行 IPC,必须使用 `bindService()` 将应用绑定到服务上。如需了解详细信息,请参阅[Service](…/BackGroundTasks/Service)开发者指南。 -------------------------------------------------------------------------------- /BackGroundTasks/SendingOperationsToMultipleThreads/将操作发送到多个线程——(4)在线程池中的线程中运行代码.md: -------------------------------------------------------------------------------- 1 | # 将操作发送到多个线程——在线程池中的线程中运行代码 2 | 3 | [原文(英文)地址](https://developer.android.com/training/multiple-threads/run-code#kotlin) 4 | 5 | 上一篇文章 [为多线程创建管理者(manager)](./将操作发送到多个线程——为多线程创建管理者(manager).md),介绍了如何定义管理线程池的类。本文主要介绍如何在线程池上运行任务。为了在线程池上运行任务,您应该将任务添加到线程池的工作队列中。当有线程可用时,`ThreadPoolExecutor`会从队列中获取一个任务并在线程上运行它。 6 | 7 | 本文还向您展示了如何停止正在运行的任务。如果任务启动,但随后发现其工作不是必需的,您可以取消运行任务的线程,而不是浪费处理器时间。例如,如果从网络下载图像并使用缓存,则可能需要在检测到缓存中已存在图像时停止任务。 8 | 9 | ## 在线程池中的线程上运行任务 10 | 11 | 要在特定线程池中的线程上启动任务,请将`Runnable`传递给[ThreadPoolExecutor.execute()](https://developer.android.com/reference/java/util/concurrent/ThreadPoolExecutor.html#execute(java.lang.Runnable))。此调用将任务添加到线程池的工作队列中,当有空闲线程可用时,管理器将获取等待时间最长的任务并在线程上运行它: 12 | 13 | - kotlin 14 | 15 | - ```kotlin 16 | object PhotoManager { 17 | 18 | fun handleState(photoTask: PhotoTask, state: Int) { 19 | when (state) { 20 | DOWNLOAD_COMPLETE -> 21 | // Decodes the image 22 | decodeThreadPool.execute(photoTask.getPhotoDecodeRunnable()) 23 | ... 24 | } 25 | ... 26 | } 27 | ... 28 | } 29 | ``` 30 | 31 | - java 32 | 33 | - ```java 34 | public class PhotoManager { 35 | public void handleState(PhotoTask photoTask, int state) { 36 | switch (state) { 37 | // The task finished downloading the image 38 | case DOWNLOAD_COMPLETE: 39 | // Decodes the image 40 | decodeThreadPool.execute( 41 | photoTask.getPhotoDecodeRunnable()); 42 | ... 43 | } 44 | ... 45 | } 46 | ... 47 | } 48 | ``` 49 | 50 | 当ThreadPollExecutor在一个线程中启动Runnable类之后,它会自动调用Runnable类的run()方法。 51 | 52 | ## 中断正在执行的代码 53 | 54 | 要停止一个任务,你需要中断执行该任务的线程。要实现此操作,您需要在创建任务时将句柄存储到任务的线程。例如: 55 | 56 | - kotlin 57 | 58 | ```kotlin 59 | class PhotoDecodeRunnable : Runnable { 60 | 61 | // Defines the code to run for this task 62 | override fun run() { 63 | /* 64 | * Stores the current Thread in the 65 | * object that contains PhotoDecodeRunnable 66 | */ 67 | photoTask.setImageDecodeThread(Thread.currentThread()) 68 | ... 69 | } 70 | ... 71 | } 72 | ``` 73 | 74 | - java 75 | 76 | ```java 77 | class PhotoDecodeRunnable implements Runnable { 78 | // Defines the code to run for this task 79 | public void run() { 80 | /* 81 | * Stores the current Thread in the 82 | * object that contains PhotoDecodeRunnable 83 | */ 84 | photoTask.setImageDecodeThread(Thread.currentThread()); 85 | ... 86 | } 87 | ... 88 | } 89 | ``` 90 | 91 | 要中断线程,请调用Thread.interrupt()。请注意,Thread对象由系统控制,系统可以在应用程序的进程外修改它们。因此,您需要在中断之前获取该线程的锁以获取该线程的访问权限,方法是将访问置于synchronized块中。例如: 92 | 93 | - kotlin 94 | 95 | ```kotlin 96 | object PhotoManager { 97 | fun cancelAll() { 98 | /* 99 | * Creates and populates an array of Runnables with the Runnables in the queue 100 | */ 101 | val runnableArray: Array = decodeWorkQueue.toTypedArray() 102 | /* 103 | * Iterates over the array of Runnables and interrupts each one's Thread. 104 | */ 105 | synchronized(this) { 106 | // Iterates over the array of tasks 107 | runnableArray.map { (it as? PhotoDecodeRunnable)?.mThread } 108 | .forEach { thread -> 109 | thread?.interrupt() 110 | } 111 | } 112 | } 113 | ... 114 | } 115 | ``` 116 | 117 | - java 118 | 119 | ```java 120 | public class PhotoManager { 121 | public static void cancelAll() { 122 | /* 123 | * Creates an array of Runnables that's the same size as the 124 | * thread pool work queue 125 | */ 126 | Runnable[] runnableArray = new Runnable[decodeWorkQueue.size()]; 127 | // Populates the array with the Runnables in the queue 128 | mDecodeWorkQueue.toArray(runnableArray); 129 | // Stores the array length in order to iterate over the array 130 | int len = runnableArray.length; 131 | /* 132 | * Iterates over the array of Runnables and interrupts each one's Thread. 133 | */ 134 | synchronized (sInstance) { 135 | // Iterates over the array of tasks 136 | for (int runnableIndex = 0; runnableIndex < len; runnableIndex++) { 137 | // Gets the current thread 138 | Runnable runnable = runnableArray[runnableIndex]; 139 | Thread thread = null; 140 | if (runnable instanceof PhotoDecodeRunnable) { 141 | thread = ((PhotoDecodeRunnable)runnable).mThread; 142 | } 143 | // if the Thread exists, post an interrupt to it 144 | if (null != thread) { 145 | thread.interrupt(); 146 | } 147 | } 148 | } 149 | } 150 | ... 151 | } 152 | ``` 153 | 154 | 在大多数情况下,Thread.interrupt()会立即停止线程。但是,它只会停止正在等待的线程,并且不会中断CPU或网络密集型任务。为避免减慢或锁定系统,您应该在尝试操作之前测试任何挂起的中断请求: 155 | 156 | - kotlin 157 | 158 | ```kotlin 159 | /* 160 | * Before continuing, checks to see that the Thread hasn't 161 | * been interrupted 162 | */ 163 | if (Thread.interrupted()) return 164 | ... 165 | // Decodes a byte array into a Bitmap (CPU-intensive) 166 | BitmapFactory.decodeByteArray(imageBuffer, 0, imageBuffer.size, bitmapOptions) 167 | ... 168 | ``` 169 | 170 | - java 171 | 172 | ```java 173 | /* 174 | * Before continuing, checks to see that the Thread hasn't 175 | * been interrupted 176 | */ 177 | if (Thread.interrupted()) { 178 | return; 179 | } 180 | ... 181 | // Decodes a byte array into a Bitmap (CPU-intensive) 182 | BitmapFactory.decodeByteArray( 183 | imageBuffer, 0, imageBuffer.length, bitmapOptions); 184 | ... 185 | ``` 186 | 187 | ## 更多内容 188 | 189 | 更多内容请参阅[进程和线程——概述](…/BestPractices/Performance/进程和线程——概述.md) 190 | 191 | -------------------------------------------------------------------------------- /Activity/Activity介绍.md: -------------------------------------------------------------------------------- 1 | # Activity简介(介绍) 2 | 3 | [原文(英文)地址](https://developer.android.com/guide/components/activities/intro-activities) 4 | 5 | Activity类是Android应用程序的重要组成部分,Activity的启动和组合方式是应用程序的基本组成部分。与使用main()方法启动应用程序的编程范例不同,Android系统通过调用与其生命周期的特定阶段相对应的特定回调方法来启动Activity实例中的代码。 6 | 7 | 本文档介绍了Activity的概念,然后提供了有关如何使用它们的一些轻量级指导。有关构建应用程序的最佳实践的其他信息,请参阅 [Guide to App Architecture](https://developer.android.com/topic/libraries/architecture/guide.html)。 8 | 9 | ## Activity的概念 10 | 11 | 移动应用与桌面应用不同,用户与移动应用的交互并不总是在同一个地方开始。相反,用户打开同一个应用的目的通常是非确定性的。例如,如果您从主屏幕打开电子邮件应用程序,则可能会看到电子邮件列表。但是,如果您在使用第三方社交媒体应用程序时从该应用内的入口启动您的电子邮件应用程序,您可能会直接进入电子邮件应用程序的屏幕撰写电子邮件。 12 | 13 | Activity类旨在促进(方便化)这种应用场景。当一个应用程序调用另一个应用程序时,调用应用程序将调用另一个应用程序中的Activity,而不是一个整体的应用程序。通过这种方式,Activity可以作为应用与用户交互的入口点。您通常需要将Activity类作为父类去实现您自己的Activity(`MYActivity extends Activity`)。 14 | 15 | Activity提供应用程序绘制其UI的Window。此Window通常填充屏幕,但可能小于屏幕并浮动在其他窗口的顶部。通常,一个Activity在应用程序中实现一个Window。例如,应用程序的某个Activity可能会implement(实现)一个*Preferences* (首选项)screen,而另一个会implement(实现) *Select Photo* (选择照片)screen。 16 | 17 | 大多数应用程序包含多个页面(screens),这意味着它们通常包含多个Activity,应用程序中的一个Activity被指定为MainActivity,这是用户启动应用程序时显示的第一页面。然后,每个Activity可以启动另一个Activity以执行不同的操作。例如,电子邮件应用程序中的MainActivity可能会提供显示电子邮件收件箱的界面,从那个页面开始,MainActivity可能会启动其他Activity,去展示编写电子邮件和打开个人电子邮件的界面。 18 | 19 | 虽然在一个应用中一般会有多个Activity紧密地在一起工作,但每个Activity仅与其他Activity轻耦合(依赖性不大),一个应用程序中不同的Activity之间通常存在很小的依赖关系。实际上,Activity通常会启动属于其他应用程序的Activity。例如,浏览器应用可能会启动社交媒体应用的共享。 20 | 21 | 要在应用程序中使用Activity,您必须在应用程序的manifest文件中注册有关它们的信息,并且必须合理地管理Activity的生命周期。本文档的其余部分介绍了这些主题。 22 | 23 | ## 配置manifest文件 24 | 25 | 为了使您的应用能够使用Activity,您必须在manifest文件中声明Activity及其某些属性。 26 | 27 | ### 声明Activity 28 | 29 | 打开你应用的manifest文件,在\中间添加元素,比如: 30 | 31 | ```xml 32 | 33 | 34 | 35 | ... 36 | 37 | ... 38 | 39 | ``` 40 | 41 | 对于该元素唯一必须的属性是android:name,该属性唯一确定了一个Activity的类名。同样,你也可以在manifest中为该activity添加其他属性,比如label、icon或者UI theme等。想要了解更多activity的其他属性,可以参考[ the element reference documentation](https://developer.android.com/guide/topics/manifest/activity-element.html) . 42 | 43 | > 注意:当你发布了你的APP之后,你就不应该再更改你Activity的名字(name),如果你在发布APP之后更改了某个Activity的name属性,你app的某些功能可能被破坏,比如你应用的快捷启动方式(app shortcuts),有关发布APP后应该禁止更改的属性,可以参见 [Things That Cannot Change](http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html) 44 | 45 | ### 声明intent filter 46 | 47 | Intent filter是Android平台一个非常强大的功能。其可以基于显式请求和隐式请求来启动Activity。例如,显式请求可能会告诉系统“在Gmail应用中启动发送电子邮件的Activity”。相反,隐式请求会告诉系统“在任何可以执行此任务的Activity中启动发送电子邮件的界面”。当系统UI询问用户在执行任务时使用哪个应用程序时,这是一个工作中的intent filter。 48 | 49 | 您可以通过在元素中声明属性来利用此功能。该元素的定义包括元素,以及可选的元素和/或元素。这些元素组合在一起以指定您的Activity可以响应的Intent类型。例如,以下代码段显示了如何配置发送文本数据的Activity,并从其他Activity接收请求以执行此操作: 50 | 51 | ```xml 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ``` 60 | 61 | 在此例中,元素指定此Activity可以发送数据。将元素声明为DEFAULT可使Activity接收启动的请求。 元素指定此Activity可以发送的数据类型。以下代码段展示了如何调用上述Activity: 62 | 63 | - kotlin 64 | 65 | ```kotlin 66 | val sendIntent = Intent().apply { 67 | action = Intent.ACTION_SEND 68 | type = "text/plain" 69 | putExtra(Intent.EXTRA_TEXT, textMessage) 70 | } 71 | startActivity(sendIntent) 72 | ``` 73 | 74 | - Java 75 | 76 | ```java 77 | // Create the text message with a string 78 | Intent sendIntent = new Intent(); 79 | sendIntent.setAction(Intent.ACTION_SEND); 80 | sendIntent.setType("text/plain"); 81 | sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage); 82 | // Start the activity 83 | startActivity(sendIntent); 84 | ``` 85 | 86 | 如果您想使某一个Activity只能在本应用内启动(不需要其他应用启动该Activity),则不需要任何其他intent filter。您不希望其他应用程序可见的Activity应该没有intent filter,您可以使用显式意图自行启动它们。有关Activity如何响应Intent的更多内容,请参阅[Intents and Intent Filters](https://developer.android.com/guide/components/intents-filters.html)。 87 | 88 | ### 声明权限 89 | 90 | 您可以使manifest中的元素(element)来控制哪些应用可以启动特定Activity。除非两个Activity在其manifest文件中具有相同的权限(permissions),否则父Activity无法启动子Activity。如果为特定Activity声明元素(element),则调用其的Activity必须具有匹配的元素(element)。 91 | 92 | 例如,加入您的应用想要通过名为SocialApp的应用中的Activity在社交媒体上分享帖子,SocialApp中的Activity本身必须定义调用它的应用必须具有的权限: 93 | 94 | ```xml 95 | 96 | 100 | ``` 101 | 102 | 同时,为了被允许调用SocialAPP,你自己的APP必须匹配SocialAPP的manifest文件中声明的权限: 103 | 104 | ```xml 105 | 106 | 107 | 108 | ``` 109 | 110 | 有关权限和安全性的更多信息请参考:[Security and Permissions](https://developer.android.com/guide/topics/security/security.html)。 111 | 112 | ## 管理Activity的生命周期 113 | 114 | 一个Activity从创建到销毁经历和很多不同的周期,你可以使用一系列的回调方法去控制不同生命周期应该执行的逻辑代码,接下来的一节介绍这些回调方法。 115 | 116 | ### onCreate() 117 | 118 | 您必须实现此回调,该回调在系统创建Activity时触发。您的实现应初始化Activity中的基本组件:例如,您的应用程序应创建视图并将数据绑定到列表。最重要的是,您必须调用setContentView()来定义Activity用户界面的布局(xml布局文件)。 119 | 120 | 当onCreate()完成时,进入Created状态,下一个回调一定是onStart()。 121 | 122 | ### onStart() 123 | 124 | 当onCreate()执行完成后,会调用onStart(),此回调主要包含Activity最终准备到达前台并变为交互式所需执行的逻辑。 125 | 126 | 执行完onStart()之后Activity进入Started状态,Activity对用户可见,接着会调用onResume()方法。 127 | 128 | ### onResume() 129 | 130 | 系统在Activity开始与用户交互之前回调此方法。此时,Activity位于Activity Stack(堆栈)的顶部,并捕获(captures)所有用户输入。应用程序的大多数核心功能都是在onResume()方法中实现的。 131 | 132 | onResume()之后一般会调用onPause()方法。 133 | 134 | ### onPause() 135 | 136 | 当Activity失去焦点并进入暂停状态时,系统回调onPause()方法。例如,当用户点击“Back(后退键)”或“Recents(最近任务)”按钮时,会出现此状态。当系统回调您的Activity的onPause()方法时,它在意味着您的Activity仍然部分可见,但通常表示用户正在离开Activity,并且Activity很快将进入“已停止(Stopped)”或“已恢复(Resumed)”状态。 137 | 138 | 如果用户期望UI继续更新,则处于暂停状态的Activity可以继续更新UI。这种场景的Activity包括导航地图的Activity或媒体播放器播放的Activity。即使这些Activity失去焦点,用户也希望他们的UI继续更新。 139 | 140 | 您不应使用onPause()来保存应用程序或用户数据、进行网络调用或执行数据库事务。有关保存数据内容,请参阅 [Saving and restoring activity state](https://developer.android.com/guide/components/activities/activity-lifecycle.html#saras). 141 | 142 | 一旦onPause()完成执行,下一个回调就是onStop()或onResume(),具体取决于进入Paused状态后发生的情况。 143 | 144 | ### onStop() 145 | 146 | 当Activity不再对用户可见时,系统会回调onStop()。这中情况一般是因为旧Activity正在被销毁(being destroyed),新Activity正在启动(Starting),或者现有Activity正在进入恢复状态(Resumed state)并且正在覆盖已停止的Activity。在这些场景下,已经停止的Activity不再可见。 147 | 148 | 之后如果Activity返回与用户交互(比如按了最近任务键之后又点击了之前的Activity),系统调用的下一个回调是onRestart(),如果此Activity完全终止,则会回调onDestroy()。 149 | 150 | ### onRestart() 151 | 152 | 当一个处于Stopped状态的Activity被重新启动(start)时系统会回调onRestart()方法。onRestart()方法会恢复之前Activity停止(stop)时保存的状态。 153 | 154 | onRestart()方法执行完成之后一般会调用onStart()方法。 155 | 156 | ### onDestroy() 157 | 158 | 系统在销毁Activity之前调用此方法。 159 | 160 | 此回调是Activity的最后一个回调。通常实现onDestroy()以确保在Activity或包含该Activity的进程被销毁时释放所有Activity占用的资源。 161 | 162 | 有关Activity生命周期及其回调的更详细的内容,请阅读 [Activity的生命周期](./Activity/Activity的生命周期.md)。 163 | 164 | -------------------------------------------------------------------------------- /ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——生成的绑定类.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——生成的绑定类 2 | 3 | 数据绑定库生成用于访问布局的变量(variables)和视图(views)的绑定类。此文档介绍如何创建和自定义生成的绑定类。 4 | 5 | 生成的绑定类将布局变量与布局中的视图链接起来,绑定类的名称和包可以自定义,所有生成的绑定类都继承自ViewDataBinding类。 6 | 7 | 为每个布局文件生成绑定类。默认情况下,类的名称基于布局文件的名称,将其转换为aaccPascal大小写并向其添加Binding后缀。布局文件名是activity_main.xml,相应的生成类是ActivityMainBinding,此类包含布局属性(例如,user变量)到布局视图的所有绑定,并知道如何为绑定表达式指定值。 8 | 9 | ## 创建一个绑定对象 10 | 11 | 在对布局进行inflateing之后,应该很快创建绑定对象,以确保布局中的表达式绑定到视图之前不会修改视图的层次结构。将对象绑定到布局的最常用方法是使用绑定类上的静态方法。您可以通过使用绑定类的inflate()方法来扩展视图层次结构并将对象绑定到该层次结构,如以下示例所示: 12 | 13 | - kotlin 14 | 15 | - ```kotlin 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | val binding: MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater) 20 | } 21 | ``` 22 | 23 | - java 24 | 25 | ```java 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater()); 30 | } 31 | ``` 32 | 33 | 除了[LayoutInflater](https://developer.android.com/reference/android/view/LayoutInflater.html)对象之外,还有一个替换版本的inflate()方法,它接受一个[ViewGroup](https://developer.android.com/reference/android/view/ViewGroup.html)对象,如下例所示: 34 | 35 | - kotlin 36 | 37 | ```kotlin 38 | val binding: MyLayoutBinding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false) 39 | ``` 40 | 41 | - java 42 | 43 | - ```java 44 | MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false); 45 | ``` 46 | 47 | 如果使用不同的机制对布局进行了inflate,则可以单独绑定,如下所示: 48 | 49 | - kotlin 50 | 51 | - ```kotlin 52 | val binding: MyLayoutBinding = MyLayoutBinding.bind(viewRoot) 53 | ``` 54 | 55 | - java 56 | 57 | - ```java 58 | MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot); 59 | ``` 60 | 61 | 有时不能事先知道绑定类型。在这种情况下,可以使用[`DataBindingUtil`](https://developer.android.com/reference/android/databinding/DataBindingUtil.html)类创建绑定,如以下代码段所示: 62 | 63 | - kotlin 64 | 65 | ```kotlin 66 | val viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent) 67 | val binding: ViewDataBinding? = DataBindingUtil.bind(viewRoot) 68 | ``` 69 | 70 | - java 71 | 72 | ```java 73 | View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent); 74 | ViewDataBinding binding = DataBindingUtil.bind(viewRoot); 75 | ``` 76 | 77 | 如果在Fragment,ListView或RecyclerView适配器中使用数据绑定item项,则可能更喜欢使用绑定类或DataBindingUtil类的inflate()方法,如以下代码示例所示: 78 | 79 | - kotlin 80 | 81 | ```kotlin 82 | val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false) 83 | // or 84 | val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false) 85 | ``` 86 | 87 | - java 88 | 89 | ```java 90 | ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); 91 | // or 92 | ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false); 93 | ``` 94 | 95 | ## 带ID的View 96 | 97 | 数据绑定库为每一个layout文件中具有ID的view创建不可变的字段,比如,以下代码中数据绑定库创建名为firstName和lastName,类型为TextView的字段: 98 | 99 | ```xml 100 | 101 | 102 | 103 | 104 | 108 | 112 | 116 | 117 | 118 | ``` 119 | 120 | 该库在一次传递中从视图层次结构中提取具有ID的View,此机制比为布局中的每个视图调用findViewById()方法更快。 121 | 122 | 如果没有数据绑定,则ID是非必要的,但仍有一些情况需要通过代码访问视图。 123 | 124 | ## 变量 125 | 126 | 数据绑定库为每一个声明在layout文件中的变量生成访问方法,比如,对于下面的layout文件,数据绑定类会为user,image和note生成setter和getter方法: 127 | 128 | ```xml 129 | 130 | 131 | 132 | 133 | 134 | 135 | ``` 136 | 137 | ## ViewStubs 138 | 139 | 和普通View不同的是,ViewStub对象开始时是不可见的,当其被手动置位visible或者被inflate,它才会inflate另一个layout以替换原来自己的位置。 140 | 141 | 由于ViewStub基本上是从视图层次结构中消失的,因此绑定对象中的视图也必须消失以允许通过垃圾回收声明。因为View是final的,所以ViewStubProxy对象取代了生成的绑定类中的ViewStub,使您可以在ViewStub存在时访问它,并在ViewStub inflate时访问inflated的视图层次结构。 142 | 143 | 在inflate另一个布局时,必须为新布局建立绑定。因此,ViewStubProxy必须侦听ViewStub 的OnInflateListener并在需要时建立绑定。由于在给定时间只能存在一个侦听器,因此ViewStubProxy允许您设置OnInflateListener,在建立绑定后它会被调用。 144 | 145 | ## 立即绑定(Immediate Binding) 146 | 147 | 当变量或可观察对象发生更改时,绑定会在下一帧(next frame)之前更改。但是,有时必须立即执行绑定,要强制立即执行,请使用[`executePendingBindings()`](https://developer.android.com/reference/android/databinding/ViewDataBinding.html#executePendingBindings())方法。 148 | 149 | ## 高级绑定 150 | 151 | ### 动态变量 152 | 153 | 有时,特殊的绑定类是未知的。例如,针对任意布局操作的RecyclerView.Adapter不知道特定的绑定类,它仍然必须在调用onBindViewHolder()方法期间分配绑定值。 154 | 155 | 在以下示例中,RecyclerView绑定的所有布局都具有item变量, BindingHolder对象有一个getBinding()方法返回ViewDataBinding基类。 156 | 157 | - kotlin 158 | 159 | - ```kotlin 160 | override fun onBindViewHolder(holder: BindingHolder, position: Int) { 161 | item: T = items.get(position) 162 | holder.binding.setVariable(BR.item, item); 163 | holder.binding.executePendingBindings(); 164 | } 165 | ``` 166 | 167 | - java 168 | 169 | - ```java 170 | public void onBindViewHolder(BindingHolder holder, int position) { 171 | final T item = items.get(position); 172 | holder.getBinding().setVariable(BR.item, item); 173 | holder.getBinding().executePendingBindings(); 174 | } 175 | ``` 176 | 177 | > 注意:数据绑定库在模块包中生成一个名为BR的类,其中包含用于数据绑定的资源的ID。在上面的示例中,库自动生成BR.item变量。 178 | 179 | ## 后台线程 180 | 181 | 只要你的data model不是一个collection,你都可以将其变为后台线程。数据绑定在评估期间本地化每个变量/字段以避免任何并发问题。 182 | 183 | ## 自定义绑定类的类名 184 | 185 | 默认的,会基于layout文件的名字生成一个绑定类,以大写字母开头,删除下划线(_),大写开头字母,并在后面添加单词Binding,该类放在模块包下的databinding包中。例如,布局文件contact_item.xml生成ContactItemBinding类,如果模块包是com.example.my.app,则绑定类放在com.example.my.app.databinding包中。 186 | 187 | 通过调整数据元素的class属性,可以重命名绑定类或将绑定类放在不同的包中。例如,以下布局在当前模块的数据绑定包中生成ContactItem绑定类: 188 | 189 | ```xml 190 | 191 | … 192 | 193 | ``` 194 | 195 | 您可以通过在类名前加一个点来为不同的包生成绑定类。以下示例在模块包中生成绑定类: 196 | 197 | ```xml 198 | 199 | … 200 | 201 | ``` 202 | 203 | 您还可以使用要在其中生成绑定类的完整包名称。以下示例在com.example包中创建ContactItem绑定类: 204 | 205 | ```xml 206 | 207 | … 208 | 209 | ``` 210 | 211 | ## 其他资源 212 | 213 | 了解更多数据绑定的内容,请参考: 214 | 215 | ### Samples 216 | 217 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 218 | 219 | ### Codelabs 220 | 221 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 222 | 223 | ### Blog posts 224 | 225 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /_book/ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——生成的绑定类.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——生成的绑定类 2 | 3 | 数据绑定库生成用于访问布局的变量(variables)和视图(views)的绑定类。此文档介绍如何创建和自定义生成的绑定类。 4 | 5 | 生成的绑定类将布局变量与布局中的视图链接起来,绑定类的名称和包可以自定义,所有生成的绑定类都继承自ViewDataBinding类。 6 | 7 | 为每个布局文件生成绑定类。默认情况下,类的名称基于布局文件的名称,将其转换为aaccPascal大小写并向其添加Binding后缀。布局文件名是activity_main.xml,相应的生成类是ActivityMainBinding,此类包含布局属性(例如,user变量)到布局视图的所有绑定,并知道如何为绑定表达式指定值。 8 | 9 | ## 创建一个绑定对象 10 | 11 | 在对布局进行inflateing之后,应该很快创建绑定对象,以确保布局中的表达式绑定到视图之前不会修改视图的层次结构。将对象绑定到布局的最常用方法是使用绑定类上的静态方法。您可以通过使用绑定类的inflate()方法来扩展视图层次结构并将对象绑定到该层次结构,如以下示例所示: 12 | 13 | - kotlin 14 | 15 | - ```kotlin 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | val binding: MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater) 20 | } 21 | ``` 22 | 23 | - java 24 | 25 | ```java 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater()); 30 | } 31 | ``` 32 | 33 | 除了[LayoutInflater](https://developer.android.com/reference/android/view/LayoutInflater.html)对象之外,还有一个替换版本的inflate()方法,它接受一个[ViewGroup](https://developer.android.com/reference/android/view/ViewGroup.html)对象,如下例所示: 34 | 35 | - kotlin 36 | 37 | ```kotlin 38 | val binding: MyLayoutBinding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false) 39 | ``` 40 | 41 | - java 42 | 43 | - ```java 44 | MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false); 45 | ``` 46 | 47 | 如果使用不同的机制对布局进行了inflate,则可以单独绑定,如下所示: 48 | 49 | - kotlin 50 | 51 | - ```kotlin 52 | val binding: MyLayoutBinding = MyLayoutBinding.bind(viewRoot) 53 | ``` 54 | 55 | - java 56 | 57 | - ```java 58 | MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot); 59 | ``` 60 | 61 | 有时不能事先知道绑定类型。在这种情况下,可以使用[`DataBindingUtil`](https://developer.android.com/reference/android/databinding/DataBindingUtil.html)类创建绑定,如以下代码段所示: 62 | 63 | - kotlin 64 | 65 | ```kotlin 66 | val viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent) 67 | val binding: ViewDataBinding? = DataBindingUtil.bind(viewRoot) 68 | ``` 69 | 70 | - java 71 | 72 | ```java 73 | View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent); 74 | ViewDataBinding binding = DataBindingUtil.bind(viewRoot); 75 | ``` 76 | 77 | 如果在Fragment,ListView或RecyclerView适配器中使用数据绑定item项,则可能更喜欢使用绑定类或DataBindingUtil类的inflate()方法,如以下代码示例所示: 78 | 79 | - kotlin 80 | 81 | ```kotlin 82 | val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false) 83 | // or 84 | val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false) 85 | ``` 86 | 87 | - java 88 | 89 | ```java 90 | ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); 91 | // or 92 | ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false); 93 | ``` 94 | 95 | ## 带ID的View 96 | 97 | 数据绑定库为每一个layout文件中具有ID的view创建不可变的字段,比如,以下代码中数据绑定库创建名为firstName和lastName,类型为TextView的字段: 98 | 99 | ```xml 100 | 101 | 102 | 103 | 104 | 108 | 112 | 116 | 117 | 118 | ``` 119 | 120 | 该库在一次传递中从视图层次结构中提取具有ID的View,此机制比为布局中的每个视图调用findViewById()方法更快。 121 | 122 | 如果没有数据绑定,则ID是非必要的,但仍有一些情况需要通过代码访问视图。 123 | 124 | ## 变量 125 | 126 | 数据绑定库为每一个声明在layout文件中的变量生成访问方法,比如,对于下面的layout文件,数据绑定类会为user,image和note生成setter和getter方法: 127 | 128 | ```xml 129 | 130 | 131 | 132 | 133 | 134 | 135 | ``` 136 | 137 | ## ViewStubs 138 | 139 | 和普通View不同的是,ViewStub对象开始时是不可见的,当其被手动置位visible或者被inflate,它才会inflate另一个layout以替换原来自己的位置。 140 | 141 | 由于ViewStub基本上是从视图层次结构中消失的,因此绑定对象中的视图也必须消失以允许通过垃圾回收声明。因为View是final的,所以ViewStubProxy对象取代了生成的绑定类中的ViewStub,使您可以在ViewStub存在时访问它,并在ViewStub inflate时访问inflated的视图层次结构。 142 | 143 | 在inflate另一个布局时,必须为新布局建立绑定。因此,ViewStubProxy必须侦听ViewStub 的OnInflateListener并在需要时建立绑定。由于在给定时间只能存在一个侦听器,因此ViewStubProxy允许您设置OnInflateListener,在建立绑定后它会被调用。 144 | 145 | ## 立即绑定(Immediate Binding) 146 | 147 | 当变量或可观察对象发生更改时,绑定会在下一帧(next frame)之前更改。但是,有时必须立即执行绑定,要强制立即执行,请使用[`executePendingBindings()`](https://developer.android.com/reference/android/databinding/ViewDataBinding.html#executePendingBindings())方法。 148 | 149 | ## 高级绑定 150 | 151 | ### 动态变量 152 | 153 | 有时,特殊的绑定类是未知的。例如,针对任意布局操作的RecyclerView.Adapter不知道特定的绑定类,它仍然必须在调用onBindViewHolder()方法期间分配绑定值。 154 | 155 | 在以下示例中,RecyclerView绑定的所有布局都具有item变量, BindingHolder对象有一个getBinding()方法返回ViewDataBinding基类。 156 | 157 | - kotlin 158 | 159 | - ```kotlin 160 | override fun onBindViewHolder(holder: BindingHolder, position: Int) { 161 | item: T = items.get(position) 162 | holder.binding.setVariable(BR.item, item); 163 | holder.binding.executePendingBindings(); 164 | } 165 | ``` 166 | 167 | - java 168 | 169 | - ```java 170 | public void onBindViewHolder(BindingHolder holder, int position) { 171 | final T item = items.get(position); 172 | holder.getBinding().setVariable(BR.item, item); 173 | holder.getBinding().executePendingBindings(); 174 | } 175 | ``` 176 | 177 | > 注意:数据绑定库在模块包中生成一个名为BR的类,其中包含用于数据绑定的资源的ID。在上面的示例中,库自动生成BR.item变量。 178 | 179 | ## 后台线程 180 | 181 | 只要你的data model不是一个collection,你都可以将其变为后台线程。数据绑定在评估期间本地化每个变量/字段以避免任何并发问题。 182 | 183 | ## 自定义绑定类的类名 184 | 185 | 默认的,会基于layout文件的名字生成一个绑定类,以大写字母开头,删除下划线(_),大写开头字母,并在后面添加单词Binding,该类放在模块包下的databinding包中。例如,布局文件contact_item.xml生成ContactItemBinding类,如果模块包是com.example.my.app,则绑定类放在com.example.my.app.databinding包中。 186 | 187 | 通过调整数据元素的class属性,可以重命名绑定类或将绑定类放在不同的包中。例如,以下布局在当前模块的数据绑定包中生成ContactItem绑定类: 188 | 189 | ```xml 190 | 191 | … 192 | 193 | ``` 194 | 195 | 您可以通过在类名前加一个点来为不同的包生成绑定类。以下示例在模块包中生成绑定类: 196 | 197 | ```xml 198 | 199 | … 200 | 201 | ``` 202 | 203 | 您还可以使用要在其中生成绑定类的完整包名称。以下示例在com.example包中创建ContactItem绑定类: 204 | 205 | ```xml 206 | 207 | … 208 | 209 | ``` 210 | 211 | ## 其他资源 212 | 213 | 了解更多数据绑定的内容,请参考: 214 | 215 | ### Samples 216 | 217 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 218 | 219 | ### Codelabs 220 | 221 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 222 | 223 | ### Blog posts 224 | 225 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-search/search.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | var MAX_RESULTS = 15; 6 | var MAX_DESCRIPTION_SIZE = 500; 7 | 8 | var usePushState = (typeof history.pushState !== 'undefined'); 9 | 10 | // DOM Elements 11 | var $body = $('body'); 12 | var $bookSearchResults; 13 | var $searchInput; 14 | var $searchList; 15 | var $searchTitle; 16 | var $searchResultsCount; 17 | var $searchQuery; 18 | 19 | // Throttle search 20 | function throttle(fn, wait) { 21 | var timeout; 22 | 23 | return function() { 24 | var ctx = this, args = arguments; 25 | if (!timeout) { 26 | timeout = setTimeout(function() { 27 | timeout = null; 28 | fn.apply(ctx, args); 29 | }, wait); 30 | } 31 | }; 32 | } 33 | 34 | function displayResults(res) { 35 | $bookSearchResults.addClass('open'); 36 | 37 | var noResults = res.count == 0; 38 | $bookSearchResults.toggleClass('no-results', noResults); 39 | 40 | // Clear old results 41 | $searchList.empty(); 42 | 43 | // Display title for research 44 | $searchResultsCount.text(res.count); 45 | $searchQuery.text(res.query); 46 | 47 | // Create an
  • element for each result 48 | res.results.forEach(function(res) { 49 | var $li = $('
  • ', { 50 | 'class': 'search-results-item' 51 | }); 52 | 53 | var $title = $('

    '); 54 | 55 | var $link = $('', { 56 | 'href': gitbook.state.basePath + '/' + res.url, 57 | 'text': res.title 58 | }); 59 | 60 | var content = res.body.trim(); 61 | if (content.length > MAX_DESCRIPTION_SIZE) { 62 | content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...'; 63 | } 64 | var $content = $('

    ').html(content); 65 | 66 | $link.appendTo($title); 67 | $title.appendTo($li); 68 | $content.appendTo($li); 69 | $li.appendTo($searchList); 70 | }); 71 | } 72 | 73 | function launchSearch(q) { 74 | // Add class for loading 75 | $body.addClass('with-search'); 76 | $body.addClass('search-loading'); 77 | 78 | // Launch search query 79 | throttle(gitbook.search.query(q, 0, MAX_RESULTS) 80 | .then(function(results) { 81 | displayResults(results); 82 | }) 83 | .always(function() { 84 | $body.removeClass('search-loading'); 85 | }), 1000); 86 | } 87 | 88 | function closeSearch() { 89 | $body.removeClass('with-search'); 90 | $bookSearchResults.removeClass('open'); 91 | } 92 | 93 | function launchSearchFromQueryString() { 94 | var q = getParameterByName('q'); 95 | if (q && q.length > 0) { 96 | // Update search input 97 | $searchInput.val(q); 98 | 99 | // Launch search 100 | launchSearch(q); 101 | } 102 | } 103 | 104 | function bindSearch() { 105 | // Bind DOM 106 | $searchInput = $('#book-search-input input'); 107 | $bookSearchResults = $('#book-search-results'); 108 | $searchList = $bookSearchResults.find('.search-results-list'); 109 | $searchTitle = $bookSearchResults.find('.search-results-title'); 110 | $searchResultsCount = $searchTitle.find('.search-results-count'); 111 | $searchQuery = $searchTitle.find('.search-query'); 112 | 113 | // Launch query based on input content 114 | function handleUpdate() { 115 | var q = $searchInput.val(); 116 | 117 | if (q.length == 0) { 118 | closeSearch(); 119 | } 120 | else { 121 | launchSearch(q); 122 | } 123 | } 124 | 125 | // Detect true content change in search input 126 | // Workaround for IE < 9 127 | var propertyChangeUnbound = false; 128 | $searchInput.on('propertychange', function(e) { 129 | if (e.originalEvent.propertyName == 'value') { 130 | handleUpdate(); 131 | } 132 | }); 133 | 134 | // HTML5 (IE9 & others) 135 | $searchInput.on('input', function(e) { 136 | // Unbind propertychange event for IE9+ 137 | if (!propertyChangeUnbound) { 138 | $(this).unbind('propertychange'); 139 | propertyChangeUnbound = true; 140 | } 141 | 142 | handleUpdate(); 143 | }); 144 | 145 | // Push to history on blur 146 | $searchInput.on('blur', function(e) { 147 | // Update history state 148 | if (usePushState) { 149 | var uri = updateQueryString('q', $(this).val()); 150 | history.pushState({ path: uri }, null, uri); 151 | } 152 | }); 153 | } 154 | 155 | gitbook.events.on('page.change', function() { 156 | bindSearch(); 157 | closeSearch(); 158 | 159 | // Launch search based on query parameter 160 | if (gitbook.search.isInitialized()) { 161 | launchSearchFromQueryString(); 162 | } 163 | }); 164 | 165 | gitbook.events.on('search.ready', function() { 166 | bindSearch(); 167 | 168 | // Launch search from query param at start 169 | launchSearchFromQueryString(); 170 | }); 171 | 172 | function getParameterByName(name) { 173 | var url = window.location.href; 174 | name = name.replace(/[\[\]]/g, '\\$&'); 175 | var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)', 'i'), 176 | results = regex.exec(url); 177 | if (!results) return null; 178 | if (!results[2]) return ''; 179 | return decodeURIComponent(results[2].replace(/\+/g, ' ')); 180 | } 181 | 182 | function updateQueryString(key, value) { 183 | value = encodeURIComponent(value); 184 | 185 | var url = window.location.href; 186 | var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'), 187 | hash; 188 | 189 | if (re.test(url)) { 190 | if (typeof value !== 'undefined' && value !== null) 191 | return url.replace(re, '$1' + key + '=' + value + '$2$3'); 192 | else { 193 | hash = url.split('#'); 194 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 195 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 196 | url += '#' + hash[1]; 197 | return url; 198 | } 199 | } 200 | else { 201 | if (typeof value !== 'undefined' && value !== null) { 202 | var separator = url.indexOf('?') !== -1 ? '&' : '?'; 203 | hash = url.split('#'); 204 | url = hash[0] + separator + key + '=' + value; 205 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 206 | url += '#' + hash[1]; 207 | return url; 208 | } 209 | else 210 | return url; 211 | } 212 | } 213 | }); 214 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-search/search 2.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'gitbook', 3 | 'jquery' 4 | ], function(gitbook, $) { 5 | var MAX_RESULTS = 15; 6 | var MAX_DESCRIPTION_SIZE = 500; 7 | 8 | var usePushState = (typeof history.pushState !== 'undefined'); 9 | 10 | // DOM Elements 11 | var $body = $('body'); 12 | var $bookSearchResults; 13 | var $searchInput; 14 | var $searchList; 15 | var $searchTitle; 16 | var $searchResultsCount; 17 | var $searchQuery; 18 | 19 | // Throttle search 20 | function throttle(fn, wait) { 21 | var timeout; 22 | 23 | return function() { 24 | var ctx = this, args = arguments; 25 | if (!timeout) { 26 | timeout = setTimeout(function() { 27 | timeout = null; 28 | fn.apply(ctx, args); 29 | }, wait); 30 | } 31 | }; 32 | } 33 | 34 | function displayResults(res) { 35 | $bookSearchResults.addClass('open'); 36 | 37 | var noResults = res.count == 0; 38 | $bookSearchResults.toggleClass('no-results', noResults); 39 | 40 | // Clear old results 41 | $searchList.empty(); 42 | 43 | // Display title for research 44 | $searchResultsCount.text(res.count); 45 | $searchQuery.text(res.query); 46 | 47 | // Create an

  • element for each result 48 | res.results.forEach(function(res) { 49 | var $li = $('
  • ', { 50 | 'class': 'search-results-item' 51 | }); 52 | 53 | var $title = $('

    '); 54 | 55 | var $link = $('', { 56 | 'href': gitbook.state.basePath + '/' + res.url, 57 | 'text': res.title 58 | }); 59 | 60 | var content = res.body.trim(); 61 | if (content.length > MAX_DESCRIPTION_SIZE) { 62 | content = content.slice(0, MAX_DESCRIPTION_SIZE).trim()+'...'; 63 | } 64 | var $content = $('

    ').html(content); 65 | 66 | $link.appendTo($title); 67 | $title.appendTo($li); 68 | $content.appendTo($li); 69 | $li.appendTo($searchList); 70 | }); 71 | } 72 | 73 | function launchSearch(q) { 74 | // Add class for loading 75 | $body.addClass('with-search'); 76 | $body.addClass('search-loading'); 77 | 78 | // Launch search query 79 | throttle(gitbook.search.query(q, 0, MAX_RESULTS) 80 | .then(function(results) { 81 | displayResults(results); 82 | }) 83 | .always(function() { 84 | $body.removeClass('search-loading'); 85 | }), 1000); 86 | } 87 | 88 | function closeSearch() { 89 | $body.removeClass('with-search'); 90 | $bookSearchResults.removeClass('open'); 91 | } 92 | 93 | function launchSearchFromQueryString() { 94 | var q = getParameterByName('q'); 95 | if (q && q.length > 0) { 96 | // Update search input 97 | $searchInput.val(q); 98 | 99 | // Launch search 100 | launchSearch(q); 101 | } 102 | } 103 | 104 | function bindSearch() { 105 | // Bind DOM 106 | $searchInput = $('#book-search-input input'); 107 | $bookSearchResults = $('#book-search-results'); 108 | $searchList = $bookSearchResults.find('.search-results-list'); 109 | $searchTitle = $bookSearchResults.find('.search-results-title'); 110 | $searchResultsCount = $searchTitle.find('.search-results-count'); 111 | $searchQuery = $searchTitle.find('.search-query'); 112 | 113 | // Launch query based on input content 114 | function handleUpdate() { 115 | var q = $searchInput.val(); 116 | 117 | if (q.length == 0) { 118 | closeSearch(); 119 | } 120 | else { 121 | launchSearch(q); 122 | } 123 | } 124 | 125 | // Detect true content change in search input 126 | // Workaround for IE < 9 127 | var propertyChangeUnbound = false; 128 | $searchInput.on('propertychange', function(e) { 129 | if (e.originalEvent.propertyName == 'value') { 130 | handleUpdate(); 131 | } 132 | }); 133 | 134 | // HTML5 (IE9 & others) 135 | $searchInput.on('input', function(e) { 136 | // Unbind propertychange event for IE9+ 137 | if (!propertyChangeUnbound) { 138 | $(this).unbind('propertychange'); 139 | propertyChangeUnbound = true; 140 | } 141 | 142 | handleUpdate(); 143 | }); 144 | 145 | // Push to history on blur 146 | $searchInput.on('blur', function(e) { 147 | // Update history state 148 | if (usePushState) { 149 | var uri = updateQueryString('q', $(this).val()); 150 | history.pushState({ path: uri }, null, uri); 151 | } 152 | }); 153 | } 154 | 155 | gitbook.events.on('page.change', function() { 156 | bindSearch(); 157 | closeSearch(); 158 | 159 | // Launch search based on query parameter 160 | if (gitbook.search.isInitialized()) { 161 | launchSearchFromQueryString(); 162 | } 163 | }); 164 | 165 | gitbook.events.on('search.ready', function() { 166 | bindSearch(); 167 | 168 | // Launch search from query param at start 169 | launchSearchFromQueryString(); 170 | }); 171 | 172 | function getParameterByName(name) { 173 | var url = window.location.href; 174 | name = name.replace(/[\[\]]/g, '\\$&'); 175 | var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)', 'i'), 176 | results = regex.exec(url); 177 | if (!results) return null; 178 | if (!results[2]) return ''; 179 | return decodeURIComponent(results[2].replace(/\+/g, ' ')); 180 | } 181 | 182 | function updateQueryString(key, value) { 183 | value = encodeURIComponent(value); 184 | 185 | var url = window.location.href; 186 | var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi'), 187 | hash; 188 | 189 | if (re.test(url)) { 190 | if (typeof value !== 'undefined' && value !== null) 191 | return url.replace(re, '$1' + key + '=' + value + '$2$3'); 192 | else { 193 | hash = url.split('#'); 194 | url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); 195 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 196 | url += '#' + hash[1]; 197 | return url; 198 | } 199 | } 200 | else { 201 | if (typeof value !== 'undefined' && value !== null) { 202 | var separator = url.indexOf('?') !== -1 ? '&' : '?'; 203 | hash = url.split('#'); 204 | url = hash[0] + separator + key + '=' + value; 205 | if (typeof hash[1] !== 'undefined' && hash[1] !== null) 206 | url += '#' + hash[1]; 207 | return url; 208 | } 209 | else 210 | return url; 211 | } 212 | } 213 | }); 214 | -------------------------------------------------------------------------------- /_book/gitbook/gitbook-plugin-fontsettings/fontsettings.js: -------------------------------------------------------------------------------- 1 | require(['gitbook', 'jquery'], function(gitbook, $) { 2 | // Configuration 3 | var MAX_SIZE = 4, 4 | MIN_SIZE = 0, 5 | BUTTON_ID; 6 | 7 | // Current fontsettings state 8 | var fontState; 9 | 10 | // Default themes 11 | var THEMES = [ 12 | { 13 | config: 'white', 14 | text: 'White', 15 | id: 0 16 | }, 17 | { 18 | config: 'sepia', 19 | text: 'Sepia', 20 | id: 1 21 | }, 22 | { 23 | config: 'night', 24 | text: 'Night', 25 | id: 2 26 | } 27 | ]; 28 | 29 | // Default font families 30 | var FAMILIES = [ 31 | { 32 | config: 'serif', 33 | text: 'Serif', 34 | id: 0 35 | }, 36 | { 37 | config: 'sans', 38 | text: 'Sans', 39 | id: 1 40 | } 41 | ]; 42 | 43 | // Return configured themes 44 | function getThemes() { 45 | return THEMES; 46 | } 47 | 48 | // Modify configured themes 49 | function setThemes(themes) { 50 | THEMES = themes; 51 | updateButtons(); 52 | } 53 | 54 | // Return configured font families 55 | function getFamilies() { 56 | return FAMILIES; 57 | } 58 | 59 | // Modify configured font families 60 | function setFamilies(families) { 61 | FAMILIES = families; 62 | updateButtons(); 63 | } 64 | 65 | // Save current font settings 66 | function saveFontSettings() { 67 | gitbook.storage.set('fontState', fontState); 68 | update(); 69 | } 70 | 71 | // Increase font size 72 | function enlargeFontSize(e) { 73 | e.preventDefault(); 74 | if (fontState.size >= MAX_SIZE) return; 75 | 76 | fontState.size++; 77 | saveFontSettings(); 78 | } 79 | 80 | // Decrease font size 81 | function reduceFontSize(e) { 82 | e.preventDefault(); 83 | if (fontState.size <= MIN_SIZE) return; 84 | 85 | fontState.size--; 86 | saveFontSettings(); 87 | } 88 | 89 | // Change font family 90 | function changeFontFamily(configName, e) { 91 | if (e && e instanceof Event) { 92 | e.preventDefault(); 93 | } 94 | 95 | var familyId = getFontFamilyId(configName); 96 | fontState.family = familyId; 97 | saveFontSettings(); 98 | } 99 | 100 | // Change type of color theme 101 | function changeColorTheme(configName, e) { 102 | if (e && e instanceof Event) { 103 | e.preventDefault(); 104 | } 105 | 106 | var $book = gitbook.state.$book; 107 | 108 | // Remove currently applied color theme 109 | if (fontState.theme !== 0) 110 | $book.removeClass('color-theme-'+fontState.theme); 111 | 112 | // Set new color theme 113 | var themeId = getThemeId(configName); 114 | fontState.theme = themeId; 115 | if (fontState.theme !== 0) 116 | $book.addClass('color-theme-'+fontState.theme); 117 | 118 | saveFontSettings(); 119 | } 120 | 121 | // Return the correct id for a font-family config key 122 | // Default to first font-family 123 | function getFontFamilyId(configName) { 124 | // Search for plugin configured font family 125 | var configFamily = $.grep(FAMILIES, function(family) { 126 | return family.config == configName; 127 | })[0]; 128 | // Fallback to default font family 129 | return (!!configFamily)? configFamily.id : 0; 130 | } 131 | 132 | // Return the correct id for a theme config key 133 | // Default to first theme 134 | function getThemeId(configName) { 135 | // Search for plugin configured theme 136 | var configTheme = $.grep(THEMES, function(theme) { 137 | return theme.config == configName; 138 | })[0]; 139 | // Fallback to default theme 140 | return (!!configTheme)? configTheme.id : 0; 141 | } 142 | 143 | function update() { 144 | var $book = gitbook.state.$book; 145 | 146 | $('.font-settings .font-family-list li').removeClass('active'); 147 | $('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active'); 148 | 149 | $book[0].className = $book[0].className.replace(/\bfont-\S+/g, ''); 150 | $book.addClass('font-size-'+fontState.size); 151 | $book.addClass('font-family-'+fontState.family); 152 | 153 | if(fontState.theme !== 0) { 154 | $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, ''); 155 | $book.addClass('color-theme-'+fontState.theme); 156 | } 157 | } 158 | 159 | function init(config) { 160 | // Search for plugin configured font family 161 | var configFamily = getFontFamilyId(config.family), 162 | configTheme = getThemeId(config.theme); 163 | 164 | // Instantiate font state object 165 | fontState = gitbook.storage.get('fontState', { 166 | size: config.size || 2, 167 | family: configFamily, 168 | theme: configTheme 169 | }); 170 | 171 | update(); 172 | } 173 | 174 | function updateButtons() { 175 | // Remove existing fontsettings buttons 176 | if (!!BUTTON_ID) { 177 | gitbook.toolbar.removeButton(BUTTON_ID); 178 | } 179 | 180 | // Create buttons in toolbar 181 | BUTTON_ID = gitbook.toolbar.createButton({ 182 | icon: 'fa fa-font', 183 | label: 'Font Settings', 184 | className: 'font-settings', 185 | dropdown: [ 186 | [ 187 | { 188 | text: 'A', 189 | className: 'font-reduce', 190 | onClick: reduceFontSize 191 | }, 192 | { 193 | text: 'A', 194 | className: 'font-enlarge', 195 | onClick: enlargeFontSize 196 | } 197 | ], 198 | $.map(FAMILIES, function(family) { 199 | family.onClick = function(e) { 200 | return changeFontFamily(family.config, e); 201 | }; 202 | 203 | return family; 204 | }), 205 | $.map(THEMES, function(theme) { 206 | theme.onClick = function(e) { 207 | return changeColorTheme(theme.config, e); 208 | }; 209 | 210 | return theme; 211 | }) 212 | ] 213 | }); 214 | } 215 | 216 | // Init configuration at start 217 | gitbook.events.bind('start', function(e, config) { 218 | var opts = config.fontsettings; 219 | 220 | // Generate buttons at start 221 | updateButtons(); 222 | 223 | // Init current settings 224 | init(opts); 225 | }); 226 | 227 | // Expose API 228 | gitbook.fontsettings = { 229 | enlargeFontSize: enlargeFontSize, 230 | reduceFontSize: reduceFontSize, 231 | setTheme: changeColorTheme, 232 | setFamily: changeFontFamily, 233 | getThemes: getThemes, 234 | setThemes: setThemes, 235 | getFamilies: getFamilies, 236 | setFamilies: setFamilies 237 | }; 238 | }); 239 | 240 | 241 | -------------------------------------------------------------------------------- /ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——使用可观察的数据对象.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——使用可观察的数据对象 2 | 3 | 观察能力指的是当对象中的数据发生改变时,它能通知其他对象该更改的能力。数据绑定库允许你使对象、字段、集合编程可观察的。 4 | 5 | 任何普通旧对象( plain-old object )都可用于数据绑定,但修改对象不会自动导致UI更新。数据绑定可用于为数据对象提供在数据更改时通知其他对象(称为侦听器)的能力。有三种不同类型的可观察类:对象,字段和集合( [objects](https://developer.android.com/topic/libraries/data-binding/observability#observable_objects), [fields](https://developer.android.com/topic/libraries/data-binding/observability#observable_fields), [collections](https://developer.android.com/topic/libraries/data-binding/observability#observable_collections))。 6 | 7 | 当这些可观察的数据对象呗绑定到UI上并且数据对象发生改变,UI将会自动更新。 8 | 9 | ## 可观察的字段(fields) 10 | 11 | 一些需要的实现在创建实现(Implement)了 [`Observable`](https://developer.android.com/reference/android/databinding/Observable.html)接口的类时会被自动完成,如果你的类只有一些属性,这是不值得的。在这种情况下,您可以使用通用Observable类和以下基于原语的类来使字段可观察: 12 | 13 | - [`ObservableBoolean`](https://developer.android.com/reference/android/databinding/ObservableBoolean.html) 14 | - [`ObservableByte`](https://developer.android.com/reference/android/databinding/ObservableByte.html) 15 | - [`ObservableChar`](https://developer.android.com/reference/android/databinding/ObservableChar.html) 16 | - [`ObservableShort`](https://developer.android.com/reference/android/databinding/ObservableShort.html) 17 | - [`ObservableInt`](https://developer.android.com/reference/android/databinding/ObservableInt.html) 18 | - [`ObservableLong`](https://developer.android.com/reference/android/databinding/ObservableLong.html) 19 | - [`ObservableFloat`](https://developer.android.com/reference/android/databinding/ObservableFloat.html) 20 | - [`ObservableDouble`](https://developer.android.com/reference/android/databinding/ObservableDouble.html) 21 | - [`ObservableParcelable`](https://developer.android.com/reference/android/databinding/ObservableParcelable.html) 22 | 23 | 可观察字段个本身就含有单个可观察对象字段的可观察对象。原始版本在访问操作期间避免装箱和拆箱。要使用此机制,请在Java编程语言中创建public final属性或在Kotlin中创建只读属性,如以下示例所示: 24 | 25 | - kotlin 26 | 27 | - ```kotlin 28 | class User { 29 | val firstName = ObservableField() 30 | val lastName = ObservableField() 31 | val age = ObservableInt() 32 | } 33 | ``` 34 | 35 | - java 36 | 37 | - ```java 38 | private static class User { 39 | public final ObservableField firstName = new ObservableField<>(); 40 | public final ObservableField lastName = new ObservableField<>(); 41 | public final ObservableInt age = new ObservableInt(); 42 | } 43 | ``` 44 | 45 | 要访问字段值,请使用get()和set()访问方法,如下所示: 46 | 47 | - kotlin 48 | 49 | ```kotlin 50 | user.firstName = "Google" 51 | val age = user.age 52 | ``` 53 | 54 | - java 55 | 56 | ```java 57 | user.firstName.set("Google"); 58 | int age = user.age.get(); 59 | ``` 60 | 61 | > 注意:Android Studio3.1及以上允许你使用LiveData对象替换可观察的字段,该做法为你的app带来了更多好处,想了解更多的信息,请参考[使用LiveData通知UI有关数据更改的信息](./数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md#use-livedata-to-notify-the-ui-about-data-changes) 62 | 63 | ## 可观察的集合(collections) 64 | 65 | 一些应用程序使用动态结构来保存数据。可观察集合(Observable collections)允许使用key访问这些结构,当键是引用类型(如String)时,[`ObservableArrayMap`](https://developer.android.com/reference/android/databinding/ObservableArrayMap.html)类很有用,如以下示例所示: 66 | 67 | - kotlin 68 | 69 | - ```kotlin 70 | ObservableArrayMap().apply { 71 | put("firstName", "Google") 72 | put("lastName", "Inc.") 73 | put("age", 17) 74 | } 75 | ``` 76 | 77 | - java 78 | 79 | - ```java 80 | ObservableArrayMap user = new ObservableArrayMap<>(); 81 | user.put("firstName", "Google"); 82 | user.put("lastName", "Inc."); 83 | user.put("age", 17); 84 | ``` 85 | 86 | 在layout文件中,map可以使用string类型的key被绑定: 87 | 88 | ```xml 89 | 90 | 91 | 92 | 93 | … 94 | 98 | 102 | ``` 103 | 104 | [`ObservableArrayList`](https://developer.android.com/reference/android/databinding/ObservableArrayList.html)在key是integer类型的时候很有用,比如: 105 | 106 | - kotlin 107 | 108 | ```kotlin 109 | ObservableArrayList().apply { 110 | add("Google") 111 | add("Inc.") 112 | add(17) 113 | } 114 | ``` 115 | 116 | - java 117 | 118 | ```java 119 | ObservableArrayList user = new ObservableArrayList<>(); 120 | user.add("Google"); 121 | user.add("Inc."); 122 | user.add(17); 123 | ``` 124 | 125 | 在layout文件中,该List可以使用index被访问,就像: 126 | 127 | ```xml 128 | 129 | 130 | 131 | 132 | 133 | … 134 | 138 | 142 | ``` 143 | 144 | ## 可观察的对象(objects) 145 | 146 | 实现[`Observable`](https://developer.android.com/reference/android/databinding/Observable.html)接口的类允许注册希望被观察对象属性改变时被通知的侦听器。 147 | 148 | Observable接口有一个添加和删除侦听器的机制,但您必须决定何时发送通知。为了简化开发,数据绑定库提供了[`BaseObservable`](https://developer.android.com/reference/android/databinding/BaseObservable.html)类,该类实现了侦听器注册机制。实现BaseObservable的数据类负责通知属性何时更改,这是通过将一个[`Bindable`](https://developer.android.com/reference/android/databinding/Bindable.html)注释分配给getter并在setter中调用[`notifyPropertyChanged()`](https://developer.android.com/reference/android/databinding/BaseObservable.html#notifyPropertyChanged(int))方法来完成的,如以下示例所示: 149 | 150 | - kotlin 151 | 152 | - ```kotlin 153 | class User : BaseObservable() { 154 | 155 | @get:Bindable 156 | var firstName: String = "" 157 | set(value) { 158 | field = value 159 | notifyPropertyChanged(BR.firstName) 160 | } 161 | 162 | @get:Bindable 163 | var lastName: String = "" 164 | set(value) { 165 | field = value 166 | notifyPropertyChanged(BR.lastName) 167 | } 168 | } 169 | ``` 170 | 171 | - java 172 | 173 | ```java 174 | private static class User extends BaseObservable { 175 | private String firstName; 176 | private String lastName; 177 | 178 | @Bindable 179 | public String getFirstName() { 180 | return this.firstName; 181 | } 182 | 183 | @Bindable 184 | public String getLastName() { 185 | return this.lastName; 186 | } 187 | 188 | public void setFirstName(String firstName) { 189 | this.firstName = firstName; 190 | notifyPropertyChanged(BR.firstName); 191 | } 192 | 193 | public void setLastName(String lastName) { 194 | this.lastName = lastName; 195 | notifyPropertyChanged(BR.lastName); 196 | } 197 | } 198 | ``` 199 | 200 | 数据绑定在模块包中生成一个名为BR的类,该类包含用于数据绑定的资源的ID, Bindable注释在编译期间在BR类文件中生成一个条目(entry),如果数据类的基类不能被更改,则可以使用[`PropertyChangeRegistry`](https://developer.android.com/reference/android/databinding/PropertyChangeRegistry.html)对象实现Observable接口,以有效地注册和通知侦听器。 201 | 202 | ## 其他资源 203 | 204 | 想了解更多有关数据绑定的内容,请参考: 205 | 206 | ### Samples 207 | 208 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 209 | 210 | ### Codelabs 211 | 212 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 213 | 214 | ### Blog posts 215 | 216 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /_book/ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——使用可观察的数据对象 2.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——使用可观察的数据对象 2 | 3 | 观察能力指的是当对象中的数据发生改变时,它能通知其他对象该更改的能力。数据绑定库允许你使对象、字段、集合编程可观察的。 4 | 5 | 任何普通旧对象( plain-old object )都可用于数据绑定,但修改对象不会自动导致UI更新。数据绑定可用于为数据对象提供在数据更改时通知其他对象(称为侦听器)的能力。有三种不同类型的可观察类:对象,字段和集合( [objects](https://developer.android.com/topic/libraries/data-binding/observability#observable_objects), [fields](https://developer.android.com/topic/libraries/data-binding/observability#observable_fields), [collections](https://developer.android.com/topic/libraries/data-binding/observability#observable_collections))。 6 | 7 | 当这些可观察的数据对象呗绑定到UI上并且数据对象发生改变,UI将会自动更新。 8 | 9 | ## 可观察的字段(fields) 10 | 11 | 一些需要的实现在创建实现(Implement)了 [`Observable`](https://developer.android.com/reference/android/databinding/Observable.html)接口的类时会被自动完成,如果你的类只有一些属性,这是不值得的。在这种情况下,您可以使用通用Observable类和以下基于原语的类来使字段可观察: 12 | 13 | - [`ObservableBoolean`](https://developer.android.com/reference/android/databinding/ObservableBoolean.html) 14 | - [`ObservableByte`](https://developer.android.com/reference/android/databinding/ObservableByte.html) 15 | - [`ObservableChar`](https://developer.android.com/reference/android/databinding/ObservableChar.html) 16 | - [`ObservableShort`](https://developer.android.com/reference/android/databinding/ObservableShort.html) 17 | - [`ObservableInt`](https://developer.android.com/reference/android/databinding/ObservableInt.html) 18 | - [`ObservableLong`](https://developer.android.com/reference/android/databinding/ObservableLong.html) 19 | - [`ObservableFloat`](https://developer.android.com/reference/android/databinding/ObservableFloat.html) 20 | - [`ObservableDouble`](https://developer.android.com/reference/android/databinding/ObservableDouble.html) 21 | - [`ObservableParcelable`](https://developer.android.com/reference/android/databinding/ObservableParcelable.html) 22 | 23 | 可观察字段个本身就含有单个可观察对象字段的可观察对象。原始版本在访问操作期间避免装箱和拆箱。要使用此机制,请在Java编程语言中创建public final属性或在Kotlin中创建只读属性,如以下示例所示: 24 | 25 | - kotlin 26 | 27 | - ```kotlin 28 | class User { 29 | val firstName = ObservableField() 30 | val lastName = ObservableField() 31 | val age = ObservableInt() 32 | } 33 | ``` 34 | 35 | - java 36 | 37 | - ```java 38 | private static class User { 39 | public final ObservableField firstName = new ObservableField<>(); 40 | public final ObservableField lastName = new ObservableField<>(); 41 | public final ObservableInt age = new ObservableInt(); 42 | } 43 | ``` 44 | 45 | 要访问字段值,请使用get()和set()访问方法,如下所示: 46 | 47 | - kotlin 48 | 49 | ```kotlin 50 | user.firstName = "Google" 51 | val age = user.age 52 | ``` 53 | 54 | - java 55 | 56 | ```java 57 | user.firstName.set("Google"); 58 | int age = user.age.get(); 59 | ``` 60 | 61 | > 注意:Android Studio3.1及以上允许你使用LiveData对象替换可观察的字段,该做法为你的app带来了更多好处,想了解更多的信息,请参考[使用LiveData通知UI有关数据更改的信息](./数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md#use-livedata-to-notify-the-ui-about-data-changes) 62 | 63 | ## 可观察的集合(collections) 64 | 65 | 一些应用程序使用动态结构来保存数据。可观察集合(Observable collections)允许使用key访问这些结构,当键是引用类型(如String)时,[`ObservableArrayMap`](https://developer.android.com/reference/android/databinding/ObservableArrayMap.html)类很有用,如以下示例所示: 66 | 67 | - kotlin 68 | 69 | - ```kotlin 70 | ObservableArrayMap().apply { 71 | put("firstName", "Google") 72 | put("lastName", "Inc.") 73 | put("age", 17) 74 | } 75 | ``` 76 | 77 | - java 78 | 79 | - ```java 80 | ObservableArrayMap user = new ObservableArrayMap<>(); 81 | user.put("firstName", "Google"); 82 | user.put("lastName", "Inc."); 83 | user.put("age", 17); 84 | ``` 85 | 86 | 在layout文件中,map可以使用string类型的key被绑定: 87 | 88 | ```xml 89 | 90 | 91 | 92 | 93 | … 94 | 98 | 102 | ``` 103 | 104 | [`ObservableArrayList`](https://developer.android.com/reference/android/databinding/ObservableArrayList.html)在key是integer类型的时候很有用,比如: 105 | 106 | - kotlin 107 | 108 | ```kotlin 109 | ObservableArrayList().apply { 110 | add("Google") 111 | add("Inc.") 112 | add(17) 113 | } 114 | ``` 115 | 116 | - java 117 | 118 | ```java 119 | ObservableArrayList user = new ObservableArrayList<>(); 120 | user.add("Google"); 121 | user.add("Inc."); 122 | user.add(17); 123 | ``` 124 | 125 | 在layout文件中,该List可以使用index被访问,就像: 126 | 127 | ```xml 128 | 129 | 130 | 131 | 132 | 133 | … 134 | 138 | 142 | ``` 143 | 144 | ## 可观察的对象(objects) 145 | 146 | 实现[`Observable`](https://developer.android.com/reference/android/databinding/Observable.html)接口的类允许注册希望被观察对象属性改变时被通知的侦听器。 147 | 148 | Observable接口有一个添加和删除侦听器的机制,但您必须决定何时发送通知。为了简化开发,数据绑定库提供了[`BaseObservable`](https://developer.android.com/reference/android/databinding/BaseObservable.html)类,该类实现了侦听器注册机制。实现BaseObservable的数据类负责通知属性何时更改,这是通过将一个[`Bindable`](https://developer.android.com/reference/android/databinding/Bindable.html)注释分配给getter并在setter中调用[`notifyPropertyChanged()`](https://developer.android.com/reference/android/databinding/BaseObservable.html#notifyPropertyChanged(int))方法来完成的,如以下示例所示: 149 | 150 | - kotlin 151 | 152 | - ```kotlin 153 | class User : BaseObservable() { 154 | 155 | @get:Bindable 156 | var firstName: String = "" 157 | set(value) { 158 | field = value 159 | notifyPropertyChanged(BR.firstName) 160 | } 161 | 162 | @get:Bindable 163 | var lastName: String = "" 164 | set(value) { 165 | field = value 166 | notifyPropertyChanged(BR.lastName) 167 | } 168 | } 169 | ``` 170 | 171 | - java 172 | 173 | ```java 174 | private static class User extends BaseObservable { 175 | private String firstName; 176 | private String lastName; 177 | 178 | @Bindable 179 | public String getFirstName() { 180 | return this.firstName; 181 | } 182 | 183 | @Bindable 184 | public String getLastName() { 185 | return this.lastName; 186 | } 187 | 188 | public void setFirstName(String firstName) { 189 | this.firstName = firstName; 190 | notifyPropertyChanged(BR.firstName); 191 | } 192 | 193 | public void setLastName(String lastName) { 194 | this.lastName = lastName; 195 | notifyPropertyChanged(BR.lastName); 196 | } 197 | } 198 | ``` 199 | 200 | 数据绑定在模块包中生成一个名为BR的类,该类包含用于数据绑定的资源的ID, Bindable注释在编译期间在BR类文件中生成一个条目(entry),如果数据类的基类不能被更改,则可以使用[`PropertyChangeRegistry`](https://developer.android.com/reference/android/databinding/PropertyChangeRegistry.html)对象实现Observable接口,以有效地注册和通知侦听器。 201 | 202 | ## 其他资源 203 | 204 | 想了解更多有关数据绑定的内容,请参考: 205 | 206 | ### Samples 207 | 208 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 209 | 210 | ### Codelabs 211 | 212 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 213 | 214 | ### Blog posts 215 | 216 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /_book/ArchitectureComponents/DataBindingLibrary/数据(Data Binding)绑定库——使用可观察的数据对象.md: -------------------------------------------------------------------------------- 1 | # 数据(Data Binding)绑定库——使用可观察的数据对象 2 | 3 | 观察能力指的是当对象中的数据发生改变时,它能通知其他对象该更改的能力。数据绑定库允许你使对象、字段、集合编程可观察的。 4 | 5 | 任何普通旧对象( plain-old object )都可用于数据绑定,但修改对象不会自动导致UI更新。数据绑定可用于为数据对象提供在数据更改时通知其他对象(称为侦听器)的能力。有三种不同类型的可观察类:对象,字段和集合( [objects](https://developer.android.com/topic/libraries/data-binding/observability#observable_objects), [fields](https://developer.android.com/topic/libraries/data-binding/observability#observable_fields), [collections](https://developer.android.com/topic/libraries/data-binding/observability#observable_collections))。 6 | 7 | 当这些可观察的数据对象呗绑定到UI上并且数据对象发生改变,UI将会自动更新。 8 | 9 | ## 可观察的字段(fields) 10 | 11 | 一些需要的实现在创建实现(Implement)了 [`Observable`](https://developer.android.com/reference/android/databinding/Observable.html)接口的类时会被自动完成,如果你的类只有一些属性,这是不值得的。在这种情况下,您可以使用通用Observable类和以下基于原语的类来使字段可观察: 12 | 13 | - [`ObservableBoolean`](https://developer.android.com/reference/android/databinding/ObservableBoolean.html) 14 | - [`ObservableByte`](https://developer.android.com/reference/android/databinding/ObservableByte.html) 15 | - [`ObservableChar`](https://developer.android.com/reference/android/databinding/ObservableChar.html) 16 | - [`ObservableShort`](https://developer.android.com/reference/android/databinding/ObservableShort.html) 17 | - [`ObservableInt`](https://developer.android.com/reference/android/databinding/ObservableInt.html) 18 | - [`ObservableLong`](https://developer.android.com/reference/android/databinding/ObservableLong.html) 19 | - [`ObservableFloat`](https://developer.android.com/reference/android/databinding/ObservableFloat.html) 20 | - [`ObservableDouble`](https://developer.android.com/reference/android/databinding/ObservableDouble.html) 21 | - [`ObservableParcelable`](https://developer.android.com/reference/android/databinding/ObservableParcelable.html) 22 | 23 | 可观察字段个本身就含有单个可观察对象字段的可观察对象。原始版本在访问操作期间避免装箱和拆箱。要使用此机制,请在Java编程语言中创建public final属性或在Kotlin中创建只读属性,如以下示例所示: 24 | 25 | - kotlin 26 | 27 | - ```kotlin 28 | class User { 29 | val firstName = ObservableField() 30 | val lastName = ObservableField() 31 | val age = ObservableInt() 32 | } 33 | ``` 34 | 35 | - java 36 | 37 | - ```java 38 | private static class User { 39 | public final ObservableField firstName = new ObservableField<>(); 40 | public final ObservableField lastName = new ObservableField<>(); 41 | public final ObservableInt age = new ObservableInt(); 42 | } 43 | ``` 44 | 45 | 要访问字段值,请使用get()和set()访问方法,如下所示: 46 | 47 | - kotlin 48 | 49 | ```kotlin 50 | user.firstName = "Google" 51 | val age = user.age 52 | ``` 53 | 54 | - java 55 | 56 | ```java 57 | user.firstName.set("Google"); 58 | int age = user.age.get(); 59 | ``` 60 | 61 | > 注意:Android Studio3.1及以上允许你使用LiveData对象替换可观察的字段,该做法为你的app带来了更多好处,想了解更多的信息,请参考[使用LiveData通知UI有关数据更改的信息](./数据(Data Binding)绑定库——将布局视图绑定到体系结构组件.md#use-livedata-to-notify-the-ui-about-data-changes) 62 | 63 | ## 可观察的集合(collections) 64 | 65 | 一些应用程序使用动态结构来保存数据。可观察集合(Observable collections)允许使用key访问这些结构,当键是引用类型(如String)时,[`ObservableArrayMap`](https://developer.android.com/reference/android/databinding/ObservableArrayMap.html)类很有用,如以下示例所示: 66 | 67 | - kotlin 68 | 69 | - ```kotlin 70 | ObservableArrayMap().apply { 71 | put("firstName", "Google") 72 | put("lastName", "Inc.") 73 | put("age", 17) 74 | } 75 | ``` 76 | 77 | - java 78 | 79 | - ```java 80 | ObservableArrayMap user = new ObservableArrayMap<>(); 81 | user.put("firstName", "Google"); 82 | user.put("lastName", "Inc."); 83 | user.put("age", 17); 84 | ``` 85 | 86 | 在layout文件中,map可以使用string类型的key被绑定: 87 | 88 | ```xml 89 | 90 | 91 | 92 | 93 | … 94 | 98 | 102 | ``` 103 | 104 | [`ObservableArrayList`](https://developer.android.com/reference/android/databinding/ObservableArrayList.html)在key是integer类型的时候很有用,比如: 105 | 106 | - kotlin 107 | 108 | ```kotlin 109 | ObservableArrayList().apply { 110 | add("Google") 111 | add("Inc.") 112 | add(17) 113 | } 114 | ``` 115 | 116 | - java 117 | 118 | ```java 119 | ObservableArrayList user = new ObservableArrayList<>(); 120 | user.add("Google"); 121 | user.add("Inc."); 122 | user.add(17); 123 | ``` 124 | 125 | 在layout文件中,该List可以使用index被访问,就像: 126 | 127 | ```xml 128 | 129 | 130 | 131 | 132 | 133 | … 134 | 138 | 142 | ``` 143 | 144 | ## 可观察的对象(objects) 145 | 146 | 实现[`Observable`](https://developer.android.com/reference/android/databinding/Observable.html)接口的类允许注册希望被观察对象属性改变时被通知的侦听器。 147 | 148 | Observable接口有一个添加和删除侦听器的机制,但您必须决定何时发送通知。为了简化开发,数据绑定库提供了[`BaseObservable`](https://developer.android.com/reference/android/databinding/BaseObservable.html)类,该类实现了侦听器注册机制。实现BaseObservable的数据类负责通知属性何时更改,这是通过将一个[`Bindable`](https://developer.android.com/reference/android/databinding/Bindable.html)注释分配给getter并在setter中调用[`notifyPropertyChanged()`](https://developer.android.com/reference/android/databinding/BaseObservable.html#notifyPropertyChanged(int))方法来完成的,如以下示例所示: 149 | 150 | - kotlin 151 | 152 | - ```kotlin 153 | class User : BaseObservable() { 154 | 155 | @get:Bindable 156 | var firstName: String = "" 157 | set(value) { 158 | field = value 159 | notifyPropertyChanged(BR.firstName) 160 | } 161 | 162 | @get:Bindable 163 | var lastName: String = "" 164 | set(value) { 165 | field = value 166 | notifyPropertyChanged(BR.lastName) 167 | } 168 | } 169 | ``` 170 | 171 | - java 172 | 173 | ```java 174 | private static class User extends BaseObservable { 175 | private String firstName; 176 | private String lastName; 177 | 178 | @Bindable 179 | public String getFirstName() { 180 | return this.firstName; 181 | } 182 | 183 | @Bindable 184 | public String getLastName() { 185 | return this.lastName; 186 | } 187 | 188 | public void setFirstName(String firstName) { 189 | this.firstName = firstName; 190 | notifyPropertyChanged(BR.firstName); 191 | } 192 | 193 | public void setLastName(String lastName) { 194 | this.lastName = lastName; 195 | notifyPropertyChanged(BR.lastName); 196 | } 197 | } 198 | ``` 199 | 200 | 数据绑定在模块包中生成一个名为BR的类,该类包含用于数据绑定的资源的ID, Bindable注释在编译期间在BR类文件中生成一个条目(entry),如果数据类的基类不能被更改,则可以使用[`PropertyChangeRegistry`](https://developer.android.com/reference/android/databinding/PropertyChangeRegistry.html)对象实现Observable接口,以有效地注册和通知侦听器。 201 | 202 | ## 其他资源 203 | 204 | 想了解更多有关数据绑定的内容,请参考: 205 | 206 | ### Samples 207 | 208 | - [Android Data Binding Library samples](https://github.com/googlesamples/android-databinding) 209 | 210 | ### Codelabs 211 | 212 | - [Android Data Binding codelab](https://codelabs.developers.google.com/codelabs/android-databinding) 213 | 214 | ### Blog posts 215 | 216 | - [Data Binding — Lessons Learnt](https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719) -------------------------------------------------------------------------------- /Activity/Interacting-With-Other-Apps/与其他APP交互——接收另一个Activity返回的结果.md: -------------------------------------------------------------------------------- 1 | # 与其他APP交互——接收另一个Activity返回的结果 2 | 3 | [原文(英文)地址](https://developer.android.com/training/basics/intents/result) 4 | 5 | 启动另一项Activity不一定是单向的,您还可以启动另一项Activity并收到其返回的结果,要接收结果,请调用startActivityForResult()(而不是startActivity())。 6 | 7 | 例如,您的应用可以启动相机应用并接收拍摄的照片。或者,您可以启动People应用程序,以便用户选择联系人,您将收到联系人详细信息。 8 | 9 | 当然,响应的Activity必须设计为可返回结果的,它将结果作为另一个Intent对象发送,您的Activity在onActivityResult()回调中接收它。 10 | 11 | > 注意:调用startActivityForResult()时可以使用显式或隐式Intent。在启动您自己的某个Activity以接收结果时,您应该使用显示的Intent来确保您收到预期的结果。 12 | 13 | ## 启动Activity 14 | 15 | 启动需要接收返回值得Activity时使用的Intent对象没有什么特别之处,但是您需要将一个整数参数传递给startActivityForResult()方法。 16 | 17 | 整数参数是标识您的请求的“请求代码”,当您收到返回结果Intent时,回调会提供相同的请求代码,以便您的应用程序可以正确识别结果并确定如何处理它。 18 | 19 | 例如,以下是启动允许用户选择联系人的Activity: 20 | 21 | - kotlin 22 | 23 | ```kotlin 24 | const val PICK_CONTACT_REQUEST = 1 // The request code 25 | ... 26 | private fun pickContact() { 27 | Intent(Intent.ACTION_PICK, Uri.parse("content://contacts")).also { pickContactIntent -> 28 | pickContactIntent.type = Phone.CONTENT_TYPE // Show user only contacts w/ phone numbers 29 | startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST) 30 | } 31 | } 32 | ``` 33 | 34 | - java 35 | 36 | ```java 37 | static final int PICK_CONTACT_REQUEST = 1; // The request code 38 | ... 39 | private void pickContact() { 40 | Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts")); 41 | pickContactIntent.setType(Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers 42 | startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST); 43 | } 44 | ``` 45 | 46 | ## 接收返回的结果 47 | 48 | 当用户完成第二个Activity并返回时,系统会调用您第一个Activity的onActivityResult()方法。此方法包括三个参数: 49 | 50 | - 您传递给startActivityForResult()的请求代码。 51 | - 由第二个Activity指定的结果代码。如果操作成功,则为RESULT_OK;如果用户由于某种原因退出或操作失败,则为RESULT_CANCELED。 52 | - 包含结果数据的Intent。 53 | 54 | 例如,以下是如何处理“选择联系人”Intent的结果: 55 | 56 | - kotlin 57 | 58 | ```kotlin 59 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { 60 | // Check which request we're responding to 61 | if (requestCode == PICK_CONTACT_REQUEST) { 62 | // Make sure the request was successful 63 | if (resultCode == Activity.RESULT_OK) { 64 | // The user picked a contact. 65 | // The Intent's data Uri identifies which contact was selected. 66 | 67 | // Do something with the contact here (bigger example below) 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | - java 74 | 75 | ```java 76 | @Override 77 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 78 | // Check which request we're responding to 79 | if (requestCode == PICK_CONTACT_REQUEST) { 80 | // Make sure the request was successful 81 | if (resultCode == RESULT_OK) { 82 | // The user picked a contact. 83 | // The Intent's data Uri identifies which contact was selected. 84 | 85 | // Do something with the contact here (bigger example below) 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | 在此示例中,Android的Contacts或People应用程序返回的结果Intent提供了一个内容Uri,用于标识用户选择的联系人。 92 | 93 | 为了成功处理结果,您必须了解Intent的结果格式。当返回结果的Activity是您自己的Activity之一时,这样做很容易。 Android平台附带的应用程序提供了自己的API,您可以依赖这些API来获取特定的结果数据。例如,People应用程序始终返回带有标识所选联系人的内容URI的结果,而Camera应用程序在“data”extra中返回一个 `Bitmap`(请参阅 [Capturing Photos](https://developer.android.com/training/camera/index.html))。 94 | 95 | ### 菜单一:发送返回数据的方法 96 | 97 | 主要是通过setResult()方法。 98 | 99 | ```kotlin 100 | //数据是使用Intent返回 101 | Intent intent = new Intent(); 102 | //把返回数据存入Intent 103 | intent.putExtra("result", "My name is linjiqin"); 104 | //设置返回数据 105 | OtherActivity.this.setResult(RESULT_OK, intent); 106 | ``` 107 | 108 | ### 彩蛋二:读取联系人数据 109 | 110 | 上面展示了如何从People应用程序获取结果的代码,但是没有详细介绍如何从结果中实际读取数据,因为它需要有关ContentProvider的更多理论。但是,如果您感到好奇,可以使用以下代码查看如何查询结果数据以获取所选联系人的电话号码: 111 | 112 | - kotlin 113 | 114 | ```kotlin 115 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { 116 | // Check which request it is that we're responding to 117 | if (requestCode == PICK_CONTACT_REQUEST) { 118 | // Make sure the request was successful 119 | if (resultCode == Activity.RESULT_OK) { 120 | // We only need the NUMBER column, because there will be only one row in the result 121 | val projection: Array = arrayOf(Phone.NUMBER) 122 | 123 | // Get the URI that points to the selected contact 124 | data.data?.also { contactUri -> 125 | // Perform the query on the contact to get the NUMBER column 126 | // We don't need a selection or sort order (there's only one result for this URI) 127 | // CAUTION: The query() method should be called from a separate thread to avoid 128 | // blocking your app's UI thread. (For simplicity of the sample, this code doesn't 129 | // do that.) 130 | // Consider using CursorLoader to perform the query. 131 | contentResolver.query(contactUri, projection, null, null, null)?.apply { 132 | moveToFirst() 133 | 134 | // Retrieve the phone number from the NUMBER column 135 | val column: Int = getColumnIndex(Phone.NUMBER) 136 | val number: String? = getString(column) 137 | 138 | // Do something with the phone number... 139 | } 140 | } 141 | } 142 | } 143 | } 144 | ``` 145 | 146 | - java 147 | 148 | ```java 149 | @Override 150 | protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) { 151 | // Check which request it is that we're responding to 152 | if (requestCode == PICK_CONTACT_REQUEST) { 153 | // Make sure the request was successful 154 | if (resultCode == RESULT_OK) { 155 | // Get the URI that points to the selected contact 156 | Uri contactUri = resultIntent.getData(); 157 | // We only need the NUMBER column, because there will be only one row in the result 158 | String[] projection = {Phone.NUMBER}; 159 | 160 | // Perform the query on the contact to get the NUMBER column 161 | // We don't need a selection or sort order (there's only one result for the given URI) 162 | // CAUTION: The query() method should be called from a separate thread to avoid blocking 163 | // your app's UI thread. (For simplicity of the sample, this code doesn't do that.) 164 | // Consider using CursorLoader to perform the query. 165 | Cursor cursor = getContentResolver() 166 | .query(contactUri, projection, null, null, null); 167 | cursor.moveToFirst(); 168 | 169 | // Retrieve the phone number from the NUMBER column 170 | int column = cursor.getColumnIndex(Phone.NUMBER); 171 | String number = cursor.getString(column); 172 | 173 | // Do something with the phone number... 174 | } 175 | } 176 | } 177 | ``` 178 | 179 | > 注意:在Android 2.3(API级别9)之前,对联系人ContentProvider执行查询(如上所示)需要您的应用程序声明READ_CONTACTS权限(请参阅 [Security and Permissions](https://developer.android.com/guide/topics/security/security.html))。但是,从Android 2.3开始,Contacts / People应用程序授予您的应用程序临时权限,以便在返回结果时从联系人提供程序中读取数据。临时权限仅适用于请求的特定联系人,因此除非您声明READ_CONTACTS权限,否则无法查询intent的Uri指定的联系人之外的联系人。 180 | 181 | 关于更多与本文档有关的内容,请参考: 182 | 183 | - [Sharing Simple Data](https://developer.android.com/training/sharing/index.html) 184 | - [Sharing Files](https://developer.android.com/training/secure-file-sharing/index.html) -------------------------------------------------------------------------------- /Activity/AppShortCuts/管理快捷方式.md: -------------------------------------------------------------------------------- 1 | # 管理快捷方式 2 | 3 | [原文(英文)地址](https://developer.android.com/guide/topics/ui/shortcuts/managing-shortcuts) 4 | 5 | 创建快捷方式后,您可能需要在应用的生命周期内管理它们。例如,您可能希望通过确定用户使用快捷方式完成特定操作的频率来优化您的应用。在另一种情况下,您可能决定禁用固定快捷方式,以防止您的应用执行过时或丢失的操作。本文档介绍了这些管理快捷方式的方法,还有几种其他常用的管理快捷方式的方法。 6 | 7 | ## 快捷方式行为(behavior) 8 | 9 | 以下部分包含有关快捷方式行为的常见内容,包括可见性(visibility),显示顺序(display order)和排名(ranks)。 10 | 11 | ### 快捷方式可见性 12 | 13 | > 重要安全说明:所有快捷方式信息都存储在凭据加密存储( [credential encrypted storage](https://developer.android.com/training/articles/direct-boot.html))中,因此您的应用在解锁设备之前无法访问用户的快捷方式。 14 | 15 | 当用户执行特定手势时,静态快捷方式和动态快捷方式将显示在受支持的启动器中。在当前支持的启动器上,手势是长按应用程序的启动器图标,但实际手势可能与其他启动器应用程序不同。 16 | 17 | [LauncherApps](https://developer.android.com/reference/android/content/pm/LauncherApps.html)类为启动器应用程序提供API以访问快捷方式。 18 | 19 | 由于固定的快捷方式出现在启动器本身中,因此它们始终可见。仅在以下情况下,才会从启动器中删除固定的快捷方式: 20 | 21 | - 用户将其删除。 22 | - 与快捷方式关联的应用程序已卸载。 23 | - 用户通过通过 设置>应用和通知,然后选择应用,然后按存储>清除存储来清除应用的数据。 24 | 25 | ### 快捷方式的显示顺序 26 | 27 | 当一个启动器展示app的快捷方式时,他们应该按照以下顺序进行展示: 28 | 29 | 1:静态快捷方式:isDeclaredInManifest()方法返回True的快捷方式 30 | 31 | 2:动态快捷方式:ShortcutInfo.isDynamic()方法返回true的快捷方式 32 | 33 | 对于每一种类型的快捷方式(静态和动态),快捷方式按照ShortcutInfo.getRank()获得的Ranks(排名)信息来进行排序。 34 | 35 | 对于每一个快捷方式,Ranks是一个非负的、顺序(sqquential)的整数,你可以通过调用[updateShortcuts(List)](https://developer.android.com/reference/android/content/pm/ShortcutManager.html#updateShortcuts(java.util.List%3Candroid.content.pm.ShortcutInfo%3E))、[addDynamicShortcuts(List)](https://developer.android.com/reference/android/content/pm/ShortcutManager.html#addDynamicShortcuts(java.util.List%3Candroid.content.pm.ShortcutInfo%3E)),或者[setDynamicShortcuts(List)](https://developer.android.com/reference/android/content/pm/ShortcutManager.html#setDynamicShortcuts(java.util.List%3Candroid.content.pm.ShortcutInfo%3E))方法去更新快捷方式的排名。 36 | 37 | > 注意:每一种类型的快捷方式(静态和动态),Ranks是会自动自动调整的。比如,有三个动态快捷方式,其ranks值分别是0,1,2,添加另一个ranks值为1的动态快捷方式之后就需要当前第二个位置的动态快捷方式就应该被代替。结构,第三和第四个快捷方式移动到快捷方式列表的底部,其排名分别变为2和3。 38 | 39 | ## 管理多个intent和activity 40 | 41 | 如果你想要当用户点击你应用的快捷方式时执行多个动作,你可以配置连续触发多个activity,你可以通过分配多个intent,从一个activity启动另一个activity,或者通过设置intent flag来完成此操作,具体取决于快捷方式的类型。 42 | 43 | ### 分配多个intent 44 | 45 | 当你使用[ShortcutInfo.Builder](https://developer.android.com/reference/android/content/pm/ShortcutInfo.Builder.html)创建快捷方式时,你可以使用[setIntents()](https://developer.android.com/reference/android/content/pm/ShortcutInfo.Builder.html#setIntents(android.content.Intent[]))而不是[setIntent()](https://developer.android.com/reference/android/content/pm/ShortcutInfo.Builder.html#setIntent(android.content.Intent))。通过调用setIntents(),当用户选择快捷方式时,您可以在应用程序中启动多个Activity,将列表中的除了最后一个Activity之外的所有activity放在回退栈([back stack](https://developer.android.com/guide/components/tasks-and-back-stack.html))中。 46 | 47 | 如果用户想要点击设备的后腿按钮,他会看到另一个activity而不是直接返回设备的启动器界面。 48 | 49 | > 注意:当用户点击了一个快捷方式然后点击后退按钮之后,你的app会启动从资源文件中列出的列表的倒数第二个Intent对应的activity。重复按下后退按钮后,系统将会继续按照此逻辑启动后面的activity(如果有的话,直到用户清除快捷方式创建的回退栈。当用户下一次按下后退按钮时,系统将会导航回启动器。 50 | 51 | ### 从另一个activity启动本activity 52 | 53 | 静态快捷方式不能有自定义的Intent flag。第一个静态类型快捷方式的Intent flag始终是 54 | 55 | Intent.FLAG_ACTIVITY_NEW)和Intent.TASK FLAG_ACTIVITY_CLEAR_TASK.这意味着,当应用程序已在运行时,启动静态快捷方式时,应用程序中的所有现有Activity都将被销毁。如果不希望出现这种情况,可以使用trampoline(蹦床?) Activity,或者Activity.onCreate(Bundle)中启动另一个不可见的Activity,然后调用Activity.finish(): 56 | 57 | - 在AndroidManifest.xml文件中,trampoline Activity应该包含属性赋值android:taskAffinity =“”。 58 | - 在快捷方式的资源文件中,静态快捷方式中的Intent应引用trampoline Activity。 59 | 60 | 有关trampoline Activity的更多信息,请阅读[Activity的生命周期](../Activity的生命周期.md))中从另一个Activity启动一个Activity部分。 61 | 62 | ### 设置Intent flag 63 | 64 | 动态快捷方式可以设置任意Intent flag。您最好指定Intent.FLAG_ACTIVITY_CLEAR_TASK以及其他flag。否则,如果您在应用程序运行时尝试启动其他Task,则可能不会显示目标Activity。 65 | 66 | 要了解有关Task和Intent flag的更多信息,请阅读[理解Task和回退栈](../理解Task和回退栈.md)。 67 | 68 | ## 更新快捷方式 69 | 70 | 每一个应用程序启动器图标都可以最多包含[getMaxShortcutCountPerActivity()](https://developer.android.com/reference/android/content/pm/ShortcutManager.html#getMaxShortcutCountPerActivity())个(静态的和/或动态的)快捷方式。但是,应用程序可以创建的固定快捷方式的数量没有限制。 71 | 72 | 对于固定动态快捷方式,即使发布者将其作为动态快捷方式删除,固定的快捷方式仍然可见并且可以启动。这允许应用程序具有多于getMaxShortcutCountPerActivity()个数量的快捷方式。 73 | 74 | 举个例子,比如getMaxShortcutCountPerActivity()返回了4: 75 | 76 | - 一个社交类app创建了4个动态快捷方式,表示4个最常用的会话(c1,c2,c3,c4) 77 | 78 | - 用户固定了这4个快捷方式 79 | 80 | - 之后,用户又开启了3个新的会话(c5,c6和c7),所以发布商应用程序重新发布其动态快捷方式。新的动态快捷键列表是:c4,c5,c6,c7。 81 | 82 | 该应用程序必须删除c1,c2和c3,因为它无法显示四个以上的动态快捷方式。但是,c1,c2和c3仍然是用户可以访问和启动的固定快捷方式。 83 | 84 | 用户现在可以访问总共七个链接到发布者应用中的Activity的快捷方式。这是因为总数包括最大快捷方式数和三个固定的快捷方式。 85 | 86 | - 该应用程序可以使用updateShortcuts(List)更新任何现有的七个快捷方式。例如,您可以在聊天对象的的头像发生更改时更新这组快捷方式。 87 | 88 | - addDynamicShortcuts(List)和setDynamicShortcuts(List)方法也可用于更新具有相同ID的现有快捷方式。但是,它们不能用于更新非动态快捷方式和固定快捷方式,因为这两种方法会尝试将给定的快捷方式列表转换为动态快捷方式。 89 | 90 | 要详细了解应用程序快捷方式指南(包括更新快捷方式),请参阅 [Best practices](https://developer.android.com/guide/topics/ui/shortcuts/best-practices). 91 | 92 | ### 处理系统局部设置更改 93 | 94 | app应该在收到Intent.ACTION_LOCALE_CHANGD广播时更新动态快捷方式和固定快捷方式,因为该广播标志着系统局部设置已更改。 95 | 96 | ## 跟踪(Track)快捷方式用法 97 | 98 | 要确定什么情况应该出现静态快捷方式、什么情况应该出现动态快捷方式,启动器会检查快捷方式的历史激活记录。通过调用reportShortcutUsed()方法并在发生以下任一事件时向其传递快捷方式的ID,您可以跟踪用户何时在应用程序中完成特定操作: 99 | 100 | - 用户选择具有给定ID的快捷方式。 101 | - 在应用程序内,用户手动完成与同一快捷方式对应的操作。 102 | 103 | ## 禁用快捷方式 104 | 105 | 由于您的应用及应用的用户可以将快捷方式固定到设备的启动器,因此这些固定的快捷方式可能会引导用户执行应用中已过期或不再存在的操作。要管理这种情况,您可以通过调用disableShortcuts()来禁用您不希望用户选择的快捷方式,该操作会从静态和动态快捷方式列表中删除指定的快捷方式,并禁用这些快捷方式的任何被固定的副本。您还可以使用此方法的重载版本,该方法接受CharSequence作为自定义错误消息。当用户尝试启动任何禁用的快捷方式时,将显示该错误消息。 106 | 107 | > 注意:如果在更新应用程序时删除了您应用程序的某些静态快捷方式,系统会自动禁用这些快捷方式。 108 | 109 | ## 限速(*rate limiting*) 110 | 111 | 使用setDynamicShortcuts(),addDynamicShortcuts()或updateShortcuts()方法时,请记住,您可能只能在一个没有任何前台Service或者前台Activity的后台APP中调用这些方法特定次。调用这些方法被调用次数的限制称为速率限制(*rate limiting*)。此功能用于防止ShortcutManager过度消耗设备资源。 112 | 113 | 当速率限制处于生效状态时,isRateLimitingActive()将返回true。但是,在某些事件期间会重置速率限制,因此即使是后台应用程序也可以调用ShortcutManager的方法,直到再次达到速率限制。这些事件包括以下内容: 114 | 115 | - 一个应用程序来到前台。 116 | - 系统局部设置发生更改。 117 | - 用户在一个通知(notification)中执行内联回复([inline reply](https://developer.android.com/guide/topics/ui/notifiers/notifications.html#direct))操作。 118 | 119 | 如果在开发或测试期间遇到速率限制,您可以从设备的设置中选择开发人员选项>重置ShortcutManager速率限制,或者您可以在adb中输入以下命令: 120 | 121 | ``` 122 | $ adb shell cmd shortcut reset-throttling [ --user your-user-id ] 123 | ``` 124 | 125 | ## 备份和还原 126 | 127 | 通过在应用程序的清单文件中包含android:allowBackup =“true”属性,您可以允许用户在更改设备时对应用程序执行备份和还原操作。如果您允许备份和还原,请记住以下有关应用程序快捷方式的要点: 128 | 129 | - 静态快捷方式会自动重新发布,但只有在用户在新设备上重新安装应用程序后才能重新发布。 130 | - 不会备份动态快捷方式,因此您必须在应用中包含特定逻辑,以便在用户在新设备上打开您的应用时重新发布它们。 131 | - 固定快捷方式会自动恢复到设备的启动器,但系统不会备份与固定快捷方式关联的图标。因此,您应该在应用中保存固定快捷方式的图像,以便在新设备上轻松恢复它们。 132 | 133 | 以下代码段显示了如何更好地恢复应用的动态快捷方式,以及如何检查应用的固定快捷方式是否已保留: 134 | 135 | - kotlin 136 | 137 | ```kotlin 138 | class MyMainActivity : Activity() { 139 | override fun onCreate(savedInstanceState: Bundle?) { 140 | super.onCreate(savedInstanceState) 141 | val shortcutManager = getSystemService(ShortcutManager::class.java) 142 | 143 | if (shortcutManager!!.dynamicShortcuts.size == 0) { 144 | // Application restored. Need to re-publish dynamic shortcuts. 145 | if (shortcutManager.pinnedShortcuts.size > 0) { 146 | // Pinned shortcuts have been restored. Use 147 | // updateShortcuts() to make sure they contain 148 | // up-to-date information. 149 | } 150 | 151 | } 152 | } 153 | // ... 154 | } 155 | ``` 156 | 157 | - java 158 | 159 | ```java 160 | public class MainActivity extends Activity { 161 | public void onCreate(Bundle savedInstanceState) { 162 | super.onCreate(savedInstanceState); 163 | ShortcutManager shortcutManager = 164 | getSystemService(ShortcutManager.class); 165 | 166 | if (shortcutManager.getDynamicShortcuts().size() == 0) { 167 | // Application restored. Need to re-publish dynamic shortcuts. 168 | if (shortcutManager.getPinnedShortcuts().size() > 0) { 169 | // Pinned shortcuts have been restored. Use 170 | // updateShortcuts() to make sure they contain 171 | // up-to-date information. 172 | } 173 | } 174 | } 175 | // ... 176 | } 177 | ``` 178 | 179 | ## 其他资源 180 | 181 | [Android AppShortcuts](https://github.com/googlesamples/android-AppShortcuts/)示例项目展示了本文档讨论的技术点的一些实现。 --------------------------------------------------------------------------------