标签归档:翻译

android中Actionbar详解

1、什么是Action Bar
Action Bar被认为是新版Android系统中最重要的交互元素,在程序运行中一直置于顶部,主要起到的作用在于:
1)突出显示一些重要操作(如“最新”、“搜索”等)
2)在程序中保持统一的页面导航和切换方式
3)将使用频率低的功能放在Action overflow中,节省页面空间
4)一个固定区域显示程序标示

2、Action Bar分成四个区域
App Icon:可显示软件icon,也可用其他图标代替。当软件不在最高级页面时,图标左侧会显示一个左箭头,用户可以通过这个箭头向上导航。
视图切换:如果你的应用要在不同的View中显示数据,这部分允许用户来切换View。一般的作法是用一个drop-down菜单或者是Tab Controls。如果只有一个界面,那这里可以显示App Title或者更长点的商标信息
Action Buttons:这个放最重要的软件功能,放不下的按钮就自动进入Action overflow了。
Action overflow:把不常用的Actions移到Action overflow

3、屏幕旋转及不同分辨率适配
写一个应用时一个重要的UI问题就是如何适应屏幕旋转和不同尺寸的屏幕。你可以通过使用split action bars来适应这种变化,就是把action bar内容拆分到不同的bars里,拆分后一般包含三个部分:
Main action bar:用户可以通过main action
bar导航至上一级,因此这个操作条是必须的;
Top bar:当页面上有不同的内容视图时,可在这个条上使用TAB或spinner下拉菜单的形式切换;
Bottom bar:要展现更多操作和功能,在页面最下端排列

4、Contextual Action Bar (CAB)

“上下文操作栏”
(contextual action bar,CAB)
是一个浮于操作栏上的临时操作栏,用来放置一些特定的子任务。“上下文操作栏”一般在项目选择和文字选择时出现。


浏览器和
Gmail 应用中的上下文操作栏

长按可选择的内容,进入选择模式,显示上下文操作栏。

此时用户可以:

  • 通过触摸选择项目。
  • 在上下文操作栏中选择操作,并应用于所有已选项目。之后上下文操作栏自动消失。
  • 通过导航栏的“返回”按钮关闭上下文操作栏,也可以通过点击上下文操作栏的选择图标关闭它。关闭上下文操作栏的同时要取消所有的选择。

当您让用户长按来选择项目时,需要使用上下文操作栏。您可以控制上下文操作栏中的图标,使用户可以执行不同的操作。

5、ActionBar包含元素
1)Tabs
如果希望用户经常在几个部分间切换,可以使用Tabs。有两种Tabs:固定的(fixed)跟可滑动的(scrollable)
Scrollable:经常占据整个Bar的宽度,当前的活动View在中间位置,因此需要在一个专用的Bar里。可以自己水平的scroll,来显示更多其他的view。使用情况:如果有很多的Views或者你不确定有多少Views,因为可能是动态添加的。ScrollableTabs应该总是允许用户通过左右Swipe来切换Views。

Fixed Tabs:将所有标签显示在屏幕上,当方向变化时,它可能会被移动到Top bar位置去。

2)Spinners下拉框
官方给出使用spinner而不用tab的情况:
当不希望tab占据太多页面竖直方向上的空间
当用户认为不需要经常在视图之间切换时

3)Action buttons
要分清楚哪些Action是经常使用的,并根据这个来安排它们的位置。应该显示最常用的Actions,把其他不是常用的放到overflow里。
如果一个Action在当前不被使用,把它隐藏掉,而不是显示为不能用。
使用FIT表来分辨优先级。如果FIT中有一个适用,就把它放到Action bar,否则就放到Action overflow里。
F – Frequent 高频
当用户在这个页面时,是否10次中至少有7次会使用这个按钮?
用户是否通常要连续使用很多次?
如果每次使用该功能都要多一步操作,会不会很繁琐?
I– Important重要
你是否希望每个用户都能发现这个功能,或者因为它很酷或者是你的卖点?
你是否认为当需要用到这个按钮时,应该很容易触及?
T –Typical典型
在相似的软件中,这个功能是不是通常是的最重要操作?
在上下文环境下,如果这个操作按键被埋在overflow中,用户会不会惊讶?
下面的链接里包含一些系统自带操作的图标素材,针对不同屏幕分辨率,可以使用在Holo
Light和Holo Dark主题中;
另外包括图标的Adobe Illustrator源文件可供自行修改。
4)Action overflow
Action overflow中存放并不会频繁用到的操作。按照官方网页上的说法,“Overflow图标仅显示在没有MENU硬按键的手机上,而对于有MENU键的手机,
overflow图标是不显示的,当用户点击MENU按键时弹出。”这样的说法比较蹊跷,似乎和Google敦促手机厂商及软件开发商取消MENU的行为不相匹配。
6、Action bar上可以放下多少操作按钮?

