Activity 进阶

Activity

一、四种形态

  1. 运行状态: 当 Activity 处于栈的顶层,可见,并可与用户进行交互 onResume()--> onPause()

  2. 暂停状态: 当 Activity 被非全屏的或者透明的 Activity 遮挡后,原来的 Activity 处于暂停状态 onPause() 被调用 ,系统内存极低时会被回收

  3. 停止状态: 当 Activity 被置于后台(例如被其它Activity完全遮挡,或者用户按下 HOME 键回到桌面) onStop() 被调用,系统内存极低时会被回收

  4. 终止状态: 当 Activity 从未被创建过或者 Activity 被销毁(调用了 Activity 的 finish() 方法或者用户按下 BACK 键,表现为 onDestory() 被调用,但不一定释放了内存)

二、生命周期

正常声明周期

  1. onCreate() 表示 Activity 正在被创建,执行加载布局资源、初始化数据等

  2. onRestart() 当 Activity 从不可见重新变见变为可见时调用

  3. onStart() 当 Activity 正在被启动,Activity 已经可见,但是还没用开始活动,还无法和用户进行交互,Activity 的布局界面还不可见

  4. onResume() 当 Activity 可见,并且已经开始活动,可与用户进行交互,并且在这个方法中将布局显示到屏幕上

  5. onPause() 当 Activity 失去焦点,不可与用户交互,暂停,可以做一些资源回收,停止动画操作,不能耗时

  6. onStop() 当 Activity 完全进入后台,可以做一些稍微重量级的回收工作,不能耗时

  7. onDestory() 表示 Activity 即将被销毁,可以做一些回收工作和最终的资源释放

生命周期的注意问题

  1. 当Activity被激活时: onCreate() -> onStart() -> onResume()

  2. 当 Activity 被销毁时:onPause() -> onStop() -> onDestory()

  3. 当 Activity 从不可见重新变见变为可见时,会经历:onStop() -> onRestart() -> onStart() -> onResume()

  4. 当新的 Activity 被激活时:原来的 Activity 先 onPause(),当新的 Activity 已经完全激活(显示在前台)时,原来的 Activity 会 onStop()
    onPause() --> onCreate() --> onStart() --> onResume() --> onStop()

  5. 当 A_Activity 被 B_Activity 不完全遮挡,B_Activity 为透明或非全屏,B_Activity 被销毁时:A_Activity 被重新显示,会经历:onPause() -> onResume()

  6. 非透明 Activity 启动透明或非全屏 Activity ,第一个 Activity 不会执行 onStop ,因为一直可见

  7. onPause() 方法是系统能销毁当前 Activity 之前调用的最后一个方法,所以一些数据的保存应该放在onPause() 方法中,但是 onPause() 方法中不能有太多操作,因为onPause() 中任何一个阻塞的过程都会影响向下一个Activity 转换

  8. Activity 的 onDestory 调用之后,其中如果有子线程或异步任务在运行,Thread 和 AsyncTask 并不会停止,会引起 Activity 内存泄漏,必须在 onDestory 方法中处理这些线程和 AsyncTask

  9. 在 AsyncTask 或者 handler 的返回方法中,必须对需要处理的 UI 使用弱引用,这样不但可以保证 Activity 中的 UI 控件被正常回收,并且在处理 UI 控件时做判空处理,还能保证在 UI 不存在时不会抛出异常

  10. 调用 finish 方法时的声明周期 onPause --> onStop --> onDestory

非正常生命周期

  1. 第一种非正常生命周期发生在当资源相关的系统配置发生改变时,例如横竖屏切换时加载的资源会发生不同,导致 Activity 被杀死并重建

  2. 第二种非正常生命周期发生情况是在系统内存不足时导致低优先级的 Activity 被杀死,并在重启 Activity 时重建

  3. 非正常生命周期中 onSaveInstanceState() 方法中保存状态数据,onCreate() 和 onRestoreInstanceState() 方法中都可以获取保存的数据并恢复状态

非正常生命周期注意问题
  1. 只有Activity 异常退出以及重建时以上两个方法才会被调用

  2. 系统销毁Activity时会调用保存数据的方法 onSaveInstanceState()

  3. onRestoreInstanceState() 方法调用在 onStart() 之后 onResume() 之前

onStart() --> onRestoreInstanceState() --> onResume()

  1. onSaveInstanceState 调用在 onStop 之前,与onPause 没有前后关系

  2. 系统在 onSaveInstanceState 方法中会调用有 id 的控件的对应方法保存状态,如果控件没有 id 则不会其保存

  3. 配置 configChanges 属性后,Activity 在相应的资源配置变化时不会调用上述两个方法,会调用,onConfigurationChanged 方法

  4. Activity 处于前台或者可见时如果系统资源不足,也会在回收 Activity 之前调用 onSaveInstanceState 方法保存数据

  5. onCreate 中使用保存的数据,需要对 Bundle 做判空验证,onRestoreInstanceState 方法如果被回调,则 Bundle 一定不为空,建议在 onRestoreInstanceState 方法中使用保存的数据

