Android四大组件完全解析---Activity
Posted fanfan-公众号-码农修仙儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android四大组件完全解析---Activity相关的知识,希望对你有一定的参考价值。
本文参考\\android\\android\\frameworks\\base\\core\\java\\android\\app\\Activity.java文件中的类注释,以及android/frameworks/base/docs/html/guide/components/activities.jd文件
One Activity简介:
Activity是一个单独的、可以和用户交互的东西。几乎所有的activities都要与用户交互,所以activity承担着创建window的重任,你可以通过setContentView的方法往window里填充view。通过一个主题属性android.R.attr#windowIsFloating来设置activities是全屏显示full-screen还是悬浮窗isfloat(比如dialog,或者是一个悬浮的view),当然这里所说的全屏显示不包括状态栏。
<!-- 全屏显示 -->
<item name="windowIsFloating">false</item>
在对应的主题中有dialog主题样式的
<!-- 悬浮窗显示 -->
<style name="Theme.Material.BaseDialog">
''''''
<item name="windowIsFloating">true</item>
''''''
</style>
在继承Activity时需要实现两个方法
- onCreate: 在该方法中初始化activity。更重要的是,通常需要在该方法中调用setContentView方法来加载layout文件,并且用findViewById来检索需要和你进行交互的layout文件中的控件。
- onPause :在用户离开该activity时调用该方法处理。用户做出的任何修改都应该在该方法中提交commit(通常保存提交的数据使用android.content.ContentProvider)。
为了能够使用 android.content.Context#startActivity Context.startActivity()打开一个activity,我们需要将activity在对应包下的androidmanifest文件中使用activity节点进行声明
<application android:icon="@drawable/icon"
''''''
<!-- 声明activity -->
<activity android:name=".MainMenuActivity"
''''''
</activity>
''''''
</application>
Two 与activity相关的主题有
- #Fragments
- #ActivityLifecycle
- #ConfigurationChanges
- #StartingActivities
- #SavingPersistentState
- #Permissions
- #ProcessLifecycle
一,Fragments:
fragment简介
Fragment开始于Android3.0,是应用程序组件碎片的意思,可以被放在activity内部。通过FragmentManager来管理与fragment的交互。fragmentmanager的对象可以通过两种方式获取到
- Activity#getFragmentManager() Activity.getFragmentManager()
- Fragment#getFragmentManager() Fragment.getFragmentManager()
fragment类可以用来获取各种各样的结果,在它内部,它代表一个普通的操作或者是交互接口。fragment与包含他的activity紧密相连,fragment依赖于activity存在。虽然fragment有它自己的生命周期,但fragment的生命周期与activity的生命周期息息相关。fragment生命周期图如下:
fragment依附于activity存在,当activity被stopped之后,activity中的fragment不能够started。当activity销毁后,位于activity中的fragment也随之销毁。
所有Fragment的子类必须包含一个无参的构造方法。当有需要时尤其是在状态恢复期,framework层会经常重新初始化fragment,framework会去找无参构造器去初始化fragment。如果fragment中无参构造器不可用的话就会在状态恢复时抛出 runtime exception 。
fragment相关主题有:
- #OlderPlatforms旧版本
- #Lifecycle生命周期
- #Layout布局
- #BackStack
OlderPlatforms:
fragment 是在Android3.0才加入进来的,所以在Android3.0之前如果想要使用fragment可以使用android.support.v4.app.FragmentActivity具体可以参考
fragments for all
Lifecycle
- #onAttach 当fragment与activity绑定时调用
- #onCreate 当fragment初始化创建时调用
- #onCreateView创建并返回与fragment相关的view视图
- #onActivityCreated 通知fragment它所绑定的activity已经oncreate
- #onViewStateRestored 通知fragment所保存的所有view的状态已经被恢复
- #onStart fragment对用户可见
- #onResume fragment对用户既可见又获取焦点
- #onPause fragment不再能和用户交互(activity被paused或者fragment之间的操作)
- #onStop fragment对用户不可见(activity被stopped或者fragment之间的操作)
- #onDestroyView 清除与fragment相关的所有views相关资源
- #onDestroy 清除fragment状态
- #onDetach 当fragment与activity解绑时调用
layout
fragment可以作为应用程序布局的一部分,借助fragment,activity可以更好的 模块化,为更大的屏幕创建更复杂的用户交互,帮助应用实现小屏和大屏之间的尺寸的切换。例如,在一个activity上可以编写一个有item列表的fragment,然后再组合一个fragment去显示每个item的详细信息。
<fragment
class="com.example.android.wifidirect.DeviceListFragment"
android:id="@+id/frag_list"
android:layout_width="match_parent"
android:layout_height="@dimen/phone_list_height">
<!-- Preview: layout=@layout/row_devices -->
</fragment>
fragment相关可以参考:
fragment嵌套
fragment与activity
二,ActivityLifeCycle
在系统中,activity被一个称为activity栈activity stack的东西在管理。当新创建一个activity时,就被被放在栈顶,并且成为正在运行的activity—-先前的activity会被保留在activity的下方,当位于栈的activity退出后,位于该activity下方的activity就会运行到前台,activity栈遵循栈的原则:后进先出。
一个activity基本上有四种状态
- active/running:当activity位于屏幕前台时(此时位于栈顶)我们称他为处于active或者是running的状态。
- paused:当activity失去焦点但仍对用户可见时,称之为paused的状态。在成这种原因可能是有一个透明的activity或者是size较小的activity覆盖在该activity之上。处于paused的activity也处于存活状态(该activity保留了所有的状态和成员信息,并仍旧和wiindowmanager绑定),但是当内存不足时,处于paused状态的activity很有可能被系统杀死
- stopped:当一个activity完全的被另一个activity覆盖时就处于stopped状态。此时activity对用户不可见且没有焦点,但是仍旧保存有所有状态和成员信息。当其他地方需要内存时,系统会将处于stopped状态的activity杀死。
- 如果用户处于paused或者stopped状态,系统很可能通过finish该activity或者是kill进程的方式将该activity从内存中移除。当再次加载给用户时,必须重新开始并且恢复他先前的状态。
接下来看一张activity生命周期的流程图:(来自源码)
写的很是清楚啊
在这个流程图中可以看到有三个关键的循环
- entire lifetime:activity的完整周期:从activity第一次调用onCreate开始到最后调用ondestroy为止。activity会在oncreate方法中创建所有的全局global状态,并且在onDestroy()方法中释放所有剩余的资源。例如,如果activity在后台开启一个网络下载进程,activity会在onCreate方法中开启该线程,并在onDestroy方法中停止该线程。
- visible lifetime可见周期:发生在activity调用onStart和onStop之间。在可见期间activity对用户可见,但有可能不在前台无法与用户交互。在这两个方法之间,你可以保存activity需要展现给用户的资源。例如,可以在onStart中注册一个BroadcastReceiver来监控影响UI的改变,并在onstop方法中注销该广播。onStart和onstop方法可以被多次调用,来让activity可见或者隐藏 。
- foreground lifetime前台时期:activity调用onResume开始,到调用onPause为止。在这期间activity位于所有activity之上并且可以和用户进行交互。一个activity可以频繁的在resumed状态和paused状态之间切换—例如当设备休眠时activity处于paused,所以activity的onResume和onPause方法中应该是一些轻量级的代码。
public class MyActivity extends Activity
protected void onStart()
//注册广播
mContext.registerReceiver(mReceiver, mIntentFilter);
protect void onStop()
//注销广播
mContext.unregisterReceiver(mReceiver);
activity的完整生命时期包括以下所有activity的方法。你可以实现这些方法来完成工作。所有activity会实现onCreate方法来完成初始化操作;好多activity也会实现onPause方法来提交数据的改变并准备停止用户交互。在覆写方法时应该调用父类的方法
public class Activity extends ApplicationContext
protected void onCreate(Bundle savedInstanceState)
protected void onStart();
protected void onRestart();
protected void onResume();
protected void onPause();
protected void onDestroy();
activity生命周期方法之间的切换如下表所示
Method | Description | killable | Next |
---|---|---|---|
onCreate() | 当activity第一次创建时调用。在onCreate方法中需要做一些静态初始化的操作:创建views,绑定列表数据等等。如果onCreate传入的bundle参数不为null的话,可以从bundle中获取到activity先前的状态 | No | onStart |
onRestart() | 当activity已经被stopped,但又重新加载时调用 | No | onStart |
onStart() | 当activity对用户可见visible时调用 | No | 如果activity成为前台activity,则接下来会调用onResume方法。如果activity被隐藏hidden则接下来会调用onStop方法 |
onResume() | 当activity可以开始于用户交互即activity获取到焦点时会调用该方法。随着用户的输入,activity会处在栈顶。 | No | onPause |
onPause() | 当系统想要让一个先前的activity获取焦点时调用。该方法通常用来提交一些未保存的数据,停止动画以及其他一些消耗cpu内存的事情。方法的实现体必须快速,因为下一个activity只有在onPause方法执行返回之后才会resumed所以会一直处于阻塞状态 | 当activity重新返回到前台to the front时会调用onResume,当activity对用户不可见invisible时会调用onStop | |
onStop() | 当activity不再对用户可见时会调用该方法,因为其他activity已经resumed并且覆盖了该activity。当一个新的activity开始时会被放在该activity的前面时会调用onStop,或者该activity被销毁会调用onStop | yes | 如果activity重新加载出来与用户交互,则会调用onRestart方法。如果activity被销毁则会调用onDestroy方法 |
onDestroy() | 当activity被销毁时会调用该方法。Activity被销毁有两种情况,一种是用户调用了activity的finish方法结束了activity,一种是系统为节省空间销毁了activity。可以调用isFinishing方法来区分是哪一种情况 | yes | Nothing |
表格中killable这一列值得注意一下:
对于被标记成可以被killable的方法来说,当activity执行完这些方法返回时,持有该activity的进程《在任何时候》都可能被系统杀死,不再执行该activity中的任何一行代码。也因为如此,你应该在onPause方法中去保存数据(例如,用户的编辑)。另外,当将activity运行到后台状态时可以调用onSaveInstanceState(Bundle)方法来将activity的动态数据保存到一个bundle对象中,如果activity需要重新create的话,可以在onCreate中获取到Bundle数据。
注:在Android3.0以前保存数据应该在onPause中进行,因为onSaveInstanceState不是activity的生命周期的一部分,在进程生命周期相关中不会被调用。从Android3.0开始发生了改变。应用只有在onStop方法返回后才能被killable。这也就导致了在activity被杀死之前运行完onPause之后可能会调用onSaveInsatanceState(Bundle),并且可以让应用一直等待去保存数据直到运行了onStop方法。对于那些没有标记为可以被killable的方法表示,在方法开始调用直至方法返回这一段时间系统都不会去杀死activity的进程。因而一个activity是在调用onPause之后调用onResume之前才处于可以被kill的状态。
三, Configuration
如果一个设备的配置Resources.Configuration发生改变,显示给用户的交互界面也应该随之更新来配合格局发生的改变(横竖屏)。因为activity是与用户交互的主要机制,它包含用来处理设备配置改变的一些函数。
除非你有其他的指定,否则当设备的configuration发生改变时(例如,屏幕方向,语言,输入设备等等)会引起你当前activity的destroyed,经历activity的正常的生命周期过程onPause–>onStop–>onDestroy。如果activity已经加载到前台in the foreground或者对用户可见visible了,该activity实例一旦调用了onDestroy方法,就会去创建一个新的activity的实例,该activity实例可以获取到通过onSaveInstanceState所保存的先前的状态savedInstanceState.
应用中任何资源,包含layout文件都会基于configuration值的改变而发生变化。因而,处理configuration改变最安全的方法就是去检索所有资源文件,包括layouts,drawable,strings。因为activity必须知道如何如保存他们的状态以及如何根据保存的状态重新创建他们,所以,快捷的方法就是提供一个新的配置来restart一个activity。
在某些情况下,你可能想在configuration发生改变是不去重启activity,这需要借助配置文件中的属性android.R.attr#configChanges android:configChanges来完成。
<!-- 当屏幕方向或者键盘方向发生改变时不去调用activity的oncreate-->
android:configChanges="keyboardHidden|orientation"
当对该属性进行了配置后,就代表你可以进行监控,当所规定的配置发生变化时就会去调用onConfigurationChanged方法,而不是重启activity。
四, StartingActivities
开启一个activity并且获取到结果
android.app.Activity#startActivity方法用来打开一个activity,打开之后activity会被放在栈顶。在调用startActivity方法时需要用intent指明要打开的activity。
有时你可能想在activity结束时获取到一个返回结果。比如,你可能开启一个activity来让用户从联系人列表中选择一个联系人,当activity结束时把选择的结果返回回来,那就可以用android.app.Activity#startActivityForResult(Intent, int)打开activity,结果可以在activity的onActivityResult方法中获取
在一个存在的activity中调用setResult(int)方法把结果返回给它的父activity。在返回结果时必须提供一个结果码,可以是RESULT_CANCELED、RESULT_OK或者是其他自定义的代号。另外,也可以选择返回一个带有额外数据的intent返回回去。借助结果码,所有的信息都可以在parent的onActivityResult获取到。如果子activity发生了崩溃,父activity接受到的结果码就是RESULT_CANCELED
public class MyActivity extends Activity
...
static final int PICK_CONTACT_REQUEST = 0;
public boolean onKeyDown(int keyCode, KeyEvent event)
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
// When the user center presses, let them pick a contact.
startActivityForResult(
new Intent(Intent.ACTION_PICK,
new Uri("content://contacts")),
PICK_CONTACT_REQUEST);
return true;
return false;
protected void onActivityResult(int requestCode, int resultCode,
Intent data)
if (requestCode == PICK_CONTACT_REQUEST)
if (resultCode == RESULT_OK)
// A contact was picked. Here we will just display it
// to the user.
startActivity(new Intent(Intent.ACTION_VIEW, data));
五,SavingPersistentState
保存永久状态:
activity通常会保存两种持久的状态
- 共享文档 :数据(借助content provider保存在数据库中的数据)
- 内部状态:比如用户的偏好设置
对于第一类共享数据,activity应该使用“edit in place”编辑到位用户模型。也就是说,用户编辑之后可以立即保存,不需要其他的步骤。在使用这个模型时必须遵循以下两条规则
- 创建一个新文档时,立即创建他所依赖的数据库条目或者是文件。例如,如果用户选择写一个新的email,与该email相关的新的条目也必须要创建起来,来保证如果用户去了其他任何的activity这个email不会在草稿中消失。
- 当activity调用onPause方法时,他应该将用户针对数据所作出的修改进行commit提交。这个操作可以保证用户的修改可以被其他将要运行的activity所知道。你也有可能想要在activity生命周期的关键时刻去主动提交修改的数据:例如,在新打开一个activity之前,在activity被finish之前,当用户切换输入字段等等
这个模型的设计是用来防止当用户在activity之间进行切换时数据的丢失,并且允许system在activity被paused之后的任何时间可以安全的把activity杀死(因为其它地方需要系统资源,该activity被杀死)。这也就是说着用户按下“BACK”键并不意味”cancel”—它意味着保存他当前的内容并离开。在一个activity中取消编辑必须通过其它的机制来提供,例如,一个明确的“revert(还原)”或者是“undo(撤销)”选项。
Activity也提供了一个API来管理与activity相关的内部状态。例如,可以用来记录用户的偏好设置,并对用户的日历接卖弄进行一个初始化,或者是在使用浏览器时为用户显示一个默认的主页。
Activity的持久的状态通过getPreferences方法管理,允许检索或者是修改与activity相关的一组name/value键值对。为了让preferences可以应用程序多个组件(activities,receivers,services,providers)之间共享,你可以使用已有的方法
Context#getSharedPreferences Context.getSharedPreferences()来检索对应某个特殊name的preferences对象。(跨进程时不能通过preferences分享数据—只能通过contentProvider)
。以下是日历中的一个代码片段
public class CalendarActivity extends Activity
...
static final int DAY_VIEW_MODE = 0;
static final int WEEK_VIEW_MODE = 1;
private SharedPreferences mPrefs;
private int mCurViewMode;
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
SharedPreferences mPrefs = getSharedPreferences();
mCurViewMode = mPrefs.getInt("view_mode", DAY_VIEW_MODE);
protected void onPause()
super.onPause();
SharedPreferences.Editor ed = mPrefs.edit();
ed.putInt("view_mode", mCurViewMode);
ed.commit();
六,Permissions
在Androidmanifest中注册activity时可以给activity写明一个权限,这样其他应用在打开该activity时就需要拥有这个权限。
当打开一个activity时,你可以设置在intent上设置标志位Intent.FLAG_GRANT_READ_URI_PERMISSION或者 Intent.FLAG_GRANT_WRITE_URI_PERMISSION。
这将授予activity对intent中特定uri的访问权限。访问权限会被保留到activity结束(他将保持到主机进程被杀死并且其他暂时性损坏)。Android2.0来说,如果activity已经被created并且一个新的intent也被发送给onNewIntent(intent),任何新授予的uri权限都会被添加到所现有的uri中。
七,ProcessLifecycle
Android系统试图将应用进程尽可能的保留更长时间,但是当内存较少时最终需要杀死旧的进程。正如在activity的生命周期ActivityLifecycle中所描述的那样,关于哪一个进程应该被移除由与用户交互状态决定。通常来说,根据运行在进程中的activity的状态可以看出进程有四种状态,按照重要性的顺序排列。系统在重新排序杀死重要进程之前会优先杀死最不重要的进程。
- foreground activity前台activity:(activity位于屏幕上方,与用户进行交互)是最重要的。如果占用的内存超过了用户的可用内存,该进程会在最后被杀死。通常,从这方面来说设备已经到达一个内存分页状态,所以为了保证用户输入的流畅必须要杀死一些进程
- visible activityactivity(对用户可见,但是没在前台即不能和用户交互,例如activity上方弹出一个dialog)也是非常重要的,只有在系统要求保持前台activity运行时才会杀死该进程
- background activity(后台进程,activity对用户不可见,并且已经被paused的activity)不再是危急的activity,所以系统可以很安全的杀死它的进程回收内存运行foreground 进程和visible 进程。如果它的进程需要被杀死,当用户再次切换回该activity时(再次显示到屏幕上),会调用oncreate方法并且从savedInstanceState中取出先前onSaveInstanceState保存的数据,以保证它在重新打开时获取到用户最后离开时的状态
- empty process(空进程)是一个没有任何activity或者其他应用组件的进程。当内存不足时会很快被杀死。因为这个原因,任何你在activity之外的后台操作都必须在service和broadcastreceiver的上下文中执行,以保证系统可以保持你的进程运行。
有时候一个activity可能需要去做一个与activity生命周期无关的长时间运行的一个操作。举个例子,相机应用允许你从网络下载图片。下载需要花很长事件,当进行下载操作时应用允许用户离开相机应用。为了实现这个目的,你的activity就应该开一个service来处理下载操作。这个操作可以让系统优先考虑你的进程(考虑到它比其他不可见的应用程序更重要),与原始的activity处于何种状态无关
Three 任务栈:Tasks and Back Stack
包括两部分内容
1. ActivityState:activity状态的保存
2. ManagingTasks:管理任务栈。对于任务栈的管理又分为四个方面。这四个方
面分别是
- #TaskLaunchModes:任务启动模式
- #Affinities:
- #Clearing:清除后台栈
- #Starting:开始任务
任务栈相关说明
一个应用程序通常包含多个activities。每一个activity都应该围绕一个特殊的action来设计,用户可以执行这个动作也可以打开其他activities。例如,email应用程序可能有一个应用程序用来显示新的message列表,当用户选则列表中的其中一条时,程序会打开另一个activity用来查看message。
activity也可以打开设备中其他应用程序中的activities。例如,如果你想发送一个email,你可以定义一个“intent”来执行发送的动作,并让携带着一些数据,这些数据有可能是一个email地址和一条message。
在其他应用程序中,处理这种intent的activity会打开。在这种情况下,intent是发送email,所以负责发送email的应用程序中相关的activity就会打开(如果有多个activity都支持这种intent,系统就会让用户进行选择)。当email被发送之后,你的activities就会重新获取焦点,就像是发送email的activity属于你的应用程序一样。尽管activity来自于不同的应用程序,Android通过保持activities位于同一个任务栈来实现无缝切换的用户体验。
任务栈是一个装载activities的容器,按照activities的打开顺序存入栈中。
一, activity中添加fragment
任务栈中也可以添加fragment,比如一个activity有两个页面,一个是显示list列表的fragmentA,一个是显示item的详细信息的fragmentB。如果此时想要显示另一个item的详细信息fragmentC,即第二个页面的fragment由B切换到了C。如果你想实现的效果为当用户按下BACK键时fragmentC消失并重新返回fragmentB,那么你可以做如下操作:在fragment进行切换时,调用commit提交之前调用addToBackStack方法。
二,开启任务栈
主屏幕是大部分应用的入口,所以很多任务栈都是从主屏幕开启的。用户点击launcher界面上的快捷方式就可以开启一个任务栈。如果该任务栈不存在就会创建并且把程序中的“main”activity作为第一个activity,如果该任务栈存在则会将后台的任务栈加载到前台来并回复任务栈被放置在后台时所保存的状态。
如果在当前activityA开启另一个activityB,那么此时activityB就会被压栈并处于栈顶,activityA就会被stopped(此时系统会保存activityA的状态)并且处于activityB的下方。当用户点击BACK键返回时就会将activityB弹出栈并将activityB销毁,然后重新加载activityA至resume并且恢复activityA被stopped之前的状态。栈中的activity的顺序由压入栈的顺序决定,不会被重新排序。当一个activity被加载时就会位于栈顶,当一个activity被销毁时就会被弹出,任务栈遵循“后进先出”的规则。
图一所示
图一说明了当activity被加载时会位于栈顶,当按下BACK键时activity会被弹出栈并且被销毁。
如果一直按BACK键,那么栈中所有的activity都会被弹出并销毁直至显示出来主屏幕界面或者是启动该任务栈之前的状态,当任务栈中没有activity存在时任务栈就会消失。
在开启任务栈中需要规定一个入口,通过activity节点下的一些属性来规定:
<activity android:name="AndouKun" android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
三,多任务栈
图二
图二显示任务栈B位于前台与用户进行交互,任务栈A位于后台等待被resume
图三
图三中可以看出一个栈中有多个activity的实例。
一个任务栈是一个整体,当用户开启一个新的任务或者是回到主屏幕需要当前任务栈回到后台时,任务栈整体处于后台状态,任务栈中的activity处于stopped的状态,但是任务栈仍旧是完好的只是被另一个任务栈替代失去焦点而已,系统会帮其保存数据。如图二所示,假设任务栈A此时处于前台且栈中有三个activity a –> b—>c a处于栈顶,如果用户按下了home键并且开启了一个新的任务栈B,当再次按下home键时任务栈B进入后台,然后用户再次开启任务栈A,此时会去加载离开任务栈A之前的状态。
多任务栈可以被系统保存,但是如果同时运行多个任务栈的话在内存不足时后台任务栈很容易被系统杀死,此时activity的状态就会丢失。所以要及时保存activity的状态。比如在activity被stopped时调用onSaveInstanceState方法来保存状态。
四,managetask 管理任务栈
因为栈中的activity从来不会重新排序,当用户加载一个已经在栈中存在的activity时会选择去重新初始化创建而不是说使用栈中存在的,这就造成一个问题,当用户点击back键返回时可以发现一个界面可能会呈现多次,造成了一种很不好的用户体验。为了解决这个问题,这就需要对activity和任务栈进行一个管理。
先来总结一下默认状态下activity和task
- 当activityA中打开activityB时,activityA被stopped且状态被保存(比如滑动位置,输入文本等数据信息),activityB被resumed。如果此时按下BACK键,activityB就会被销毁,activityA就会重新resume获取焦点并且恢复所保存的数据
- 当用户按下HOME键回到主屏幕时,activity就会被stopped并且此时任务栈会进入后台模式,但是系统仍旧保存任务栈中每个activity的状态。如果用户再次打开任务栈,任务栈又会重新进入前台并且加载处于栈顶的activity
- 如果用户按下了BACK键,当前的activity会被销毁,就会去加载在栈中位于该activity下方的activity。
- 即便是来自于其他栈,activity也可以被多次初始化。
ActivityState activity的状态
经过以上可以看出,当activity被stopped的时候系统默认情况下会保存activity的状态,但是当activity被stopped的时候很容易被系统杀死,所以应该主动去保存activity的状态。
ManagingTasks 管理任务栈
默认情况下Android任务栈的管理遵循后进先出的原则,无需在意activity在任务栈中怎么存在以及activity是如何和任务栈联系的。我们也可以改变这种默认的方式,比如
- 当启动activity时放在一个新的任务栈中而不是在当前任务栈中
- 重用任务栈中的某个activity实例而不是重新创建
- 当用户离开任务栈时清除所有activity只保留根activity(典型的用法:按两次back键结束应用)
修改任务栈默认行为
改变默认的任务栈中activity的启动方式有两种方法
- 在Androidmanifest文件的activity节点下定义一些任务栈相关属性
- 在代码中给在start一个activity时给intent添加flag标志位
任务栈对activity的默认处理一般不用修改。
第一种方式:activity节点下的属性来修改
主要有以下几种属性
- taskAffinity
- launchMode
- allowTaskReparenting
- clearTaskOnLaunch
- alwaysRetainTaskState
- finishOnTaskLaunch
第二种方式:为intent设置标志位
主要有以下几种标志位
- android.content.Intent#FLAG_ACTIVITY_NEW_TASK
- android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP
- android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP
TaskLaunchModes:任务启动模式
任务启动模式定义了如何去初始化一个与当前任务栈相关的activity,可以通过两种方式来定义不同的 启动模式
- 在Androidmanifest文件中定义
- 在intent文件中定义
如果activityA中启动activityB,activityB可以在Androidmanifest文件中定义如何去启动,activityA也可以在intent中定义activityB如何启动。intent中的参数高于配置文件中定义的参数。有些参数对Androidmanifest可以用,有些intent可以用,有些两者都可用。
ManifestForTasks:在Androidmanifest中规定启动模式
launchMode属性:定义了activity如何进入任务栈,总共有四种模式
- standard:activity的默认启动模式。activity可以被多次初始化,每一个实例可以属于不同的任务栈,任务栈中可以有多个该activity的实例
- singleTop:如果activity实例存在于当前栈的顶端则重用该实例。会调用onNewIntent()而不是去create该activity的实例。activity可以被多次初始化,每个实例可以属于不同的栈,一个栈可以有过个实例(但是仅仅是activity不在栈顶时才会去重新创建activity的实例)。
例如,假设任务栈中有activityA—>B—>C—>D,且A处于栈底D处于栈顶,有一个intent需要启动D,如果D的启动模式是默认的standard的话那么就会在栈中创建D的实例,即此时任务栈中情况为A—>B—>C—>D—>D。如果D的启动模式是singleTop的话则,在启动D时会调用onNewIntent()方法重用任务栈中的实例,任务栈中的 情况是A—>B—>C—>D。但如果是启动B,就算设置了B的启动模式为singleTop,仍旧会重新创建一个B的实例,即任务栈情况是A–>B–>C–>D–>B。
如果是直接创建的activity实例,则在按下BACK按键时会返回先前activity的状态,但如果是通过intent去重用一个已经存在的activity,则在按下返回键时不会返回重用之前的状态,只能返回重用之后的状态。 - singleTask:系统在一个新的任务栈中初始化一个activity并把该activity当做根activity。但是,如果activity的实例已经存在于另一个栈中(不同于当前栈),系统就会调用 onNewIntent() 重用该实例,而不是去重新创建。虽然activity是在一个新的任务栈中开启的但是按下back键仍旧会返回先前的activity(此时按下back键是把目前处于前台的任务栈即承载该activity的任务栈销毁,显示开启该任务栈之前的任务栈)。
- singleInstance:类似singleTask。但是在存储该activity的任务栈中不会去初始化其他activity的实例。所以是单例任务栈。
举个例子,浏览器的web界面就应该是singleTask启动模式,别的应用可以声明一个intent来打开浏览器页面,此时浏览器页面的activity被放在另一个任务栈中(如果activity存在则将所属任务栈置为前台,否则为activity开启新的任务栈)而不是当前任务栈。
无论打开activity时是放置在当前任务栈还是重新去开启一个任务栈,按下BACK键总是返回先前的activity。但是如果在开启一个activity时使用singleTask模式,如果后台任务栈中存在该activity的实例,则整个后台任务栈就会被带到前台来。
如下图四所示:
有两个任务栈,前台任务栈A(activity1->activity2),后台任务栈B(activityX –>activityY),y的启动模式为singleTask,当在activity2中启动y时会将任务栈B整体移到任务栈A上,此时按下BACK键会显示activityX。
IntentFlagsForTasks 利用intent的flag来定义activity的启动模式
当调用startActivity方法开启一个activity时可以通过设置intent的flag来修改activity的默认启动模式。
- android.content.Intent#FLAG_ACTIVITY_NEW_TASK:在一个新的任务栈中开启activity,如果包含你正在打开的activity的任务栈已经存在,则将任务栈移动至前台并恢复最后的状态,此时activity收到新的intent调用onNewIntent();(等同于singleTask)
- android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP:如果activity的实例处于当前任务栈的栈顶,则重用该实例,调用activity的onNewIntent()(等同于singleTop)
android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP:如果activity已经运行在当前栈,那么就会将当前栈中所有位于该activity之上的activity的实例销毁,并且重用该实例,调用activity的onNewIntent(activity节点下没有与该功能对应的启动模式)。该标志位经常和FLAG_ACTIVITY_NEW_TASK联合使用,
Affinities 处理相关的任务栈
affinity表明activity想要属于的任务栈,也就是说在开启该activity时会放在哪个任务栈中。默认情况下一个应用中所有的activity有一个affinity,所以默认情况下所有activity启动会放在同一个任务栈中,但是也可以通过修改taskAffinity属性(Androidmanifest的activity节点下)来修改activity想要放置的任务栈,已达到同一应用不同activity放置在不同的任务栈中或者是不同应用的activity共享同一任务栈中的目的。
taskAffinity属性需要一个字符串值,为一个应用的包名(应用所特有的,系统可以通过该包名来识别不同的应用程序)。
affinity在两种情况下会发生作用:
- 当启动一个activity时的intent中包含FLAG_ACTIVITY_NEW_TASK时。默认情况下activity会被加载到调用startActivity的activity所在的任务栈中也就是当前栈,被调用这放入到相同的栈中。但是如果startActivity时intent中包含标志FLAG_ACTIVITY_NEW_TASK时系统就会去寻找一个不同的任务栈来加载该activity。通常情况下都会需要一个新的任务栈,但是其实并不全是。如果存在一个后台栈与要启动的activity所指定的affinity一致则在该后台栈中启动activity,否则就要去创建新的任务栈。一些应用经常会在外部的任务栈中去开启activity比如通知栏notificationmanager。
2.当activity的allowTaskReparenting被设置为true时,在这种情况下当任务栈运行到前台时,允许activity从启动它的任务栈移动到他想要属于的任务栈。
例如,一个天气预报选择城市界面A作为一个应用程序的一部分,与该应用程序的其他activity有相同的affinity。当在你的任务栈中启动该activityA时,会在你的任务栈中添加该activityA的实例,但是当天气预报的 应用程序所属的任务栈重新回到前台时activityA就会被重新分配到天气预报应用的任务栈中去。
Clearing:清除任务栈
如果用户离开任务栈很长时间,系统会将任务栈中除了根activity之外的所有activity都清除掉,当系统再次开启该任务栈时只能恢复根activity的状态。系统之所以这么做是因为觉得在过了一段时间后用户很可能想要放弃曾经未保存的修改。
有一些属性可以影响系统对任务栈的处理。
- alwaysRetainTaskState :如果任务栈中根activity的该属性为true,就算过了很长时间,任务栈中的 所有activity也不会被销毁。
- clearTaskOnLaunch:如果任务栈中根activity的该属性为true,一旦任务栈处于后台就会被清除。也就是说与alwaysRetainTaskState 属性值恰好相反,一旦离开任务栈再次返回回来就是初始状态。
- finishOnTaskLaunch:与clearTaskOnLaunch属性类似,但该属性是针对activity而言的而不是整个任务栈。一旦用户离开了该任务栈再次返回时该activity已经被销毁。
如果觉得还不错,欢迎点赞,关注我的微信公众号
以上是关于Android四大组件完全解析---Activity的主要内容,如果未能解决你的问题,请参考以下文章