如果操作按钮和软件标题等放在一排,放按钮的空间只能最多占用一半空间,如果按钮采用屏幕底部的整行action bar则可以使用整个屏幕宽度。

屏幕宽度值Density-independent Pixels(dp)决定可以放置的图标数:
少于360 dp = 2个图标
360-499 dp = 3个图标
500-599 dp = 4个图标
多于600 dp = 5个图标

本文参考:http://developer.android.com/design/patterns/actionbar.html

欢迎转载,但请注明出处与作者

出处:http://blog.sina.com.cn/staratsky

作者:流星

 

Android Fragments的使用

Fragment 表现 Activity 中用UI的一个行为或者一部分.可以组合多个fragment放在一个单独的activity中来创建一个多界面区域的UI,并可以在多个activity里重用某一个fragment.把fragment想象成一个activity的模块化区域, 有它自己的生命周期, 接收属于它的输入事件,并且可以在activity运行期间添加和删除.

Fragment 必须总是被嵌入到一个activity中, 它们的生命周期直接被其所属的宿主activity的生命周期影响.例如, 当activity被暂停,那么在其中的所有fragment也被暂停; 当activity被销毁,所有隶属于它的fragment也被销毁. 然而,当一个activity正在运行时(处于resumed状态),我们可以独立地操作每一个fragment, 比如添加或删除它们. 当处理这样一个fragment事务时,也可以将它添加到activity所管理的back stack — 每一个activity中的backstack实体都是一个发生过的fragment事务的记录. back stack允许用户通过按下 BACK按键从一个fragment事务后退(往后导航).

将一个fragment作为activity布局的一部分添加进来时, 它处在activity的viewhierarchy中的ViewGroup中,并且定义有它自己的view布局.通过在activity的布局文件中声明fragment来插入一个fragment到你的activity布局中,或者可以写代码将它添加到一个已存在的ViewGroup.然而, fragment并不一定必须是activity布局的一部分;也可以将一个fragment作为activity的隐藏的后台工作者.

本文档描述了如何使用fragment创建你的应用程序, 包括:当被添加到activity的back stack后,fragment如何维护他们的状态.在activity中,与activity和其他fragment共享事件.构建到activity的actionbar.以及更多内容.

设计哲学


Android在3.0中引入了fragments的概念,主要目的是用在大屏幕设备上–例如平板电脑上,支持更加动态和灵活的UI设计.平板电脑的屏幕要比手机的大得多,有更多的空间来放更多的UI组件,并且这些组件之间会产生更多的交互.Fragment允许这样的一种设计,而不需要你亲自来管理viewhierarchy的复杂变化. 通过将activity的布局分散到fragment中, 你可以在运行时修改activity的外观,并在由activity管理的back stack中保存那些变化.

例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输入事件. 因此, 取代使用一个activity来选择一篇文章,而另一个activity来阅读文章 的方式,用户可以在相同的activity中选择一篇文章并且阅读, 如图所示:

fragment在你的应用中应当是一个模块化和可重用的组件.即,因为fragment定义了它自己的布局, 以及通过使用它自己的生命周期回调方法定义了它自己的行为,你可以将fragment包含到多个activity中. 这点特别重要, 因为这允许你将你的用户体验适配到不同的屏幕尺寸.举个例子,你可能会仅当在屏幕尺寸足够大时,在一个activity中包含多个fragment,并且,当不属于这种情况时,会启动另一个单独的,使用不同fragment的activity.

继续之前那个新闻的例子 — 当运行在一个特别大的屏幕时(例如平板电脑),app可以在Activity A中嵌入2个fragment.然而,在一个正常尺寸的屏幕(例如手机)上,没有足够的空间同时供2个fragment用, 因此, Activity A会仅包含文章列表的fragment, 而当用户选择一篇文章时, 它会启动Activity B,它包含阅读文章的fragment. 因此, 应用可以同时支持图1中的2种设计模式.

