本文参考\android\android\frameworks\base\core\java\android\app\Activity.java文件中的类注释,以及android/frameworks/base/docs/html/guide/components/activities.jd文件
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时需要实现两个方法
为了能够使用 android.content.Context#startActivity Context.startActivity()打开一个activity,我们需要将activity在对应包下的androidmanifest文件中使用activity节点进行声明
<application android:icon="@drawable/icon"
''''''
<!-- 声明activity -->
<activity android:name=".MainMenuActivity"
''''''
</activity>
''''''
</application>
Fragment开始于Android3.0,是应用程序组件碎片的意思,可以被放在activity内部。通过FragmentManager来管理与fragment的交互。fragmentmanager的对象可以通过两种方式获取到
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 是在Android3.0才加入进来的,所以在Android3.0之前如果想要使用fragment可以使用android.support.v4.app.FragmentActivity具体可以参考
fragments for all
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
在系统中,activity被一个称为activity栈activity stack的东西在管理。当新创建一个activity时,就被被放在栈顶,并且成为正在运行的activity—-先前的activity会被保留在activity的下方,当位于栈的activity退出后,位于该activity下方的activity就会运行到前台,activity栈遵循栈的原则:后进先出。
一个activity基本上有四种状态
接下来看一张activity生命周期的流程图:(来自源码)
写的很是清楚啊
在这个流程图中可以看到有三个关键的循环
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的状态。
如果一个设备的配置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。
开启一个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));
}
}
}
}
保存永久状态:
activity通常会保存两种持久的状态
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();
}
}
在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中。
Android系统试图将应用进程尽可能的保留更长时间,但是当内存较少时最终需要杀死旧的进程。正如在activity的生命周期ActivityLifecycle中所描述的那样,关于哪一个进程应该被移除由与用户交互状态决定。通常来说,根据运行在进程中的activity的状态可以看出进程有四种状态,按照重要性的顺序排列。系统在重新排序杀死重要进程之前会优先杀死最不重要的进程。
包括两部分内容
1. ActivityState:activity状态的保存
2. ManagingTasks:管理任务栈。对于任务栈的管理又分为四个方面。这四个方
面分别是
一个应用程序通常包含多个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的打开顺序存入栈中。
任务栈中也可以添加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方法来保存状态。
因为栈中的activity从来不会重新排序,当用户加载一个已经在栈中存在的activity时会选择去重新初始化创建而不是说使用栈中存在的,这就造成一个问题,当用户点击back键返回时可以发现一个界面可能会呈现多次,造成了一种很不好的用户体验。为了解决这个问题,这就需要对activity和任务栈进行一个管理。
先来总结一下默认状态下activity和task
经过以上可以看出,当activity被stopped的时候系统默认情况下会保存activity的状态,但是当activity被stopped的时候很容易被系统杀死,所以应该主动去保存activity的状态。
默认情况下Android任务栈的管理遵循后进先出的原则,无需在意activity在任务栈中怎么存在以及activity是如何和任务栈联系的。我们也可以改变这种默认的方式,比如
改变默认的任务栈中activity的启动方式有两种方法
第一种方式:activity节点下的属性来修改
主要有以下几种属性
第二种方式:为intent设置标志位
主要有以下几种标志位
任务启动模式定义了如何去初始化一个与当前任务栈相关的activity,可以通过两种方式来定义不同的 启动模式
如果activityA中启动activityB,activityB可以在Androidmanifest文件中定义如何去启动,activityA也可以在intent中定义activityB如何启动。intent中的参数高于配置文件中定义的参数。有些参数对Androidmanifest可以用,有些intent可以用,有些两者都可用。
launchMode属性:定义了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。
当调用startActivity方法开启一个activity时可以通过设置intent的flag来修改activity的默认启动模式。
android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP:如果activity已经运行在当前栈,那么就会将当前栈中所有位于该activity之上的activity的实例销毁,并且重用该实例,调用activity的onNewIntent(activity节点下没有与该功能对应的启动模式)。该标志位经常和FLAG_ACTIVITY_NEW_TASK联合使用,
affinity表明activity想要属于的任务栈,也就是说在开启该activity时会放在哪个任务栈中。默认情况下一个应用中所有的activity有一个affinity,所以默认情况下所有activity启动会放在同一个任务栈中,但是也可以通过修改taskAffinity属性(Androidmanifest的activity节点下)来修改activity想要放置的任务栈,已达到同一应用不同activity放置在不同的任务栈中或者是不同应用的activity共享同一任务栈中的目的。
taskAffinity属性需要一个字符串值,为一个应用的包名(应用所特有的,系统可以通过该包名来识别不同的应用程序)。
affinity在两种情况下会发生作用:
如果用户离开任务栈很长时间,系统会将任务栈中除了根activity之外的所有activity都清除掉,当系统再次开启该任务栈时只能恢复根activity的状态。系统之所以这么做是因为觉得在过了一段时间后用户很可能想要放弃曾经未保存的修改。
有一些属性可以影响系统对任务栈的处理。
如果觉得还不错,欢迎点赞,关注我的微信公众号