三、启动模式

standard 标准模式

  1. 被本应用或非本应用非 SingleInstance 的 Activity 启动时,每次都会在启动他们的那 Activity 所在的栈中创建一个新的 Activity,多次创建多次入栈

  2. 被本应用 singleInstance 的 Activity 启动时,如果存在以包名为名的栈,则将目标 Activity 入栈,如果不存在则创建以包名为栈名的栈,然后入栈

  3. 被非本应用 singleInstance 的 Activity 启动时,如果被启动的 Activity 所属应用未启动;将新创建的 Activity 放入栈名为包名新的栈中,并且只会启动一次,之后重复启动都不再重复创建新的 Activity,并且不执行 newIntent 方法

  4. 被非本应用 singleInstance 的 Activity 启动时,如果被启动的 Activity 所属应用已启动并且该应用当前栈顶 Activity 为 singleInstance,并且不存在栈名为包名的栈,则会创建栈名为包名的栈,然后将新启动 Activity 入该栈,之后重复启动都不重复创建新的 Activity,并且不执行 newIntent 方法

  5. 被非本应用 singleInstance 的 Activity 启动时,如果被启动的 Activity 所属应用已启动并且该应用当前栈顶 Activity 为 singleInstance,且存在栈名为包名的栈,会直接将新启动 Activity 入该栈,多次启动,多次创建并入该栈。

  6. 被非本应用 singleInstance 的 Activity 启动时,如果被启动的 Activity 所属应用已启动并且该应用当前栈顶 Activity 为非 singleInstance,不论当前栈顶 Activity 所在栈栈名是否为包名也不论是否有栈名为包名的栈,都会直接将新启动 Activity 入该栈,多次启动,多次创建并入该栈。

singleTop 栈顶复用模式

  1. singleTop 启动时需要入的栈同 standard

  2. 启动时,如果要加入的栈栈顶为需要启动的 Activity,此时不会重复启动,且会回调 onNewIntent 方法

  3. 启动时,如果要加入的栈栈顶不是需要启动的 Activity,此时会重复启动,且入栈

singleTask 栈内复用模式

  1. singleTask 启动时需要入的栈可以在注册时声明 TaskAffinity 属性,如果没有声明该属性,则需要入的栈同 standard ,不会创建新的任务和任务栈

  2. singleTask 注册时如果声明 TaskAffinity 属性,如果启动时该栈存在则直接入栈,不论要启动该 Activity 的应用是否是同一应用,并且模式为栈内复用,如果该栈不存在则创建新任务以及栈名为 TaskAffinity 参数的栈,并入栈,再启动是不会创建,onNewIntent 方法会被回调

  3. taskAffinity 值为一个字符串,其中必须包含包名分隔符 .

  4. 系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。

  5. 注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity,不论这个前 Activity 是不是本应用的,可以是桌面,其他应用的界面等。如果切换其他任务栈到前台,则启动一个在目标栈的 Activity 即可将目标栈切换到前台

  6. 这里有一个问题,就是如果普通栈和 singleTask 栈都存在,并且这时 singleTask 为后台栈,普通栈为前台,普通栈中的 Activity 都出栈之后,singleTask 的栈也不会显示到前台,要想将 singleTask 的栈切换到前台,只有再次调用 singleTask 栈中的 Activity 启动方法,此时 singleTask 栈中存在的 Activity 不会重新创建,会调用 onNewInent ,不过也会同时切换到前台

TaskAffinity 只有和 singleTask 模式结合使用时才有意义,其他启动模式时没意义

singleInstance 单实例模式

singleInstance 独自运行在一个任务栈中,不同应用启动统一个 singleInstance 模式的 Activity 时,该 Activity 只会创建一次,之后会调用 onNewIntent 方法

singleInstance 的 Activity 启动任何 Activity (除非自己) 都会将目标 Activity 放入其他的栈中

与 "singleTask" 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在其他的任务中打开。

注意:如果 singleInstance 的栈在后台,显示到前台的方法同 singleTask,以及切换其他栈到前台的方式也与 singleTask 的相同

allowTaskReparenting 属性