创建Fragment


    要创建一个fragment, 必须创建一个 Fragment 的子类 (或者继承自一个已存在的它的子类). Fragment类的代码看起来很像 Activity .它包含了和activity类似的回调方法, 例如 onCreate(), onStart(),onPause, 以及 onStop(). 事实上, 如果你准备将一个现成的Android应用转换到使用fragment,你可能只需简单的将代码从你的activity的回调函数分别移动到你的fragment的回调方法.

    通常, 应当至少实现如下的生命周期方法:

 

 

  • onCreate()
    当创建fragment时, 系统调用此方法.
    在实现代码中,应当初始化想要在fragment中保持的必要组件, 当fragment被暂停或者停止后可以恢复.
  • onCreateView()
    fragment第一次绘制它的用户界面的时候, 系统会调用此方法. 为了绘制fragment的UI,此方法必须返回一个View, 这个view是你的fragment布局的根view. 如果fragment不提供UI, 可以返回null.
  • onPause()
    用户将要离开fragment时,系统调用这个方法作为第一个指示(然而它不总是意味着fragment将被销毁.) 在当前用户会话结束之前,通常应当在这里提交任何应该持久化的变化(因为用户有可能不会返回).

    大多数应用应当为每一个fragment实现至少这3个方法,但是还有一些其他回调方法你也应当用来去处理fragment生命周期的各种阶段.全部的生命周期回调方法将会在后面章节 Handlingthe Fragment Lifecycle 中讨论.

    除了继承基类 Fragment , 还有一些子类你可能会继承:

 

  • DialogFragment
    显示一个浮动的对话框.
    用这个类来创建一个对话框,是使用在Activity类的对话框工具方法之外的一个好的选择,
    因为你可以将一个fragment对话框合并到activity管理的fragment back stack中,允许用户返回到一个之前曾被摒弃的fragment.
  • ListFragment
    显示一个由一个adapter(例如 SimpleCursorAdapter)管理的项目的列表, 类似于ListActivity.
    它提供一些方法来管理一个list view, 例如 onListItemClick()回调来处理点击事件.
  • PreferenceFragment
    显示一个 Preference对象的层次结构的列表, 类似于PreferenceActivity.
    这在为你的应用创建一个”设置”activity时有用处.

添加一个用户界面


fragment通常用来作为一个activity的用户界面的一部分,并将它的layout提供给activity.为了给一个fragment提供一个layout,你必须实现 onCreateView()回调方法, 当到了fragment绘制它自己的layout的时候,Android系统调用它.你的此方法的实现代码必须返回一个你的fragment的layout的根view.

       注意: 如果你的fragment是ListFragment的子类,它的默认实现是返回从onCreateView()返回一个ListView,所以一般情况下不必实现它.

从onCreateView()返回的View, 也可以从一个xmllayout资源文件中读取并生成. 为了帮助你这么做, onCreateView() 提供了一个LayoutInflater 对象.

举个例子, 这里有一个Fragment的子类, 从文件 example_fragment.xml 加载了一个layout:

public static class ExampleFragment extends Fragment { 
         @Override 
         public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
             // Inflate the layout for this fragment 
             return inflater.inflate(R.layout.example_fragment, container, false); 
         } 
     }

 

传入 onCreateView() 的 container 参数是你的fragmentlayout将被插入的父ViewGroup(来自activity的layout).savedInstanceState 参数是一个Bundle, 如果fragment是被恢复的,它提供关于fragment的之前的实例的数据,

inflate() 方法有3个参数:

 

  • 想要加载的layout的resource ID.
  • 加载的layout的父ViewGroup.
    传入container是很重要的, 目的是为了让系统接受所要加载的layout的根view的layout参数,
    由它将挂靠的父view指定.
  • 布尔值指示在加载期间, 展开的layout是否应当附着到ViewGroup (第二个参数).
    (在这个例子中, 指定了false, 因为系统已经把展开的layout插入到container –传入true会在最后的layout中创建一个多余的view group.)

将fragment添加到activity
通常地, fragment为宿主activity提供UI的一部分, 被作为activity的整个viewhierarchy的一部分被嵌入. 有2种方法你可以添加一个fragment到activitylayout:

在activity的layout文件中声明fragment
你可以像为View一样, 为fragment指定layout属性.
例子是一个有2个fragment的activity:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
     <fragment android:name="com.example.news.ArticleListFragment"
             android:id="@+id/list"
             android:layout_weight="1"
             android:layout_width="0dp"
             android:layout_height="match_parent" />
     <fragment android:name="com.example.news.ArticleReaderFragment"
             android:id="@+id/viewer"
             android:layout_weight="2"
             android:layout_width="0dp"
             android:layout_height="match_parent" />
