关于Activity
前言(Activity)
官方简介
Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
一个应用通常由多个彼此松散联系的 Activity 组成。 一般会指定应用中的某个 Activity 为“主”Activity,即首次启动应用时呈现给用户的那个 Activity。 而且每个 Activity 均可启动另一个 Activity,以便执行不同的操作。
如何使用
自定义一个Activity,继承Activity,在onCreate()
生命周期方法中setContentView(layoutId)
(layoutId
是自定义的布局文件),然后在清单文件中注册该Activity。
重要方法
-
启动 Activity 的方法:
startActivity(Intent intent)
-
结束 Activity 的方法:
finish()
、finishActivity(requestCode)
-
三组生命周期方法
- 完整生存周期:
onCreate()
~onDestroy()
- 可见生存周期:
onStart()
~onStop()
- 前台(可操作)生存周期:
onResume()
~onPause()
- 完整生存周期:
-
数据回收和恢复
-
onSaveInstanceState(Bundle outState)
在Activity销毁前保存重要数据,在onPause方法之后被调用 -
onRestoreInstanceState(Bundle savedInstanceState)
恢复数据,在onResume()方法之前调用 -
onCreate(Bundle savedInstanceState)
这里也可以恢复数据
-
生命周期方法详解(官方文档)
方法 | 说明 | 是否能事后终止 | 后接 |
---|---|---|---|
onCreate() | 首次创建 Activity 时调用。 您应该在此方法中执行所有正常的静态设置 — 创建视图、将数据绑定到列表等等。 系统向此方法传递一个 Bundle 对象,其中包含 Activity 的上一状态,不过前提是捕获了该状态(请参阅后文的保存 Activity 状态)。始终后接 onStart()。 | 否 | onStart() |
onRestart() | 在 Activity 已停止并即将再次启动前调用。始终后接 onStart() | 否 | onStart() |
onStart() | 在 Activity 即将对用户可见之前调用。如果 Activity 转入前台,则后接 onResume(),如果 Activity 转入隐藏状态,则后接 onStop()。 | 否 | onResume() 或 onStop() |
onResume() | 在 Activity 即将开始与用户进行交互之前调用。 此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。始终后接 onPause()。 | 否 | onPause() |
onPause() | 当系统即将开始继续另一个 Activity 时调用。 此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。 它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行。如果 Activity 返回前台,则后接 onResume(),如果 Activity 转入对用户不可见状态,则后接 onStop()。 | 是 | onResume() 或 onStop() |
onStop() | 在 Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。如果 Activity 恢复与用户的交互,则后接 onRestart(),如果 Activity 被销毁,则后接 onDestroy()。 | 是 | onRestart() 或 onDestroy() |
onDestroy() | 在 Activity 被销毁前调用。这是 Activity 将收到的最后调用。 当 Activity 结束(有人对 Activity 调用了 finish()),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。 您可以通过 isFinishing() 方法区分这两种情形。 | 是 | 无 |
特别介绍:onNewIntent()
,该方法在要启动的Activity已经存在,并且不需要重新创建时调用。
启动方式
显式启动:
//方式1
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
//方式2
Intent intent = new Intent();
ComponentName componentName = new ComponentName(this,SecondActivity.class);
intent.setComponent(componentName);
startActivity(intent);
//方式3
Intent intent = new Intent();
intent.setClassName("com.hdib","com.hdib.SecondActivity");
startActivity(intent);
// 方式4
startActivityForResult(intent,requestCode);
隐式启动(详见 Intent匹配规则):
//案例1(发送电子邮件)
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
一、Activity启动流程
Activity A 启动 Activity B
Activity A 的 onPause() 方法执行。
Activity B 的 onCreate()、onStart() 和 onResume() 方法依次执行。(Activity B 现在具有用户焦点。)
然后,如果 Activity A 在屏幕上不再可见,则其 onStop() 方法执行。
Launcher启动 MainActivity
-
Launcher
组件向ActivityManagerService
发送一个启动MainActivity
的进程间通信请求。 -
ActivityManagerService
首先将要启动的MainActivity
信息保存起来,然后再向Launcher发送一个进入终止状态的进程间通信请求。 -
Launcher
组件进入终止状态后就会向ActivityManagerService
发送一个已经进入终止状态的进程间通信请求,以便ActivityManagerService
可以继续执行MainActivity
的启动。 -
ActivityManagerService
发现用于运行MainActivity
的应用程序进程不存在,因此会先启动一个新的应用程序进程。 - 新的应用程序进程启动后就会向
ActivityManagerService
发送一个启动完成的进程间通信请求,以便ActivityManagerService
可以继续执行MainActivity
的启动。 -
ActivityManagerService
将第二步保存起来的MainActivity
信息发送第四步创建的新的应用程序进程,以便将MainActivity
启动起来。
一、Activity启动模式
launchMode | Description |
---|---|
standard | 默认模式,多实例模式。系统总是会启动一个新的Activity来满足要求——即便已经存在该Activity。 并且它总是归属于调用 startActivity() 将其启动的那个task。 |
singleTop | 共同点:同上。 不同点:当该Activity已经被启动并且位于目标Task的栈顶时,就通过 onNewIntent() 方法将Intent传递给该Activity,而不是新启动一个Activity。 |
singleTask | 这样的Activity在一个Task中只能有一个。 如果该Activity已经启动,那么将通过 onNewIntent() 方法将Intent传递给它,并清空栈顶Activity,同时将包含剩余Activity的整个Task移到前台。这种模式允许与其他Activity在相同的task中,只是要保证该Activity在Task中只有一个就可以了。 |
singleInstance | 单实例模式,永远单独在一个task中。启动该Activity时,如果Activity实例不存在,一定会重新创建task并将该Activity实例放入其中。 |
Flag
Flag | Description |
---|---|
FLAG_ACTIVITY_NEW_TASK |
等同于singleTask 模式。以下flag需要与这个flag一起用。FLAG_ACTIVITY_CLEAR_TASK :清除栈中其他Activity。FLAG_ACTIVITY_TASK_ON_HOME :新启动的Activity放在task栈中Launcher的上面。FLAG_ACTIVITY_MULTIPLE_TASK :阻止系统恢复一个现有的task,也就是每次都新启动一个task。FLAG_ACTIVITY_LAUNCH_ADJACENT :仅用于分屏多窗口模式,新启动的Activity显示在启动它的Activity旁边,如果需要创建现有Activity的新实例,应同时设置FLAG_ACTIVITY_MULTIPLE_TASK 。 |
FLAG_ACTIVITY_BROUGHT_TO_FRONT |
launchMode中设置singleTask 时会自动加上这个标志。如果该Activity已经被创建,而且存在于某个task中,此时另外一个task启动该Activity,系统会自动清理该Activity之上的所有Activity |
FLAG_ACTIVITY_SINGLE_TOP |
等同于singleTop 模式。 |
FLAG_ACTIVITY_CLEAR_TOP |
如果要启动的Activity已经在栈中,那么需要清除在该Activity之上的所有Activity,并通过onNewIntent() 方法将Intent传递给该Activity |
FLAG_ACTIVITY_PREVIOUS_IS_TOP |
类似于FLAG_ACTIVITY_CLEAR_TOP
|
FLAG_ACTIVITY_NO_HISTORY |
该Activity将不会被保存在History Stack 中。 |
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
该Activity不会被放在系统最近启动的Activity列表中。 如进程列表截图中不会有这个页面(会获取上一个页面的截图) 按home键回到桌面后,在点击桌面图标应用重新回到前台时,也不会显示这个页面,而是显示上一个页面。 |
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED |
无论Activity的目标task是新task还是现有task,都会处于task的上端。 一般要 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 标识配合使用,不过该标识 API 21 以后过时,FLAG_ACTIVITY_NEW_DOCUMENT 取代之。该Activity启动时,如果目标栈中已经有该Activity,那么清除其上面的所有Activity。 |
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY |
系统自动设置,从历史记录中启动 |
FLAG_ACTIVITY_NO_USER_ACTION |
可使onUserLeaveHint() 回调不执行,该方法用于指示用户将要离开,Activity会退出前台。 |
FLAG_ACTIVITY_REORDER_TO_FRONT |
如果Activity已经在History stack中,则调整顺序使其到栈顶。 |
FLAG_ACTIVITY_NO_ANIMATION |
无动画启动Activity |
FLAG_ACTIVITY_FORWARD_RESULT |
该Flag不能和startActivityForResult() 同时使用,表示不接受onActivityResult() 回调。该Flag启动的Activity如果调用了 setResult() 方法,那么回调结果不会传递给启动它的Activity,而是传递给前一个Activity。 |
FLAG_ACTIVITY_MATCH_EXTERNAL |
Android P Developer Priview中加入 |
设置后,如果设备上没有能够处理该intent的app,那么将会启动一个instant app来进行处理。 | |
FLAG_ACTIVITY_RETAIN_IN_RECENTS |
默认情况下通过FLAG_ACTIVITY_NEW_DOCUMENT 启动的activity在关闭之后,task中的记录会相对应的删除。如果为了能够重新启动这个activity你想保留它,就可以使用这个flag,最近的记录将会保留在接口中以便用户去重新启动。接受该flag的activity可以使用autoRemoveFromRecents 去复写这个request或者调用Activity.finishAndRemoveTask() 方法。 |
FLAG_DEBUG_LOG_RESOLUTION |
在处理这个intent的时候,将会打印相关创建日志。 |
FLAG_RECEIVER_NO_ABORT |
如果这是一个有序广播,不允许接受者终止这个广播,它仍然能够传递给下面的接受者。 |
FLAG_RECEIVER_REGISTERED_ONLY |
如果设置了这个flag,当发送广播的时,动态注册的接受者才会被调用,在AndroidManifest.xml 里定义的Receiver 是接收不到这样的Intent的。 |
FLAG_RECEIVER_REPLACE_PENDING |
如果设置了的话,ActivityManagerService 就会在当前的系统中查看有没有相同的intent还未被处理,如果有的话,就由当前这个新的intent来替换旧的intent,所以就会出现在发送一系列的这样的 Intent 之后,中间有些 Intent 有可能在你还没有来得及处理的时候,就被替代掉了的情况。 |
FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS |
设置之后,广播将对instant app中的广播接收器可见。默认不可见。 |
FLAG_GRANT_READ_URI_PERMISSION |
读权限,允许组件从Intent中包含的URI里边读取数据。 |
FLAG_GRANT_WRITE_URI_PERMISSION |
写权限,允许组件向Intent中包含的URI里边写入数据。 |
FLAG_FROM_BACKGROUND |
该Intent是一个后台操作。 |
关于startActivityForResult()
因为 activity 的很多启动模式或 flag 具有清除栈内已有 activity 的效果,清除实际是调用的系统的 remove task 方法,该方法会使得被清除的 activity 执行 onDestory()
等方法销毁,同时如果被销毁的 activity 是被其他 activity 用startActivityForResult()
方法启动的,销毁时会给它的启动 activity 传递回去 result cancel 事件,启动方 activity 的onActivityResult()
方法会被调用,这个时候容易出现我们意料之外的问题。
从 Task 的角度看,Android 认为不同 Task 之间的 Activity 是不能传递数据的,因此如果启动用startActivityForResult()
方式启动新 activity,而新启动的 activity 和当前 activity 不在同一个栈时,当前 activity 的onActivityResult()
方法回马上被回调,并且传递回result cancel
事件,可以从 AMS 打出的 log 上看到有这么一句:
WARN/ActivityManager(67): Activity is launching as a new task, so cancelling activity result.
就是这个意思,下面startActivityForResult()
的注释也进行了说明。
三、数据传递
Intent传递数据
putExtra("key","value");
String str = getStringExtra(“key”);
Bundle传递数据
Bundle bundle = new Bundle();
bundle.putString("key","value");
intent.putExtras(bundle);
String str = getIntent().getExtras().getString("key");
四、Intent匹配规则
显式启动,直接用ComponentName
定位到确定的组件并启动。
隐式启动,与三个因素有关:Category
、Action
、Data
。Extras
和Flag
只在目标组件已经选择好准备启动时才起作用。这些因素中的一个组合就是一个intent-filter
。
隐士启动匹配规则:至少有一个intent-filter
匹配成功,才能启动该组件。
几个注意事项:
-
Intent
中如果不指定Category
那么系统会默认添加CATEGORY_DEFAULT
。 - 如果
intent-filter
中action
为空,那么所有的Intent
都不能与之匹配。 - 如果
intent-filter
中有非空action
,那么Intent
中的action
必须为空,或是其子集才能与之匹配。 -
Intent
的data
没有指定MIME
也没有指定URI
,此时只有intent-filter
中也不指定相应值才能匹配。 -
Intent
的data
没有指定MIME
(也无法从URI推断出来),但有指定URI
,此时只有intent-filter
中也不指定MIME
,且URI
符合要求,才能匹配。 -
Intent
的data
指定了MIME
,但没有指定URI
,此时只有intent-filter
中也指定MIME
且不指定URI
,才能匹配。 -
Intent
的data
指定了MIME
,也指定了URI
,此时只有intent-filter
中也指定MIME
且URI
符合要求,才能匹配。
ComponentName
指定包名和全类名,直接定位到要启动的组件。好比一个人的名字,可以直接定位到这个人。
Category
从大的方向上对Intent进行了分类。好比国籍,可以定位到这个人所在的国家,不能定位到这个人。
一般来说,CATEGORY_DEFAULT
可以解决所有问题,Intent默认指定的也是该值。
以下是标准Category。
Category Name | Description |
---|---|
CATEGORY_DEFAULT |
目标方对于默认的Action是一个可选项。 |
CATEGORY_BROWSABLE |
目标方能正确显示链接所指向的内容,如图片、网页等。能够被浏览器安全调用的组件必须制定该值。 |
CATEGORY_TAB |
目标方可以在已有的TabActivity内部作为一个Tab使用。 |
CATEGORY_ALTERNATIVE |
目标方是能正确打开用户正在浏览的数据的一个选择。 |
CATEGORY_SELECTED_ALTERNATIVE |
目标方能正确打开用户已经选择的数据。 |
CATEGORY_LAUNCHER |
目标方可以通过点击Launcher中的图标来启动。 |
CATEGORY_INFO |
目标方用于提供包信息,当应用没有CATEGORY_LAUNCHER 组件时使用。 |
CATEGORY_HOME |
Launcher,系统启动后启动的第一个组件。 |
CATEGORY_PREFERENCE |
该组件是选项卡 |
CATEGORY_TEST |
测试使用(一般情况不使用) |
CATEGORY_CAR_DOCK |
手机被插入汽车底座(硬件)时启动目标方。 |
CATEGORY_DESK_DOCK |
手机被插入桌面底座(硬件,柜台展示样机)时启动目标方。 |
CATEGORY_LE_DESK_DOCK |
|
CATEGORY_HE_DESK_DOCK |
|
CATEGORY_CAR_MODE |
目标方可在车载环境下使用。 |
CATEGORY_APP_MARKET |
用来指定目标方是应用市场。 |
CATEGORY_VR_HOME |
目标方作为VR启动页面。 |
以下Category
,以后可能不兼容。
Category Name | Description |
---|---|
CATEGORY_VOICE |
目标方能与用户进行语音交互,可能不需要UI展示。 |
CATEGORY_LEANBACK_LAUNCHER |
目标方将在LEANBACK 模式时启动。leanback,指可以像看电视一样向后靠着看,形容清晰度高。 |
CATEGORY_CAR_LAUNCHER |
目标方将在CAR_LAUNCHER 模式时启动。 |
CATEGORY_LEANBACK_SETTINGS |
目标方将作为LEANBACK 模式中一个设置页面。 |
CATEGORY_DEVELOPMENT_PREFERENCE |
该组件是一个开发者选项卡。 |
CATEGORY_EMBED |
可以运行在父Activity容器内。 |
CATEGORY_MONKEY |
目标方可以被monkey或者其他的自动测试工具执行。 |
CATEGORY_UNIT_TEST |
单元测试使用。 |
CATEGORY_SAMPLE_CODE |
Sample组件 |
CATEGORY_OPENABLE |
用来指示一个GET_CONTENT 意图希望只有ContentResolver.openInputStream 能够打开URI。 |
CATEGORY_TYPED_OPENABLE |
用来打开文件或流,使用openFileDescriptor 、openTypedAssetFileDescriptor 、getStreamTypes 方法。 |
CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST |
用于测试时,作为framework层测试代码使用。 |
CATEGORY_APP_BROWSER |
和ACTION_MAIN 一起使用,用来指定目标方是浏览器应用。 |
CATEGORY_APP_CALCULATOR |
和ACTION_MAIN 一起使用,用来指定目标方是计算器应用。 |
CATEGORY_APP_CALENDAR |
和ACTION_MAIN 一起使用,用来指定目标方是日历应用。 |
CATEGORY_APP_CONTACTS |
和ACTION_MAIN 一起使用,用来指定目标方是联系人应用。 |
CATEGORY_APP_EMAIL |
和ACTION_MAIN 一起使用,用来指定目标方是邮件应用。 |
CATEGORY_APP_GALLERY |
和ACTION_MAIN 一起使用,用来指定目标方是图库应用。 |
CATEGORY_APP_MAPS |
和ACTION_MAIN 一起使用,用来指定目标方是地图应用。 |
CATEGORY_APP_MESSAGING |
和ACTION_MAIN 一起使用,用来指定目标方是短信应用。 |
CATEGORY_APP_MUSIC |
和ACTION_MAIN 一起使用,用来指定目标方是音乐应用。 |
Action
启动Activity使用。
Action | Description |
---|---|
ACTION_MAIN |
应用程序入口。 |
ACTION_VIEW |
显示数据给用户。 |
ACTION_ATTACH_DATA |
指明附加信息。 |
ACTION_EDIT |
显示可编辑数据。 |
ACTION_PICK |
显示可选择数据。 |
ACTION_CHOOSER |
选择器。 |
ACTION_GET_CONTENT |
用于获取信息。 |
ACTION_DIAL |
显示打电话面板。 |
ACTION_CALL |
直接打电话。 |
ACTION_SEND |
直接发短信。 |
ACTION_SENDTO |
选择联系人发短信。 |
ACTION_ANSWER |
应答电话。 |
ACTION_INSERT |
插入数据。 |
ACTION_DELETE |
删除数据。 |
ACTION_RUN |
运行数据。 |
ACTION_SYNC |
同步数据。 |
ACTION_PICK_ACTIVITY |
选择Activity。 |
ACTION_SEARCH |
搜索。 |
ACTION_WEB_SEARCH |
Web搜索。 |
ACTION_FACTORY_TEST |
工厂测试入口点。 |
系统广播使用。
Action | Description |
---|---|
ACTION_TIME_TICK |
系统时间,1分钟发一次广播 |
ACTION_TIME_CHANGED |
系统时间通过设置发生了变化。 |
ACTION_TIMEZONE_CHANGED |
时区改变。 |
ACTION_BOOT_COMPLETED |
系统启动完毕。 |
ACTION_PACKAGE_ADDED |
新的应用程序apk包安装完毕。 |
ACTION_PACKAGE_CHANGED |
现有应用程序apk包改变。 |
ACTION_PACKAGE_REMOVED |
现有应用程序apk包被删除。 |
ACTION_PACKAGE_RESTARTED |
应用重启。 |
ACTION_PACKAGE_DATA_CLEARED |
应用数据被清理。 |
ACTION_PACKAGES_SUSPENDED |
应用被挂起。 |
ACTION_PACKAGES_UNSUSPENDED |
应用被解除挂起状态,恢复正常。 |
ACTION_UID_REMOVED |
用户id被删除。 |
ACTION_BATTERY_CHANGED |
电池信息变化,包括电量变化。 |
ACTION_POWER_CONNECTED |
外接电源,如充电宝。 |
ACTION_POWER_DISCONNECTED |
外接电源拔出。 |
ACTION_SHUTDOWN |
设备关机。 |
Data
data属性有以下5部分组成:
android:scheme
android:host
android:port
android:path
android:mimeType
data的前四个属性构成了URI(scheme://host:port/path
),mimeType设置了数据的类型。
附:参考
官方文档。
深入理解Android内核设计思想.林学森
SDK源码。