注意当用户打开新的 Activity 或者切换到桌面的时候,回调如下: onPause -> onStop ,这里有一种特殊的情况,如果新的 Activity 采用了透明主题或 Dialog 主题,那么当前的 Activity 不会回调 onStop 。
onStart和onStop是从activity是否可见的角度来回调的。onResume和onPause是从activity是否定位于前台这个角度来回调的。
旧的Activity先onPause,新的Activity再启动。
当系统配置发生改变后,Activity 会被销毁,其 onPause 、 onStop 、 onDestroy 均会被调用,同时由于 Activity 是在异常情况下终止的,系统会调用 onSaveInstanceState 来保存当前 Activity 的状态。
这个方法的调用时机是在 onStop 之前,它和 onPause 没有既定的时序关系,可能在 onPause 之前,也可能在 onPause 之后。
该方法只会在 Activity 被异常销毁时才会被调用,正常情况下不会调用该方法。
当 Activity 被重新创建后,系统会调用 onRestoreInstanceState ,并且把 Activity 销毁时 onSaveInstanceState 方法所保存的 Bundle 对象作为参数同时传递给 onRestoreInstanceState 方法和 OnCreate 方法。
因此,我们可以通过 onRestoreInstanceState 方法和 OnCreate 方法判断 Activity 是否被重建了,进而恢复之前保存的数据,从时序上讲 onRestoreInstanceState 的调用时机在 onStart 之后。
同时,在 onSaveInstanceState 和 onRestoreInstanceState 方法中,系统已经自动为我们恢复了一些数据,如:用户输入的数据、 ListView 滚动的位置等,有关具体哪些信息被系统自动恢复,可以查看相应 View 的 onSaveInstanceState 和 onRestoreInstanceState 方法的具体实现。
比较难模拟内存不足的情况,Activity 的优先级一般为下面三种:
当系统内存不足时,系统会按照上述优先级去杀死目标 Activity 所在的进程,并在后续通过 onSaveInstanceState 和 onRestoreInstanceState 来保存和恢复数据;如果一个进程中没有四大组件在运行,那么这个进程很容易被杀死。
配置 Activity 的启动模式一般有一下两个方法:
1.通过 AndroidManifest.xml 为 Activity 指定启动模式;
<activity
android:launchMode="singleTask"
/>
2.通过在 Intent 中设置标志位来为 Activity 指定启动模式;
Intent intent = new Intent() ;
intent.setClass(xx , xxx.class) ;
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ;
startActivity(intent) ;
上面两种方式都可以为 Activity 指定启动方式,但是二者之间还是有区别的。
首先,优先级上,第二种启动方式的优先级要高于第一种,即两种指定都存在时,以第二种方式为准;其次,在限定范围上不同,例如,第一种无法为 Activity 设定 FLAG_ACTIVITY_CLEAR_TOP 标识,而第二种无法为 Activity 指定 singleInstance 模式。
注意 adb shell dumpsys activity 命令可以查看 Activity 启动的相关信息。
TaskAffinity 可以翻译为“任务相关性”,这个参数标示了一个 Activity 所需的任务栈名称,默认情况下,所有 Activity 的任务栈名都为应用包名;当然,我们也可以为每个 Activity 单独指定 TaskAffinity 属性,如果该属性值与应用包名一样,就相当于没有指定,TaskAffinity 主要和 singleTask 启动模式或者 allowTaskReparenting 属性配对使用,在其他情况下没意义。另外,任务栈为前台任务栈和后台任务栈,后台任务栈中的 Activity 处于暂停状态,用户可以通过切换将后台任务栈再次调到前台。
当 TaskAffinity 和 singleTask启动模式配合使用时,该 Activity 会运行在名字和 TaskAffinity 相同的任务栈中。
当 TaskAffinity 和 allowTaskReparenting 结合的时候,若应用 A 启动了一个应用 B 中的某个 Activity C 后,如果 Activity C的 allowTaskReparenting 属性为 true , 那么当应用 B 被启动后,此 Activity 会直接从应用 A 的任务栈转移到应用 B 的任务栈中;此时点击 Home 键回到桌面,点击应用 B 的桌面图标,这是不是启动应用 B 的主 Activity ,而是从新显示了被应用 A 启动的 Activity C ,或者说 C 中 A 的任务栈转移到了 B 的任务栈中。
<activity
android:taskAffinity="com.xxx.task"
/>
Activity 的 Flags 比较多,这里不一一介绍,要注意的是:有些 Flag 是系统内部使用的,应用程序不需要手动去设置这些 Flag ,以防出现问题。
启动 Activity 分为两种:显示调用和隐式调用。显示调用需要明确地指定被启动对象的信息,包括包名和类名;隐式调用需要明确指定组件信息。原则上一个 Intent 不应该既是显示调用又是隐式调用,如果二者共存,以显示调用为准。
隐式调用需要 Intent 能够匹配目标组件的 IntentFilter 中所设置的过滤信息,若不匹配则无法启动目标 Activity 。 IntentFilter 中的过滤信息有 action 、 category 、 data , 只有一个 Activity 同时匹配 action 、 category 、 data 才算完全匹配,只有完全匹配才能成功启动目标 Activity ;另外,一个 Activity 中可以有多组 IntentFilter ,只要 Intent 可以匹配任何一组 IntentFilter ,都可以成功启动该 Activity 。
action 是一个字符串,当 Intent 中的 action 和 IntentFilter 中的 action 完全一样(包括大小写)时,才算匹配成功。存在多个action只需匹配一个即可完全匹配
category 也是一个字符串,Intent 可以没有 category ,此时系统会默认给 Intent 添加 “android.intent.category.DEFAULT” 这个 category ,如果为 Intent 指定了 category ,那么不管为 Intent 设置了多少个 category 都要能够和 IntentFilter 中的任何一个 category 相同。有多少个category就要匹配多少个。
data 的语法格式如下:
data>
data 由两部分组成 mimeType 和 URI ,mimeType 是指媒体类型,如 image/jpeg , video/mp4 等; URI 的结构如下:
<scheme>://<host>:<port>[<path>|<pathPattern>|<pathPrefix>]
如果 URI 中没有指定 scheme 和 host ,那么这个 URI 是无效的。path 、 pathPattern 和 pathPrefix : path 表示完整的路径信息;pathPattern 也表示完整的路径信息,但是他里面可以包含通配符 “*” , “*” 表示 0 个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么 “*” 要写成 “\\*” , “\” 要写成 “\\\\”;data 匹配和 action 类似,也需要完全匹配,下面分两种情况介绍下 data 的匹配规则:
1.只有 mimeType
<data android:mimeType="image/*" />
这种情况下虽然没有指定 URI ,但却有默认值,URI 的默认值为 content 和 file 。也就是说 data 使用默认值的时候,Intent 中的 URI 部分必须为 content 或 file 才能匹配成功。另外,如果要为 Intent 指定完整的 data ,必须要调用 setDataAndType 方法,不能先调用 setData 和 setType 中的任何一个,再调用另一个,因为 setData 和 setType 会彼此情况对方的值。
2.完整的 data 格式
这种和上面的 “data 语法格式” 内容一样,信息比较全,需要完全匹配。
**注意:**关于 AndroidManifest.xml 中 data 的书写格式有种特殊情况,下面的两种写法是等效的:
<intent-filter ...>
<data android:scheme="file" android:host="www.baidu.com" />
...
intent-filter>
<intent-filter ...>
<data android:scheme="file" />
<data android:host="www.baidu.com" />
...
intent-filter>
最后,当我们隐式启动一个 Activity 的时候,需要先判断一下是否能够找到能够匹配我们 Intent 的 Activity ,否则会报 ActivityNotFoundException 异常。判断方法可以使用 PackageManager 的 resolveActivity 或 Intent 的 resolveActivity ,如果它们找不到匹配的 Activity 则返回 null ,这样我们可以规避上述异常。另外,PackageManager 还提供了 queryIntentActivities ,这个方法不是返回最佳匹配的 Activity 信息,而是返回所有成功匹配的 Activity 信息。 下面让我们看一下这两个方法原型:
public abstract List<ResolveInfo> queryIntentActivities(Intent intent , int flag) ;
public abstract ResolveInfo resolveActivity(Intent intent , int flag) ;
上面两个方法的第一个参数好理解,第二个参数需要注意,我们要使用 MATCH_DEFAULT_ONLY 这个标记位,这个标记位的含义是仅仅匹配那些在 intent-filter 中声明了 这个 category 的 Activity 。使用这个标记位的意义在于,只有上述两个方法不返回 null ,那么 startActivity 一定可以成功。如果不使用这个标记位,就可以把 intent-filter 中 category 不含 DEFAULT 的那些 Activity 给匹配出来,从而导致 startActivity 可能失败。因为不含 DEFAULT 这个 category 的 Activity 是无法接收隐式 Intent 的。在 action 和 category 中,有一类 action 和 category 比较重要,它们是:
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
这二者共同作用是用来标明这是一个入口 Activity 并且会出现在系统的应用列表中,少了任何一个都没意义,也无法出现在系统应用列表中。另外,针对 Service 和 BroadcastReceive,PackageManager 同样提供了类似的方法去获取成功匹配的组件信息。