</LinearLayout>

<fragment> 中的 android:name属性指定了在layout中实例化的Fragment类.

当系统创建这个activity layout时,它实例化每一个在layout中指定的fragment,并调用每一个上的onCreateView()方法,来获取每一个fragment的layout.系统将从fragment返回的 View直接插入到<fragment>元素所在的地方.

        注意: 每一个fragment都需要一个唯一的标识,如果activity重启,系统可以用来恢复fragment(并且你也可以用来捕获fragment来处理事务,例如移除它.)

有3种方法来为一个fragment提供一个标识:

 

  • 为 android:id 属性提供一个唯一ID.
  • 为 android:tag 属性提供一个唯一字符串.
  • 如果以上2个你都没有提供, 系统使用容器view的ID.

撰写代码将fragment添加到一个已存在的ViewGroup.
当activity运行的任何时候, 都可以将fragment添加到activitylayout.只需简单的指定一个需要放置fragment的ViewGroup.为了在你的activity中操作fragment事务(例如添加,移除,或代替一个fragment),必须使用来自FragmentTransaction 的API.

可以按如下方法,从你的Activity取得一个 FragmentTransaction 的实例:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

然后你可以使用 add() 方法添加一个fragment, 指定要添加的fragment, 和要插入的view.

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();

 

add()的第一个参数是fragment要放入的ViewGroup, 由resource ID指定,第二个参数是需要添加的fragment.一旦用FragmentTransaction做了改变,为了使改变生效,必须调用commit().

添加一个无UI的fragment

之前的例子展示了对UI的支持, 如何将一个fragment添加到activity. 然而,也可以使用fragment来为activity提供后台行为而不用展现额外的UI.

要添加一个无UI的fragment, 需要从activity使用 add(Fragment, String) 来添加fragment (为fragment提供一个唯一的字符串”tag”, 而不是一个view ID).这么做添加了fragment,但因为它没有关联到一个activity layout中的一个view, 所以不会接收到onCreateView()调用.因此不必实现此方法.

为fragment提供一个字符串tag并不是专门针对无UI的fragment的 –也可以提供字符串tag给有UI的fragment – 但是如果fragment没有UI,那么这个tag是仅有的标识它的途径.如果随后你想从activity获取这个fragment, 需要使用 findFragmentByTag().

管理Fragment


要在activity中管理fragment,需要使用FragmentManager. 通过调用activity的getFragmentManager()取得它的实例.

可以通过FragmentManager做一些事情, 包括:

 

 

  • 使用findFragmentById() (用于在activitylayout中提供一个UI的fragment)或findFragmentByTag()(适用于有或没有UI的fragment)获取activity中存在的fragment
  • 将fragment从后台堆栈中弹出, 使用 popBackStack() (模拟用户按下BACK 命令).
  • 使用addOnBackStackChangeListener()注册一个监听后台堆栈变化的listener.

处理Fragment事务


关于在activity中使用fragment的很强的一个特性是:根据用户的交互情况,对fragment进行添加,移除,替换,以及执行其他动作.提交给activity的每一套变化被称为一个事务,可以使用在 FragmentTransaction 中的 API 处理.我们也可以保存每一个事务到一个activity管理的backstack,允许用户经由fragment的变化往回导航(类似于通过activity往后导航).

从 FragmentManager 获得一个FragmentTransaction的实例 :

FragmentManager fragmentManager = getFragmentManager(); 
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

每一个事务都是同时要执行的一套变化.可以在一个给定的事务中设置你想执行的所有变化,使用诸如 add(), remove(),和 replace().然后, 要给activity应用事务, 必须调用 commit().
在调用commit()之前, 你可能想调用 addToBackStack(),将事务添加到一个fragment事务的backstack. 这个back stack由activity管理, 并允许用户通过按下 BACK按键返回到前一个fragment状态.

举个例子, 这里是如何将一个fragment替换为另一个, 并在后台堆栈中保留之前的状态:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();

在这个例子中, newFragment替换了当前layout容器中的由R.id.fragment_container标识的fragment.通过调用addToBackStack(), replace事务被保存到back stack,因此用户可以回退事务,并通过按下BACK按键带回前一个fragment.

如果添加多个变化到事务(例如add()或remove())并调用addToBackStack(),然后在你调用commit()之前的所有应用的变化会被作为一个单个事务添加到后台堆栈, BACK按键会将它们一起回退.