在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)

  1. 如果注册清单中为 Activity 配置 allTaskReparenting 属性为 true 之后,默认栈名为包名,可以通过 taskAffinity 来指定想要的栈,如果从其他任务栈启动该 Activity 时该 Activity 指定的栈不存在,此时不会创建新栈,会将目标 Activity 放入启动它的 Actiivty 所在栈,当目标 Activity 指定栈被创建并为前台栈时,例如启动该 Activity 所在的应用,此时已经启动的那个 Activity 会马上从启动它的应用的栈中跳回到本身指定的任务栈中,并获取栈顶位置,此时按返回键效果为显示该指定栈中的 Activity

  2. 如果从其他应用启动该 Activity 时该 Activity 指定栈已经存在,这时候从其他栈启动该 Activity 不会马上加入指定的栈中,同样需要在指定栈切换到前台时,例如屏幕首页点一下启动该应用的图标,这时候该 Activity 就会自动从启动它的应用的栈跳回本身所指定的栈中

启动模式注意问题

  1. 使用 singleTop/singleTask/singleInstance 时,如果当前界面是这个 Activity,那么再次启动时 onPause --> onNewIntent --> onResume 方法会依次回调,onNewIntent 中可以收到新的 Intent 数据,不过getIntent 得到的还是第一次启动时的 Intent

  2. 使用 singleTask/singleInstance 时,如果当前界面不是这个 Activity,那么再次启动时 onNewIntent --> onRestart --> onStart --> onResume 方法会依次回调,onNewIntent 中可以收到新的 Intent 数据,不过getIntent 得到的还是第一次启动时的 Intent

  3. 当注册时指定启动模式且通过 Intent 指定的启动模式同时存在,则以 Intent 中指定的为主

  4. 注册时指定启动模式不能实现 FLAG_ACTIVITY_CLEAR_TOP 效果

  5. Intent 指定启动模式不能实现 singleInstance 效果

  6. 某个任务栈中的 Activity 切换到前台,该任务栈也会切换到前台

  7. 如果某应用中启动了别的任务,在桌面点击该应用图标时,该应用自己的任务栈会回到前台

  8. 使用 singleTask 以及 singleInstance 时一定要做好测试,因为不同的情况会由不同的表现

通过设置 Intent 的 Flag 来设置启动模式

  1. FLAG_ACTIVITY_SINGLE_TOP 与 singleTop 效果相同

  2. FLAG_ACTIVITY_CLEAR_TOP 如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity (仅限于其启动模式不是 standart 的情况下),并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。

FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。

注:如果指定 Activity 的启动模式为 "standard",则该 Activity 也会从堆栈中移除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 "standard" 时,将始终为新 Intent 创建新实例。

  1. FLAG_ACTIVITY_NEW_TASK 在新任务中启动 Activity。如果需要启动的 Activity 存在及需要的任务也存在,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 onNewIntent() 中收到新 Intent。

如果已存在目标 Activity 实例,并且目标 Activity 不在栈顶,此时如果再调用启动目标 Activity 的代码,Activity 并不会显示到前台,因为 FLAG_ACTIVITY_NEW_TASK 的作用只是在没有目标 Activity 实例的情况下创建 Activity 实例,并将目标 Activity 入栈,所以存在 Activity 实例的情况下在调用启动方法只是将该任务会转到前台并恢复其最后状态,此时该 Activity 不一定在栈顶

FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP 结合 FLAG_ACTIVITY_SINGLE_TOP 一起使用时就可以达到 singleTask 的效果,FLAG_ACTIVITY_NEW_TASK 作用为保证目标 Activity 存在于 taskAffinity 指定栈名相同的栈,并将目标 Activity 入栈,FLAG_ACTIVITY_SINGLE_TOP 作用为如果栈中有目标 Activity,则将栈中目标 Activity 之上的 Activity 清除,所以就达到了 singleTask 的效果。

FLAG_ACTIVITY_NEW_TASK 要产生效果的前提一定是要启动的 Activity 的 taskAffinity 与当前 Activity 的是不相同的,否则就是默认 standard 的效果

  1. FLAG_ACTIVITY_CLEAR_TASK 标签,必须与 FLAG_ACTIVITY_NEW_TASK 配合使用,且必须是 taskAffinity 不同的情况下,如果两个条件不满足,则为 standard 模式

如果目标 Activity 所在栈不存在,则创建新栈并创建目标 Activity 对象,放入该栈

如果目标 Activity 存在,则将 Activity 及其所在栈中的所有 Activity 全部清空,包括已经存在的目标 Activity 对象,然后重新创建目标 Activity ,再放入目标栈中

  1. FLAG_ACTIVITY_NO_HISTORY 使用该标签启动的 Activity 再启动其他 Activity 之后,该 Activity 会自动消失,即 onDestory 被调用,不会保留在 Activity 栈中

...


四、任务栈

getTaskId() 方法用来获取 Activity 所在的任务栈 ID

