关于Activity

1,331 阅读13分钟

前言(Activity)

官方简介

Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。

一个应用通常由多个彼此松散联系的 Activity 组成。 一般会指定应用中的某个 Activity 为“主”Activity,即首次启动应用时呈现给用户的那个 Activity。 而且每个 Activity 均可启动另一个 Activity,以便执行不同的操作。

如何使用

自定义一个Activity,继承Activity,在onCreate()生命周期方法中setContentView(layoutId)layoutId是自定义的布局文件),然后在清单文件中注册该Activity。

重要方法

  • 启动 Activity 的方法:startActivity(Intent intent)

  • 结束 Activity 的方法:finish()finishActivity(requestCode)

  • 三组生命周期方法

    1. 完整生存周期:onCreate() ~ onDestroy()
    2. 可见生存周期:onStart() ~ onStop()
    3. 前台(可操作)生存周期: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

  1. Launcher组件向ActivityManagerService发送一个启动MainActivity的进程间通信请求。
  2. ActivityManagerService首先将要启动的MainActivity信息保存起来,然后再向Launcher发送一个进入终止状态的进程间通信请求。
  3. Launcher组件进入终止状态后就会向ActivityManagerService发送一个已经进入终止状态的进程间通信请求,以便ActivityManagerService可以继续执行MainActivity的启动。
  4. ActivityManagerService发现用于运行MainActivity的应用程序进程不存在,因此会先启动一个新的应用程序进程。
  5. 新的应用程序进程启动后就会向ActivityManagerService发送一个启动完成的进程间通信请求,以便ActivityManagerService可以继续执行MainActivity的启动。
  6. 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定位到确定的组件并启动。
隐式启动,与三个因素有关:CategoryActionDataExtrasFlag只在目标组件已经选择好准备启动时才起作用。这些因素中的一个组合就是一个intent-filter

隐士启动匹配规则:至少有一个intent-filter匹配成功,才能启动该组件。

几个注意事项:

  1. Intent中如果不指定Category那么系统会默认添加CATEGORY_DEFAULT
  2. 如果intent-filteraction为空,那么所有的Intent都不能与之匹配。
  3. 如果intent-filter中有非空action,那么Intent中的action必须为空,或是其子集才能与之匹配。
  4. Intentdata没有指定MIME也没有指定URI,此时只有intent-filter中也不指定相应值才能匹配。
  5. Intentdata没有指定MIME(也无法从URI推断出来),但有指定URI,此时只有intent-filter中也不指定MIME,且URI符合要求,才能匹配。
  6. Intentdata指定了MIME,但没有指定URI,此时只有intent-filter中也指定MIME且不指定URI,才能匹配。
  7. Intentdata指定了MIME,也指定了URI,此时只有intent-filter中也指定MIMEURI符合要求,才能匹配。

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 用来打开文件或流,使用openFileDescriptoropenTypedAssetFileDescriptorgetStreamTypes方法。
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源码。