添加变化到 FragmentTransaction的顺序不重要, 除以下例外:

 

 

  • 必须最后调用 commit().
  • 如果添加多个fragment到同一个容器, 那么添加的顺序决定了它们在view hierarchy中显示的顺序.

 

当执行一个移除fragment的事务时, 如果没有调用 addToBackStack(), 那么当事务提交后,那个fragment会被销毁,并且用户不能导航回到它. 有鉴于此, 当移除一个fragment时,如果调用了addToBackStack(), 那么fragment会被停止, 如果用户导航回来,它将会被恢复.

提示: 对于每一个fragment事务, 你可以应用一个事务动画,通过在提交事务之前调用setTransition()实现.

调用 commit() 并不立即执行事务.恰恰相反, 它将事务安排排期, 一旦准备好,就在activity的UI线程上运行(主线程).如果有必要, 无论如何, 你可以从你的UI线程调用executePendingTransactions()来立即执行由commit()提交的事务. 但这么做通常不必要,除非事务是其他线程中的job的一个从属.

       警告:你只能在activity保存它的状态(当用户离开activity)之前使用commit()提交事务.

如果你试图在那个点之后提交, 会抛出一个异常.这是因为如果activity需要被恢复,提交之后的状态可能会丢失.对于你觉得可以丢失提交的状况, 使用 commitAllowingStateLoss().

与Activity通信


尽管Fragment被实现为一个独立于Activity的对象,并且可以在多个activity中使用,但一个给定的fragment实例是直接绑定到包含它的activity的. 特别的,fragment可以使用 getActivity() 访问Activity实例, 并且容易地执行比如在activitylayout中查找一个view的任务.

View listView = getActivity().findViewById(R.id.list);

同样地,activity可以通过从FragmentManager获得一个到Fragment的引用来调用fragment中的方法, 使用findFragmentById() 或 findFragmentByTag().

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

Android Task和返回栈

应用通常包含多个Activity。每个 Activity 均应围绕用户可以执行的特定操作设计,并且能够启动其他 Activity。

例如,电子邮件应用可能有一个 Activity 显示新邮件的列表。用户选择某邮件时,会打开一个新 Activity 以查看该邮件。

一个 Activity 甚至可以启动设备上其他应用中存在的 Activity。例如,如果应用想要发送电子邮件,则可将 Intent 定义为执行“发送”操作并加入一些数据,如电子邮件地址和电子邮件。

然后,系统将打开其他应用中声明自己处理此类Intent 的 Activity。在这种情况下, Intent是要发送电子邮件,因此将启动电子邮件应用的“撰写”Activity(如果多个 Activity 支持相同Intent,则系统会让用户选择要使用的 Activity)。发送电子邮件时,Activity 将恢复,看起来好像电子邮件 Activity 是您的应用的一部分。
即使这两个 Activity 可能来自不同的应用,但是Android 仍会将 Activity 保留在相同的任务中,以维护这种无缝的用户体验。

任务(Task)是指在执行特定作业时与用户交互的一系列 Activity。
这些 Activity 按照各自的打开顺序排列在堆栈(即“返回栈”)中。

设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷键)时,该应用的任务将出现在前台。

如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开。

当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。
前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。
用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。

堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。

因此,返回栈以“后进先出”对象结构运行。

图 1 通过时间线显示 Activity 之间的进度以及每个时间点的当前返回栈,直观呈现了这种行为。

图 1. 显示任务中的每个新 Activity 如何向返回栈添加项目。
用户按“返回”按钮时,当前 Activity 随即被销毁,而前一个 Activity 恢复执行。

如果用户继续按“返回”,堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务开始时正在运行的任意 Activity)。

当所有 Activity 均从堆栈中删除后,任务即不复存在。

图 2. 两个任务:任务 B在前台接收用户交互,而任务 A 则在后台等待恢复。

图 3. 一个 Activity 将多次实例化。

任务是一个有机整体,当用户开始新任务或通过“Home”按钮转到主屏幕时,可以移动到“后台”。
尽管在后台时,该任务中的所有 Activity 全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅仅失去焦点而已,如图 2 中所示。

然后,任务可以返回到“前台”,用户就能够回到离开时的状态。
例如,假设当前任务(任务 A)的堆栈中有三个 Activity,即当前 Activity 下方还有两个 Activity。
用户先按“Home”按钮,然后从应用启动器启动新应用。

