一、简介
Activity是Android四大组件之一,对应着应用的一个个界面。Activity实现了Window.Callback
和KeyEvent.Callback
两个接口,所以用户通过屏幕点击或点击按键可以和Activity交互。
创建Activity就是继承Activity创建子类,必须在清单文件注册
。
二、使用
1. 创建Activity
/**
* 继承Activity,创建子类
*/
public class MainActivity extends Activity {
/**
* 生命周期方法:首次创建Activity时调用,可以做一些初始化工作。始终后接 onStart()
* 必须重写,需要通过setContentView()设置界面
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置布局
setContentView(R.layout.activity_main);
// The activity is being created.
}
/**
* 生命周期方法:正在启动Activity,在 Activity 即将对用户可见之前调用。如果 Activity
* 转入前台,则后接 onResume(),如果 Activity转入隐藏状态,则后接 onStop()
*/
@Override
protected void onStart() {
super.onStart();
// The activity is about to become visible.
}
/**
* 生命周期方法:Activity已经启动,在 Activity即将开始与用户进行交互之前调用。始终后接 onPause()
*/
@Override
protected void onResume() {
super.onResume();
// The activity has become visible (it is now "resumed").
}
/**
* 生命周期方法:正在停止,当系统即将开始继续另一个 Activity 时调用。
* 不能做耗时操作,因为必须先执行完旧Activity的onPause(),才能执行新Activity的onResume()。
* 如果 Activity 返回前台,则后接 onResume(),如果 Activity 转入对用户不可见状态,则后接 onStop()
*/
@Override
protected void onPause() {
super.onPause();
// Another activity is taking focus (this activity is about to be "paused").
}
/**
* 生命周期方法:即将停止,在 Activity 对用户不再可见时调用。
* 可以做一些重量级的回收操作,同样不能太耗时。
* 如果 Activity 恢复与用户的交互,则后接 onRestart(),如果 Activity 被销毁,则后接 onDestroy()。
*/
@Override
protected void onStop() {
super.onStop();
// The activity is no longer visible (it is now "stopped")
}
/**
* 生命周期方法:即将销毁,在 Activity 被销毁前调用。做一些最终的资源释放
*/
@Override
protected void onDestroy() {
super.onDestroy();
// The activity is about to be destroyed.
}
/**
* 生命周期方法:正在重新启动Activity,在 Activity 已停止并即将再次启动前调用。始终后接 onStart()
*/
@Override
protected void onRestart() {
super.onRestart();
}
}
2. 编写界面
3. 清单文件注册
...
...
4. 启动Activity
上面的Activity,因为设置了上面的filter,标识了是程序的入口,安装到手机上后,打开程序,就会为我们启动上面这个MainActivity。
重复上面步骤,创建SecondActivity,清单文件注册。
- 显示启动
startActivity(new Intent(MainActivity.this, SecondActivity.class));
- 隐式启动
// intent,设置action,action是自定义的字符串
Intent intent = new Intent("com.test.activity");
startActivity(intent);
- 带返回值的启动
// MainActivity启动SecondActivity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
// 方法不同
startActivityForResult(intent,1);
// SecondActivity返回数据
Intent intent = new Intent(this,MainActivity.class);
intent.putExtra("test","返回的数据");
setResult(RESULT_OK,intent);
/**
* 重写Activity的该方法,接收返回数据
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
// 判断请求码
switch (requestCode){
case 1:
// 判断返回码
if(resultCode == RESULT_OK){
String test = data.getStringExtra("test");
Log.e("MainActivity",test); // 将会输出 “返回的数据”
}
break;
}
}
startActivity()本质也是调用startActivityForResult():
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
三、详解生命周期
1. 7个生命周期
小知识:
- onStart()和onStop()是从Activity是否可见的角度来回调的,onResume()和onPause()是从Activity是否位于前台的角度回调的
2.Dialog、Popupwindow 和 Toast 不会对Activity的生命周期造成影响,可以仔细品读上面onPause()生命周期的解读,个人理解是Activity之间的相互切换才会导致生命周期方法的回调
2. 2个最常用的生命周期方法
onCreate()
是必须实现的,用来初始化Activity,通过setContentView()来使用布局资源定义UIonPause()
用来在用户离开时保存或提交数据
3. 3种生命周期阶段
完整生命周期:发生在 onCreate() 调用与 onDestroy() 调用之间。
可见生命周期: 发生在 onStart() 调用与 onStop() 调用之间。在这段时间,用户可以在屏幕上看到 Activity 。
前台生命周期: 发生在 onResume() 调用与 onPause() 调用之间。在这段时间,Activity 位于屏幕上的所有其他 Activity 之前,并具有用户输入焦点。
4. 3种存在状态
运行
onResume()执行后,此 Activity 位于屏幕前台并具有用户焦点。暂停
onPause()执行后,另一个 Activity 位于屏幕前台并具有用户焦点,但此 Activity 仍可见。比如:被一个透明背景的Activity或一个dialog样式的Activity覆盖停止
onStop()执行后,该 Activity 被另一个 Activity 完全遮盖(该 Activity 目前位于“后台”)。
小知识:
- 死亡: onDestory()执行后 ,已经不算存在状态了
- 以上三种Activity,从上到下优先级顺序为 1 ,2,3,在系统内存不足情况下,系统回收的优先级为3,2,1
- 如果一个进程没有四大组件,那么它将很快被杀死。所以,一些后台工作最好放在Service中进行,保证进程的优先级,不会被轻易杀死
5. 切换时生命周期变换
6. 与Fragment之间生命周期变换
真正的生命周期顺序,应该在重写的生命周期方法中的super()之前打印Log,才是真实的顺序,如下:
四、Activity异常销毁
1. 发生异常的情况
- 资源相关系统配置发生改变导致Activity被杀死并重建
重启行为旨在通过利用与新设备配置匹配的备用资源自动重新加载您的应用,来帮助它适应新配置。 - 资源内存不足导致优先级低的Activity被杀死
2. 发生异常时的2个回调方法
onSaveInstanceState() 保存状态
在Activity异常终止的情况下,才会被调用,如旋转屏幕配置发生变更,这个方法调用在onStop()之前onRestoreInstanceState() 恢复状态
异常终止后被重新创建会被调用,调用在onStart()之后
系统会自动帮我们做一些恢复工作,如Activity的视图结构,其中的文本框输入内容等,因为View都有以上两个方法。
流程:委托思想
Activity异常 > Activity > window > DecorView > 各个子view,调用
3. 横竖屏切换Activity的生命周期
上面提到,资源配置变更导致Activity重启的原因是:通过利用与新设备配置匹配的备用资源自动重新加载您的应用,来帮助它适应新配置。
那么,我们同样可声明 Activity 自行处理配置变更,这样可以阻止系统重启 Activity。如下:
测试环境:Android 5.0
1. 未设置android:configChanges
横竖屏切换都是,销毁,重建;onPause > onStop > onDestory > onCreate > onStart > onResume
2. 设置android:configChanges="orientation"
同上,销毁,重建
3. 设置android:configChanges="orientation|keyboardHidden"
同上,销毁,重建
4. 设置android:configChanges="orientation|keyboardHidden|screenSize"
或
android:configChanges="orientation|screenSize"
只回调onConfigurationChanged()方法
小知识:
- 只是想监测到横竖屏的变化只要使用orientation + screenSize两个属性就行了,keyboardHidden是监测软键盘变化的
- 因为 Android 3.2(API 级别 13)开始,当设备在纵向和横向之间切换时,“屏幕尺寸”也会发生变化。若要避免由于设备方向改变而导致运行时重启,则除了 "orientation" 值以外,您还必须添加 "screenSize" 值
- 有个说法是切换成竖屏调用两次生命周期的,这个网上有说是2.2版本是这样,未亲测
五、Activity启动模式
1. 4种启动模式
- Standard 默认模式
每启动一次Activity,就创建一个Activity实例
- SingleTop 栈顶复用
任务栈顶有该Activity实例,回调onNewIntent(); 栈顶无实例,创建新实例
应用场景:登录页面、接收推送的资讯详情页
- SingleTask 栈内复用
该Activity的taskAffity属性指定的任务栈,如果不存在,则创建该任务栈,创建Activity实例;任务栈存在,如果栈内有该Activity实例,回调onNewIntent(),并将任务栈中在其上面的Activity全部弹出栈; 栈内无实例,创建新Activity实例
应用场景:主页、浏览器打开网页的Activity(设置SingleTask,设置IntentFilter允许隐式启动)
正常情况,点击返回键都会返回上一个Activity,但存在这样一个特殊情况:
- SingleInstance 单一实例
SingleTask的加强版,如果存在该Activity实例,则回调onNewIntent();无实例,则创建新的任务栈和新的Activity实例,该实例是全局的,该任务栈中也将有且仅有这一个Activity实例,通过该Activity启动的其他Activity也将在其他单独的任务栈中创建
应用场景:来电提醒、闹钟提醒这种全局唯一的页面,项目中还未用到过
复用Activity,回调onNewIntent()的情况:
A为SingleTask,B为Standard,启动顺序:A > B > A ,生命周期切换如下
A : onCreate() > onStart() > onResume() > onPause()
B : onCreate() > onStart() > onResume()
A : onStop()
B : onPause()
A : onNewIntent() > onRestart() > onStart() > onResume()
B : onStop() > onDestroy()
2. 设置启动模式的2种方式
- 清单文件
在清单文件中声明 Activity 时,您可以使用
元素的 launchMode 属性指定 Activity 应该如何与任务关联。
通过launchMode属性,指定上述四种启动模式
- Intent 标志
启动 Activity 时,您可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,
例如: intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
常用Flag:
* **FLAG_ACTIVITY_SINGLE_TOP**
和SingleTop作用相同
* **FLAG_ACTIVITY_CLEAR_TOP**
没有该Activity实例的情况下,没有意义,创建新实例;
存在该Activity实例的情况下,和该启动模式有关:
Standard:销毁任务栈中已存在的该Activity自身和它之上的所有Activity,重建实例
SingleTop: 销毁任务栈中其上的所有Activity,并回调onNewIntent()
SingleTask:同上,无意义,和不设置一样,回调onNewIntent(),就是SingTask模式
SingleInstance: 同上,就是SingleInstance模式
* **FLAG_ACTIVITY_NEW_TASK**
旨在找到该Activity属性taskAffity所指定的任务栈,创建实例,默认是和包名相同名称的任务栈。
**FLAG_ACTIVITY_NEW_TASK不等于 SingleTask**
和要启动的Activity的启动模式有关:
Standard:
不指定taskAffity,在包名任务栈,始终创建新的实例;
指定与包名不同任务栈,不存在的话,创建任务栈和新的实例;存在后无反应,startActivity无效
SingleTop:
不指定taskAffity,在包名任务栈,始终创建新的实例;
指定与包名不同任务栈,不存在的话,创建任务栈和新的实例;存在后无反应,startActivity无效
SingleTask:
在taskAffity指定的任务栈中,销毁其上的Activity,回调onNewIntnet(),不设置该Flag也一样,就是Singletask效果
SingleInstance:
在taskAffity指定的任务栈中,SingleInstance效果,回调onNewIntent,不设置该Flag也一样
**FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP也不等于 SingleTask**
和要启动的Activity的启动模式有关:
Standard:销毁任务栈中已存在的该Activity自身和它之上的所有Activity,重建实例
SingleTop:SingleTask效果,销毁其上的Activity,回调onNewIntnet()
SingleTask:singletask效果,销毁其上的Activity,回调onNewIntnet(),不设置该Flag也一样
SingleInstance:SingleInstance效果,回调onNewIntent,不设置该Flag也一样
以上,测试环境为Android P
3. taskAffity 和 allowTaskReparenting
taskAffinity表示与 Activity 有着亲和关系的任务。也就是Activity应该存在的任务栈。属性取字符串值,该值必须不同于在
在两种情况下,关联会起作用:
- 启动 Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志
将会在taskAffinity所指定的任务栈中创建Activity。
效果参考上述FLAG_ACTIVITY_NEW_TASK
- allowTaskReparenting = true
该属性含义是:是否允许 Activity 在与它有着亲和关系的任务(taskAffity指定)到达前台时,转移到那个任务。也就是Activity应该存在的任务栈。
试验场景:
A应用,B应用,B应用中的C Activity, C设置allowTaskReparenting = true
在A中打开C,startActivity 时,设置FLAG_ACTIVITY_NEW_TASK,如果不设置没有转移任务的效果
结论:
如果B应用任务栈存在,C直接在B中创建实例,位于栈顶 ;只要A任务切换到后台,再回到前台,都看不到C了,看到的是C之前的A应用中自己的页面;中间不管是点击桌面B的laucher按钮,还是从任务栈中将B切换到前台,显示的都是C
如果B应用任务栈不存在,会创建C的taskAffity所指定的任务栈,默认是B的包名;同样A切换到后台再回前台,只能看到C之前的页面; 启动B应用时,会直接显示C页面
六、Intent和IntentFilter
1. Intent的作用
Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作。四大组件中有Activity、Service、BroadcastReceiver需要使用Intent来启动。
2. Intent的分类
-
显式Intent
Activity的显式启动,就是使用显式Intent;需要指定目标组件名,也是类名;
使用显式Intent,系统将立即启动 Intent 对象中指定的应用组件
-
隐式Intent
Activity的隐式启动,就是使用隐式Intent;不需要指定类名,需要制定Action,一个Intent只能有一个Action;
使用隐式Intent,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件,如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。
如果没有找到对应的组件,会崩溃,处理方式如下:
// 创建一个隐式Intent
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");
// 校验系统是否存在对应的Activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
3. IntentFilter是什么
Intent 过滤器是应用清单文件中的一个表达式,它指定该组件要接收的 Intent 类型。
如果 Activity 声明 Intent 过滤器,您可以使其他应用能够直接使用某一特定类型的 Intent 启动 Activity。同样,如果您没有为 Activity 声明任何 Intent 过滤器,则 Activity 只能通过显式 Intent 启动。
// 我们最熟悉的过滤器
* ACTION_MAIN 操作指示这是主要入口点,且不要求输入任何 Intent 数据。
* CATEGORY_LAUNCHER 类别指示此 Activity 的图标应放入系统的应用启动器。 如果 元素未使用 icon 指定图标,则系统将使用 元素中的图标。
特别注意:
为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 启动Service,系统会引发异常(Service Intent must be explicit)。
4. 匹配规则
- action
...
action是一个字符串,系统预定了一些,我们也可自定义。
action匹配要求如果IntentFilter中有action,则Intent中必须存在action且必须和IntentFilter中的其中一个action匹配,另外action字符串区分大小写。
- category
...
category是一个字符串,系统预定了一些,我们也可自定义。
category匹配要求Intent中可以没有category,但intent可以有多个category,所以如果一旦有category,则Intent的每个category都必须是IntentFilter中的一个。
另外startActivity和startActivityForResult都默认为Intent增加一个intent.addCategory(Intent.CATEGORY_DEFAULT);
,所以,为了我们的Activity能够接受隐式调用时,需要给我们的Activity 的IntentFilter增加这个Default category
- data
// 合并写法
...
// 分开写法
...
// 都一样
data由MimeType和Uri构成
-
Uri构成:
例如::// : / http://www.baidu.com:80/a
- scheme: Uri模式,如http,file,content等
- host: 主机名
- port: 端口号
- path: 路径
线性依赖关系,scheme未指定,则后面其他参数无效,
MIME:
媒体类型,由两部分组成,前面是数据的大类别,例如声音audio、图象image等,后面定义具体的种类,用来表示不同的文本、图片、视频等类型。如:text/plain、image/jpeg
data匹配要求如果IntentFilter中有data,则Intent必须含有data,并且要完全匹配IntentFilter中的其中一项。
IntentFilter中Uri默认为content或file,如果未指定Uri,Intent种的scheme部分必须为content或file,才能匹配。
Intent需要设置Uri和MimeType时,要使用setDataAndType()
方法,因为单独调用setData()
和setType()
,会互相清除数据。
七、任务和返回栈
任务是指在执行特定作业时与用户交互的一系列 Activity,启动Activity为根Activity,其它为子Activity。 Android系统中所有的 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。 堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。 因此,返回栈以“后进先出”对象结构运行。
任务分为前台任务(正在与用户交互的)和后台任务,后台可以同时运行多个任务。但是,如果用户同时运行多个后台任务,则系统资源紧张时可能会开始销毁后台 Activity,以回收内存资源,从而导致 Activity 状态丢失
清理返回栈
如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。
您可以使用下列几个 Activity 属性修改此行为:
- alwaysRetainTaskState
如果在任务的根 Activity 中将此属性设置为 "true",则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。
- clearTaskOnLaunch
如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与 alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。
- finishOnTaskLaunch
此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 设置为 "true" 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。
八、Activity使用小技巧
1. 启动Activity的最佳写法
A启动B,需要传递参数,自己开发需要每次去看B需要接收哪些参数;分工合作,别人开发B,你需要问他需要哪些参数,参数代表什么含义。所以,我们可以在B中定义一个静态方法,如下:
/**
* 启动 B Activity
* @param context 源组件
* @param title 标题
* @param id id
*/
public static void actionStart(Context context,String title,String id){
Intent intent = new Intent(context,SecondActivity.class);
intent.putExtra("title",title);
intent.putExtra("id",id);
context.startActivity(intent);
}
在A中调用如下:
SecondActivity.actionStart(this,"启动Activity的最佳写法","888");
2. 快速退出程序
详情见百度搜索Activity管理类第一条,思路是:
创建一个集合,在BaseActivity的onCreate()方法中添加Activity,在onDestroy()中删除Activity,退出程序时,遍历集合中所有Activity调用finish(),将该应用的所有Activity销毁
3. Activity的Dialog样式
或
// 继承AppCompatActivity时使用
4. Activity转场动画
通过重写overridePendingTransition(int enterAnim, int exitAnim)
实现的。
参数:
- enterAnim,将要打开的Activity的进入动画id
- exitAnim,将要退出的Activity的退出动画id
这个方法在startActivity(Intent)或finish()之后会被立即调用。
动画使用补间动画
,在res/anim下创建动画文件,需要以
为根节点。
下面我们以MainActivity打开SecondActivity示例:
left_out.xml
right_in.xml
MainActivity使用
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.right_in,R.anim.left_out);
left_in.xml
right_out.xml
SecondActivity使用
@Override
public void onBackPressed() {
// 这里会调用finish
super.onBackPressed();
overridePendingTransition(R.anim.left_in,R.anim.right_out);
}
如上,即可实现在打开和退出SecondActivity时有动画效果,不过,要是每个Activity都这样写会很麻烦,所以,升级版如下:
value/styles.xml
以上,通过设置一个Activity切换样式,然后设置给我们应用的主题,最后应用主题,这样我们的应用就会有Activity切换效果了,而不需要每个Activity都去调用overridePendingTransition方法了。
个人总结,水平有限,如果有错误,希望大家能给留言指正!如果对您有所帮助,可以帮忙点个赞!如果转载,希望可以留言告知并在显著位置保留草帽团长的署名和标明文章出处!最后,非常感谢您的阅读!