Activity 栈中如果没有了 Activity 存在则会销毁,之后再由新 Activity 产生是会进入新的栈中。

在栈中返回时会先将当前栈中的 Activity 返回,全部返回之后则将跳转到该栈的界面所在的栈切换到前台,这个界面可以是屏幕首页,可以是其他应用

任务是指在执行特定作业时与用户交互的一系列 Activity。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中。

返回栈是任务的一部分 一个任务包含一个任务栈,任务栈中是一系列 Activity

Android 的多任务管理,即管理多个任务的前台后台变化

singleTask 如果指定 taskAffinity 或者 singleInstance 都是会启动新的任务

首页的启动图标作用为 第一:使 Activity 的图标和标签显示在应用启动器中 第二:可以让用户在启动之后可以随时切换到创建的任务栈中

通过为 Activity 提供一个以 "android.intent.action.MAIN" 为指定操作、以 "android.intent.category.LAUNCHER" 为指定类别的 Intent 过滤器,您可以将 Activity 设置为任务的入口点

第二点非常重要,其完成的任务是,如果用户切换其他任务栈到前台,则必须为用户提供方式当离开任务栈后还能再次切换到指定栈,首页图标则有这个功能。

IntentFilter 的匹配规则

  1. Activity 的 intent-filter 可以有多组,一个 Intent 只要能匹配任何一组 intent-filter 即可成功启动对应 Activity

  2. aciont 匹配规则:intent-filter 中指定一个或多个 action,Intent 中的 Action 必须匹配其中任意一个

intent-filter 中 action 可以多个,Intent 中必须要有唯一的 action,只要在该 Intent 在 intent-filter 存在即可匹配,action 大小写敏感
intent-filter 中有的 action 最少有一个,否则无法被隐式启动
Intent 中 action 必须也只能有一个,否则无法找到对应 Activity

  1. category 的匹配规则:Intent 中指定的 category 在 intent-filter 中必须能够找到

Intent 中有的 intent-filter 中必须有
Intent 中默认有 "ndroid.intent.category.DEFAULT" ,即 intent-filter 中至少有 DEFAULT

  1. data 匹配规则同 Intent ,intent-filter 中定义了 data,Intent 中必须存在最少一个data可以与 intent-filter 中的任一 data 匹配

  2. data 分为 mimeType 和 URI 两部分,intent-filter 中 URI 的默认支持 content 和 file ,Intent 中没有默认 URI 值,如果 intent-filter 中不声明 URI 和 type,此时 Intent 可以不包含 URI 和 type ,如果 Intent 中定义了 file 或 content 的 URI,intent-filter 中没有定义,此时 intent-filter 是支持的

  3. Intent 中调用 setDataAndType 来添加 URI 和 mimeType,setData 和 setType 方法会默认清除另一对象的值,所以同时设置 data 和 type 时必须使用 setDataAndType 方法

Intent

  1. 通用Intent

  2. 判断是否有接收 Intent 的组件,并在有多个响应的时候设置选择器名称

  3. 两种方式判断是否有接收 Intent 的组件,第一种是使用 PackageManager 的 resolve... 系列方法判断,返回值为 null 或者一个组件,以及使用 PackageManager 的 queryIntent... 系列方法判断,返回值为可以响应的组件集合,可以判断 service、activity、BroadcastReceiver

  4. 第二种是 Intent 的 resolveActivity 方法判断,如果有则返回该组件,如果没有返回 null

  5. 使用 PackageManager 的方法需要第二个参数,使用 PackageManager.MATCH_DEFAULT_ONLY 常量来剔除 intent-filter 中没有声明 默认 category 的响应,因为 Intent 默认有 category

  6. 强制使用选择器来让用户选择需要启动的 Activity

    Intent sendIntent = new Intent(Intent.ACTION_SEND);
    
    String title = getResources().getString(R.string.chooser_title);
    
    Intent chooser = Intent.createChooser(sendIntent, title);
    
    if (sendIntent.resolveActivity(getPackageManager()) != null) {
        startActivity(chooser);
    }
    
  7. 过滤器中必须指明至少一个action,Intent 必须有唯一一个 Action,且在过滤器中必须存在

  8. 过滤器中的 category 必须多于或等于 activity 启动组件使用的 intent 中设置的

  9. 过滤器中 data 匹配规则同 action ,如果 Intent 指明了 URL 而 过滤器中没有 URL,则判断结果为假定过滤器中支持 content 和 file 的 URL

  10. 为确保应用安全性,启动Service时,应始终使用显示 Intent,且不要为 Service 声明过滤器,因为使用隐式 Intent 启动服务存在安全隐患,因为不能确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。

推荐阅读更多精彩内容