显示主屏幕时,任务 A进入后台。新应用启动时,系统会使用自己的 Activity 堆栈为该应用启动一个任务(任务B)。与该应用交互之后,用户再次返回主屏幕并选择最初启动任务 A 的应用。现在,任务 A 出现在前台,其堆栈中的所有三个 Activity 保持不变,而位于堆栈顶部的 Activity 则会恢复执行。

此时,用户还可以通过转到主屏幕并选择启动该任务的应用(或者,通过从概览屏幕选择该应用的任务)切换回任务 B。这是 Android 系统中的一个多任务示例。

注:后台可以同时运行多个任务。但是,如果用户同时运行多个后台任务,则系统可能会开始销毁后台 Activity,以回收内存资源,从而导致 Activity 状态丢失。请参阅下面有关 Activity 状态的部分。

由于返回栈中的 Activity 永远不会重新排列,因此如果应用允许用户从多个 Activity 中启动特定 Activity,则会创建该 Activity 的新实例并推入堆栈中(而不是将 Activity 的任一先前实例置于顶部)。

因此,应用中的一个 Activity 可能会多次实例化(即使 Activity 来自不同的任务),如图 3 所示。
因此,如果用户使用“返回”按钮向后导航,则会按 Activity 每个实例的打开顺序显示这些实例(每个实例的 UI 状态各不相同)。

但是,如果您不希望 Activity 多次实例化,则可修改此行为。
具体操作方法将在后面的管理任务部分中讨论。

Activity 和任务的默认行为总结如下:

  • 当 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态(例如,滚动位置和已输入表单中的文本)。如果用户在处于 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行。
  • 用户通过按“Home”按钮离开任务时,当前 Activity 将停止且其任务会进入后台。系统将保留任务中每个 Activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将出现在前台并恢复执行堆栈顶部的 Activity。
  • 如果用户按“返回”按钮,则当前 Activity 会从堆栈弹出并被销毁。堆栈中的前一个 Activity 恢复执行。销毁 Activity 时,系统绝对不会保留该 Activity 的状态。
  • 即使来自其他任务,Activity 也可以多次实例化。

导航设计

如需了解有关 Android 应用导航工作方式的详细信息,请阅读 Android 设计的导航指南。

保存 Activity 状态


正如上文所述,当 Activity 停止时,系统的默认行为会保留其状态。
这样一来,当用户导航回到上一个 Activity 时,其用户界面与用户离开时一样。
但是,在 Activity 被销毁且必须重建时,您可以而且应当主动使用回调方法保留 Activity 的状态。

系统停止您的一个 Activity 时(例如,新 Activity 启动或任务转到前台),如果系统需要回收系统内存资源,则可能会完全销毁该 Activity。

发生这种情况时,有关该 Activity 状态的信息将会丢失。如果发生这种情况,系统仍会知道该 Activity 存在于返回栈中,但是当该 Activity 被置于堆栈顶部时,系统一定会重建 Activity(而不是恢复 Activity)。

为了避免用户的工作丢失,您应主动通过在 Activity 中实现
onSaveInstanceState()
回调方法来保留工作。

如需了解有关如何保存 Activity 状态的详细信息,请参阅Activity文档。

管理任务


Android 管理任务和返回栈的方式(如上所述,即:将所有连续启动的 Activity 放入同一任务和“后进先出”堆栈中)非常适用于大多数应用,而您不必担心 Activity 如何与任务关联或者如何存在于返回栈中。

但是,您可能会决定要中断正常行为。
也许您希望应用中的 Activity 在启动时开始新任务(而不是放置在当前任务中);或者,当启动 Activity 时,您希望将其现有实例上移一层(而不是在返回栈的顶部创建新实例);或者,您希望在用户离开任务时,清除返回栈中除根 Activity 以外的所有其他 Activity。

通过使用 &lt;activity&gt; 清单文件元素中的属性和传递给 startActivity()
的 Intent 中的标志,您可以执行所有这些操作以及其他操作。

在这一方面,您可以使用的主要
&lt;activity&gt;
属性包括:

您可以使用的主要 Intent 标志包括:

在下文中,您将了解如何使用这些清单文件属性和 Intent标志定义 Activity 与任务的关联方式,以及 Activity 在返回栈中的行为方式。

此外,我们还单独介绍了有关如何在概览屏幕中显示和管理任务与 Activity 的注意事项。
如需了解详细信息,请参阅概览屏幕
通常,您应该允许系统定义任务和 Activity 在概览屏幕中的显示方法,并且无需修改此行为。

注意:大多数应用都不得中断 Activity 和任务的默认行为:
如果确定您的 Activity 必须修改默认行为,当使用“返回”按钮从其他 Activity 和任务导航回到该 Activity 时,请务必要谨慎并确保在启动期间测试该 Activity 的可用性。请确保测试导航行为是否有可能与用户的预期行为冲突。

定义启动模式

启动模式允许您定义 Activity 的新实例如何与当前任务关联。
您可以通过两种方法定义不同的启动模式:

因此,如果 ActivityA 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B应该如何与当前任务关联。如果这两个 Activity 均定义 Activity B应该如何与任务关联,则 Activity A 的请求(如 Intent 中所定义)优先级要高于 ActivityB 的请求(如其清单文件中所定义)。

注:某些适用于清单文件的启动模式不可用作 Intent 标志,同样,某些可用作 Intent标志的启动模式无法在清单文件中定义。

使用清单文件

在清单文件中声明 Activity 时,您可以使用 &lt;activity&gt;元素的 launchMode属性指定 Activity 应该如何与任务关联。

launchMode属性指定有关应如何将 Activity 启动到任务中的指令。您可以分配给launchMode属性的启动模式共有四种:

"standard"(默认模式)
默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送
Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。
"singleTop"
如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的onNewIntent()
方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。例如,假设任务的返回栈包含根 Activity A 以及 Activity B、C
和位于顶部的 D(堆栈是 A-B-C-D;D 位于顶部)。收到针对 D 类 Activity 的 Intent。如果 D
具有默认的 "standard" 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。但是,如果 D 的启动模式是 "singleTop",则 D的现有实例会通过 onNewIntent() 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为A-B-C-D。但是,如果收到针对 A 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 "singleTop" 也是如此。

注:为某个 Activity 创建新实例时,用户可以按“返回”按钮返回到前一个 Activity。
但是,当 Activity 的现有实例处理新Intent 时,则在新 Intent 到达onNewIntent()
之前,用户无法按“返回”按钮返回到 Activity 的状态。

"singleTask"
系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。

注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。

"singleInstance"
"singleTask" 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。

我们再来看另一示例,Android 浏览器应用声明 Web 浏览器 Activity 应始终在其自己的任务中打开(通过在 &lt;activity&gt; 元素中指定 singleTask启动模式)。这意味着,如果您的应用发出打开Android 浏览器的Intent,则其 Activity 与您的应用位于不同的任务中。相反,系统会为浏览器启动新任务,或者如果浏览器已有任务正在后台运行,则会将该任务上移一层以处理新Intent。

无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按“返回”按钮始终会转到前一个 Activity。但是,如果启动指定singleTask启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。此时,返回栈包括上移到堆栈顶部的任务中的所有 Activity。

图 4 显示了这种情况。

图 4. 显示如何将启动模式为“singleTask”的 Activity 添加到返回栈。
如果 Activity 已经是某个拥有自己的返回栈的后台任务的一部分,则整个返回栈也会上移到当前任务的顶部。

如需了解有关在清单文件中使用启动模式的详细信息,请参阅
<activity>元素文档,其中更详细地讨论了 launchMode属性和可接受的值。

注:使用 launchMode属性为 Activity 指定的行为可由 Intent附带的 Activity 启动标志替代,下文将对此进行讨论。

使用 Intent 标志

启动 Activity 时,您可以通过在传递给 startActivity() 的 Intent
中加入相应的标志,修改 Activity 与其任务的默认关联方式。可用于修改默认行为的标志包括:

FLAG_ACTIVITY_NEW_TASK在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在onNewIntent() 中收到新Intent。正如前文所述,这会产生与 "singleTask" launchMode值相同的行为。

FLAG_ACTIVITY_SINGLE_TOP如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则
现有实例会接收对 onNewIntent()的调用,而不是创建 Activity 的新实例。正如前文所述,这会产生与 "singleTop" launchMode值相同的行为。

FLAG_ACTIVITY_CLEAR_TOP如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent()将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。产生这种行为的 launchMode属性没有值。

FLAG_ACTIVITY_CLEAR_TOP 通常与FLAG_ACTIVITY_NEW_TASK结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent的位置。

注:如果指定 Activity 的启动模式为"standard",则该 Activity 也会从堆栈中删除,并在其位置启动一个新实例,以便处理传入的 Intent。

这是因为当启动模式为 "standard"时,将始终为新 Intent 创建新实例。

处理关联

“关联”指示 Activity 优先属于哪个任务。默认情况下,同一应用中的所有 Activity 彼此关联。
因此,默认情况下,同一应用中的所有 Activity 优先位于相同任务中。
不过,您可以修改 Activity 的默认关联。
在不同应用中定义的 Activity 可以共享关联,或者可为在同一应用中定义的 Activity 分配不同的任务关联。

可以使用 &lt;activity&gt;元素的taskAffinity 属性修改任何给定 Activity 的关联。

taskAffinity属性取字符串值,该值必须不同于在
&lt;manifest&gt;
元素中声明的默认软件包名称,因为系统使用该名称标识应用的默认任务关联。

在两种情况下,关联会起作用:

  • 启动 Activity 的 Intent 包含FLAG_ACTIVITY_NEW_TASK标志。默认情况下,新 Activity 会启动到调用startActivity() 的 Activity 任务中。它将推入与调用方相同的返回栈。
    但是,如果传递给startActivity()的 Intent 包含 FLAG_ACTIVITY_NEW_TASK标志,则系统会寻找其他任务来储存新 Activity。这通常是新任务,但未做强制要求。
    如果现有任务与新 Activity 具有相同关联,则会将 Activity 启动到该任务中。
    否则,将开始新任务。如果此标志导致 Activity 开始新任务,且用户按“Home”按钮离开,则必须为用户提供导航回任务的方式。有些实体(如通知管理器)始终在外部任务中启动 Activity,而从不作为其自身的一部分启动 Activity,因此它们始终将FLAG_ACTIVITY_NEW_TASK 放入传递给
    startActivity()的 Intent 中。请注意,如果 Activity 能够由可以使用此标志的外部实体调用,则用户可以通过独立方式返回到启动的任务,例如,使用启动器图标(任务的根 Activity 具有
    CATEGORY_LAUNCHER
    Intent 过滤器;请参阅下面的启动任务部分)。
  • Activity 将其
    allowTaskReparenting
    属性设置为 "true"。在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)。例如,假设将报告所选城市天气状况的 Activity 定义为旅行应用的一部分。
    它与同一应用中的其他 Activity 具有相同的关联(默认应用关联),并允许利用此属性重定父级。当您的一个 Activity 启动天气预报 Activity 时,它最初所属的任务与您的 Activity 相同。但是,当旅行应用的任务出现在前台时,系统会将天气预报 Activity 重新分配给该任务并显示在其中。

提示:如果从用户的角度来看,一个 .apk
文件包含多个“应用”,则您可能需要使用 taskAffinity属性将不同关联分配给与每个“应用”相关的 Activity。

清理返回栈

如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根任务除外。
当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。

您可以使用下列几个 Activity 属性修改此行为:

alwaysRetainTaskState
如果在任务的根 Activity 中将此属性设置为 "true",则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。
clearTaskOnLaunch
如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。

换而言之,它与alwaysRetainTaskState 正好相反。
即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。

finishOnTaskLaunch
此属性类似于clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。
此外,它还有可能会导致任何 Activity 停止,包括根 Activity。
设置为 "true"时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。

启动任务

通过为 Activity 提供一个以 "android.intent.action.MAIN"为指定操作、以"android.intent.category.LAUNCHER"为指定类别的 Intent 过滤器,您可以将活动设置为任务的入口点。
例如:

<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

此类 Intent 过滤器会使 Activity 的图标和标签显示在应用启动器中,让用户能够启动 Activity 并在启动之后随时返回到创建的任务中。

第二个功能非常重要:用户必须能够在离开任务后,再使用此 Activity 启动器返回该任务。
因此,只有在 Activity 具有ACTION_MAINCATEGORY_LAUNCHER
过滤器时,才应该使用将 Activity 标记为“始终启动任务”的两种启动模式,即 "singleTask"
"singleInstance"。例如,我们可以想像一下如果缺少过滤器会发生什么情况:
Intent 启动一个 "singleTask"Activity,从而启动一个新任务,并且用户花了些时间处理该任务。然后,用户按Home按钮。
任务现已发送到后台,而且不可见。现在,用户无法返回到任务,因为该任务未显示在应用启动器中。

如果您并不想用户能够返回到 Activity,对于这些情况,请将
<activity>元素的finishOnTaskLaunch设置为 "true"(请参阅清理堆栈)。

有关如何在概览屏幕中显示和管理任务与 Activity 的更多信息,请参阅概览屏幕