Activity的启动模式有4种,分别为standard、singleTop、singleTask、single Instance。
standard默认值,标准的,特点是:启动一个Activity就进栈一个Activity。
singleTop独占顶端,特点是Activity在顶端的时候,启动Activity会自动重用Activity,不会进栈,只有在顶端才会被重用。
singleTask单任务,特点是单任务,同一个栈不会有两个Activity引用,Activity一旦进栈就不会再次进栈了。
singleInstance单实例,特点就是:单独开启一个任务栈,再次启动Activity的时候都会重用。
Intent Flag相关
-
FLAG_ACTIVITY_BROUGHT_TO_FRONT:当launchMode为singleTask时系统会默认设置这个标志。
-
FLAG_ACTIVITY_CLEAR_TASK:清空任务标志,如果Intent中设置了这个标志,会导致含有待启动Activity的Task在Activity被启动前清空。这个Activity会成为一个新的root,并且所有任务栈内旧的activity都被finish掉,这个标志只能与FLAG_ACTIVITY_NEW_TASK一起使用。
-
FLAG_ACTIVITY_CLEAR_TOP:清空任务在其之上的activity,如果设置了这个标志,并且待启动的Activity已经存在于当前的task中,那就不会再给这个activity新起一个实例,而是将task中在其之上的其他activity全部关闭,然后把intent作为一个新的intent传给这个activity(当前已在栈顶)。
-
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET:任务重置时将任务中在此标志之后的activity清空。设置这个标志在activity栈中做一个标记,在Task重置的时候栈就把从标记往上的activity都清除。也就是说,下次这个Task被通过FLAG_ACTIVITY_RESET_TASK_IF_NEEDED调到前台时(通常是由于用户从桌面重新启动),这个activity和它之上的activity都会被finish掉,这样用户就不会再回到它们,而是直接回到在它们之前的activity。
-
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:不显示在近期任务中。如果设置这个标志,这个activity就不会在近期任务中显示。
-
FLAG_ACTIVITY_FORWARD_RESULT:转发结果。如果activity A在启动activity B时设置了这个标志,那A的答复目标会传递给B,这样一来B就可以通过调用set Result(int)将返回结果返回给A的答复目标。注意:这个标志不能和startActivityForResult()一起使用。
-
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY:从近期任务中启动的标志,这个标志通常情况下不会通过应用的代码来设置,而是通过最近任务启动activity时由系统设置的。
-
FLAG_ACTIVITY_MULTIPLE_TASK:activity可在多任务中运行的标志,除非你实现了自己的顶级应用启动器,否则不要使用这个标志。与FLAG_ACTIVITY_NEW_TASK一起使用可以不再把已存在的任务唤起到前台。当被设置时,系统总会为Intent的Activity启动一个新的task,而不管是否已有已存在的任务做同样的事情。因为默认系统不包含图形化的任务管理功能,所以除非你给用户提供了返回到已启动任务的方法,否则就不要用这个标志。如果FLAG_ACTIVITY_NEW_TASK没有设置,则这个标志也被忽略。
-
FLAG_ACTIVITY_NEW_TASK:尝试在新任务中启动activity的标志,设置这个标志可以为待启动的activity创建一个新的任务。一个任务(从启动它的activity到任务中的下一个activity)就是用户可以跳转到的activity的原子群。任务可以在前台与后台之间切换;在某一特定任务之中的所有activity一直会保持同样的顺序。
-
这个标志通常被用来呈现一种“launcher”类型的行为:为用户提供一种可单独解决的事情列表,完全独立于启动他们的Activity之外运行。使用这个标志时,如果有一个任务已经运行了你要启动的activity,那就不会再创建新的activity,而是将现有的任务保持之前的状态直接唤到前台。参见FLAG_ACTIVITY_MULTIPLE_TASK这个标志,可以禁用掉这个行为。
-
这个标志不能在调用者向待启动activity请求返回结果时使用。注意:假设A启动B,如果要让B在新的task中创建,要求这两个activity的taskAffinity不同。也就是说,设置了这个标志之后,新启动的activity并非就一定在新的task中创建,如果A和B在属于同一个package,而且都是使用默认的taskAffinity,那B还是会在A的task中被创建。所以,只有A和B的taskAffinity不同时,设置了这个标志才会使B被创建到新的task。
-
FLAG_ACTIVITY_NO_ANIMATION:禁用切换动画,禁用掉系统默认的activity切换动画。
-
FLAG_ACTIVITY_HISTORY:不保存Activity的历史状态,如果设置这个标志,新的activiactivity就不会在历史栈中保存。用户一旦离开,这个activity就会finish掉。也可以使用noHistory属性设置。
-
FLAG_ACTIVITY_NO_USER_ACTION:不响应onUserLeaveHint方法,如果设置了这个标志,可以在避免用户离开当前activity时回调到onUserLeaveHint()。通常,activity可以通过这个回调表明有明确的用户行为将当前activity切出前台。这个回调标记了activity生命周期中的一个恰当的点。可以用来“在用户看过通知之后”将它们清除,如闪烁LED灯。
如果activity是由非用户驱动的事件(如电话呼入或闹钟响铃)启动的,那这个标志就应该被传入Context.startActivity,以确保被打断的activity不会认为用户已经看过了通知。
-
FLAG_ACTIVITY_PREVIOUS_IS_TOP:如果启动Activity时设置了这个标志,那当前这个 Activity 不会被当作顶部的 Activity 来判断是否之后新Intent应该被传给栈顶Activity而不是启动一个新的Activity。之前一个的Activity会被当作栈顶,假定当前的Acitvity会立即自己finish掉。我们经常与FLAG_ACTIVITY_FORWARD_RESULT一起使用。
-
FLAG_ACTIVITY_REORDER_TO_FRONT:任务中的Activity顺序重排,如果设置了这个标志,而且被启动的activity如果已经在运行,那这个activity会被调到栈顶。如果使用了标志FLAG_ACTIVITY_CLEAR_TOP,那这个FLAG_ACTIVITY_REORDER_TO_FRONT标志会被忽略。
-
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED : 如果已设置,并且此活动正在新任务中启动,或者正在将现有任务置于首位,则它将作为任务的前门启动。这将导致应用使该任务处于正确状态所需的任何亲缘关系(将活动移动到该任务或从该任务移出),或者在需要时简单地将该任务重置为其初始状态。
-
FLAG_ACTIVITY_SINGLE_TOP : 不会被再次启动,设置这个标志之后,如果被启动的Activity已经在栈顶,那它就不会被再次启动。
FLAG_ACTIVITY_TASK_ON_HOME : 直接返回桌面,这个标志可以将一个新启动的任务置于当前的home任务(home activity task)之上(如果有的话)。也就是说,在任务中按back键总是会回到home界面,而不是回到他们之前看到的activity。这个标志只能与FLAG_ACTIVITY_NEW_TASK标志一起用。 -
FLAG_DEBUG_LOG_RESOLUTION : 打开Activity解析的Log开关,可以用来启用调试的标志:设置以后,在intent的处理过程中log信息会打印出来,可以看到都找到了些什么来创建最终的解析列表。
-
FLAG_EXCLUDE_STOPPED_PACKAGES : 排除已停止的包,设置之后,Intent就不会再匹配那些当前被停止的包里的组件。如果没有设置,默认的匹配行为会包含这些被停止的包。
-
FLAG_FROM_BACKGROUND : 后台启动Activity,可以给调用者用来标识这个Intent是来自后台操作,而不是用户的交互行为。
-
FLAG_GRANT_READ_URI_PERMISSION:给Activity授权,如果设置,Intent的接收者会被授予读权限,用来读取Intent中包含的或是在ClipData中指定的Uri。当被用于Intent中的ClipData时,被授予的是Intent中其它ClipData中的所有Uri和它们递归遍历到的Uri的读权限。只有顶级Intent中的授予标志会被使用。
-
FLAG_INCLUDE_STOPPED_PACKAGES :包含已停止的包,设置之后Intent总是会去匹配那些已被停止的包里的组件。如果没有设置 FLAG_EXCLUDE_STOPPED_PACKAGES 标志,那这个就是默认行为。如果两个标志都被设置,那这个会生效(在框架中一些地方可能会自动设置exclude标志,这些标志可以被覆盖掉)。
-
FLAG_RECEIVER_FOREGROUND :接受器以前台优先级运行,当发送广播的时候设置了这个标志,会允许接收者以前台的优先级运行,有更短的时间间隔。正常广播的接受者是后台优先级,不会被自动提升。
-
FLAG_RECEIVER_REGISTERED_ONLY: 只调用手动注册的接收器,如果发送广播时设置了这个标志,那只会调用注册了的接收器BroadcastReceiver组件不会被启动。
-
FLAG_RECEIVER_REPLACE_PENDING : 替换掉等待中的广播,如果设置,发送广播时,新的广播将被替换。
Intent
安卓中提供Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,安卓则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,Intent在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。在SDK中给出了Intent作用的表现形式为:
- 通过Context.startActivity()或者Activity.startActivityForResult()启动一个Activity;
- 通过Context.startActivity()启动一个服务,或者通过Context.bindService()和后台服务交互;
- 通过广播方法(比如Context.sendBroadcast(),Context.sendOrderedBroadcast(),Context.sendStickyBroadcast())发给broadcast receivers。
Intent可分为隐式(implicitly)和显示(explicitly)两种:
(1)显式Intent
即在构造Intent对象时就指定接受者,它一般用在知道目标组件名称的前提下,一般是在相同的应用程序内部实现的,如下:
Intent intent = new Intent(MainActivity.this, NewActivity.class);
startActivity(intent);
上面那个intent中直接指明了接受者,NewActivity
(2)隐式Intent
即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于降低发送者和接收者之间的耦合,它一般用在没有明确指出目标组件名称的前提下,一般是用于在不同应用程序之间,如下:
Intent intent = new Intent();
intent.setAction("com.wooyun.test");
startActivity(intent);
上面那个intent,没有指明接收者,只是给了一个action作为接收者的过滤条件。
对于现实Intent,安卓不需要去解析,因为目标组件已经很明确,安卓需要解析的是那些隐式Intent,通过解析,将Intent映射给可以处理此Intent的Activity、IntentReceiver或Service。
Intent Filter匹配规则
Intent解析机制主要是通过查找已注册在AndroidManifest.xml中的所有Intent Filter及其中定义的Intent,最终找到匹配的Intent。在这个解析过程中,安卓是通过Intent的action、type、category这三个属性来进行匹配判断的。一个过滤列表中的action、type、category可以有多个,所有的action、type、category这三个类别才算完全匹配,只有完全匹配才能启动Activity。另外一个组件若声明了多个Intent filter,只需要匹配任意一个即可启动该组件。例如:
<action android:name="com.wooyun.project.SHOW_CURRENT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/mpeg" android:scheme="http" . . . />
<data android:mimeType="image/*" />
<data android:scheme="http" android:type="video/*" />
(1)action的匹配规则
action是一个字符串,如果Intent指明了action,则目标组件的IntentFillter的action列表中就必须包含有这个action,否则不能匹配。一个IntentFilter中可声明多个action,Intent中的action与其中的任意一个在字符串形式上完全相同(注意,区分大小写,大小写不同但字符串内容相同也会造成匹配失败),action方面就匹配成功。可通过setAction方法为Intent设置action,也可在构造Intent时传入action。需要注意的是,隐式Intent必须指定action。比如我们在Manifest文件中为MyActivity定义了如下Intent Filter:
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_TO"/>
</intent-filter>
那么只要Intent的action为“SEND”或“SEND_TO”,那么这个Intent在action方面就能和上面那个Activity匹配成功。比如我们的Intent定义如下:
Intent intent = new Intent("android.intent.action.SEND") ;
startActivity(intent);
那么我们的Intent在action方面就与MyActivity匹配了。
安卓系统预定义了许多action,这些action代表了一些常见的操作。常见action如下(Intent类中的常量):
Intent.ACTION_VIEW
Intent.ACTION_DIAL
Intent.ACTION_SENDTO
Intent.ACTION_SEND
Intent.ACTION_WEB_SEARCH
(2)data的匹配规则
如果Intent没有提供type,系统将从data中得到数据类型。和action一样,同action类似,只要Intent的data与Intent Filter中的任意一个data声明完全相同,data方面就完全匹配成功。
data由两部分组成:mineType和URI
MineType指的是媒体类型:例如image/jpeg,auto/mpeg4和viedo/*等,可以表示图片、文本、视频等不同的媒体格式uri则由scheme、host、port、path\pathPattern\pathPrefix这四部分组成
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern >]
(3)category的匹配规则
category也是一个字符串,但是它与action的过滤规则不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同。也就是说,Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中的定义了的category。当然,Intent中也可以没有category(若Intent中未指定category,系统会自动为它带上"android.intent.category.DEFAULT"),如果没有,仍然可以匹配成功。category和action的区别在于,action要求Intent中必须有一个action且必须和过滤规则中的某几个action相同,而category要求Intent可以没有 category,但是一旦发现存在于category,不论你有多少,每个都要能够和过滤规则中的任何一个category相同。我们可以通过addCategory方法为Intent添加category。特别说明:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
这两者共同出现,标明该Activity是一个入口Activity,并且会出现在系统应用列表中,二者缺一不可。
生命周期
1、主要生命周期
首先我们先看到最重要的七个生命周期,这七个生命周期是严格意义上的生命周期,它符合状态切换这个关键定义,onReStart并不涉及状态切换,但因为执行完它之后马上执行onStart,所以也放在一起讲。
- onCreate:当Activity创建实例完成,并调用attach方法赋值PhoneWindow、ContextImpl等属性之后,调用此方法。该方法在整个Activity生命周期内只会调用一次。调用该方法之后Activity进入ON_CREATE状态。
该方法是我们使用最频繁的一个回调方法。
我们需要在这个方法中初始化基础组件和视图。如viewmodel、textview。同时必须在该方法中调用setContentView来给activity设置布局。
这个方法接收一个参数,该参数保留之前状态的数据。如果是第一次启动,则该参数为空。该参数来自onSaveInstanceState存储的数据。只有当activity暂时销毁并且预期一定会被重新创建的时候才会被回调,如屏幕旋转、后台应用被销毁等。
- onStart:当Activity准备进入前台时会调用此方法。调用后Activity会进入ON_START状态。
要注意理解这里的前台的意思。虽然谷歌文档中表示调用该方法之后activity可见,如下图:
但是我们前面提及,“前台并不意味着Activity可见,只是表示activity处于活跃状态。”
前台activity一般只有一个,所以也就意味着其它的activity都进入后台了。这里的前后台需要结合activity返回栈来理解。
这个方法一般用于从别的activity切回本activity的时候调用。
- onResume:当activity准备与用户交互的时候调用。调用之后Activity进入ON_RESUME状态。
注意,这个方法一直被认为是activity一定可见,且准备好与用户交互的状态。但事实并不一直是这样。如果在onReume方法中弹出popupWindow,你会收获一个异常:token is null,表示界面尚没有被添加到屏幕上。
但是,这种情况只出现在第一次启动activity的时候。当activity启动后decorview就已经拥有token了,再次在onResume方法中弹出popupWindow就不会出现问题了。
因此,在onResume调用的时候activity是否可见要区分是否是第一次创建activity。
onStart方法是后台与前台的区分,而这个方法是是否可交互的区分。使用场景最多是在当弹出别的activity的窗口时,原activity就会进入ON_PAUSE状态,但是仍然可见;当再次回到原activity的时候,就会回调onResume方法了。
- onPause:当前activity窗口失去焦点的时候,会调用此方法。调用后activity进入ON_PAUSE状态,并进入后台。
这个方法一般在另一个activity要进入前台前被调用。只有当前activity进入后台,其它的activity才能进入前台。所以,该方法不能做重量级的操作,不然会引起界面切换卡顿。
一般的使用场景为界面进入后台时的轻量级资源释放。
最好理解这个状态就是弹出另一个activity的窗口的时候。因为前台activity只能有一个,所以当前可交互的activity变成另一个activity后,原activity就必须调用onPause方法进入ON_PAUSE状态;但是!!!仍然是可见的,只是无法进行交互。这里也可以更好地体会前台可交互与可见性的区别。
- onStop:当activity不可见的时候进行调用。调用后activity进入ON_STOP状态。
这里的不可见是严谨意义上的不可见。
当activity不可交互时会回调onPause方法并进入ON_PAUSE状态。但如果进入的是另一个全屏的activity而不是小窗口,那么当新的activity界面显示出来的时候,原activity才会进入ON_STOP状态,并回调onStop方法。同时,activity第一次创建的时候,界面是在onResume方法之后才显示出来,所以onStop方法会在新activity的onResume方法回调之后再被回调。
注意,被启动的activity并不会等待onStop执行完毕之后再显示。因而如果onStop方法里做一些比较耗时的操作也不会导致被启动的activity启动延迟。
onStop方法的目的就是做资源释放操作。因为是在另一个activity显示之后再被回调,所以这里可以做一些相对重量级的资源释放操作,如中断网络请求、断开数据库连接、释放相机资源等。
如果一个应用的全部activity都处于ON_STOP状态,那么这个应用是很有可能被系统杀死的。而如果一个ON_STOP状态的activity被系统回收的话,系统会保留该activity中view的相关信息到bundle中,下一次恢复的时候,可以在onCreate或者onRestoreInstanceState中进行恢复。
- onRestart:当另一个activity切回到该activity的时候会调用。调用该方法后会立即调用onStart方法,之后activity进入ON_START状态。
这个方法一般在activity从ON_STOP状态被重新启动的时候会调用,执行该方法后会立即执行onStart方法,然后activity进入ON_START状态,进入前台。
- onDestroy:当activity被系统杀死或者调用finish方法之后,会回调该方法。调用该方法之后activity进入ON_DESTROY状态。
这个方法是activity在被销毁前回调的最后一个方法。我们需要在这个方法中释放所有的资源,防止造成内存泄漏问题。
回调改方法后的activity就等待被系统回收了。如果再次打开该activity需要从onCreate开始执行,重新创建activity。
2、其它生命周期回调方法
- onActivityResult
这个方法很常见,它需要结合startActivityForResult一起使用。
使用场景是:启动一个activity,并期望在该activity结束的时候返回数据。
当启动的activity结束的时候,返回原activity,原activity就会回调onActivityResult方法了。该方法执行在其它所有的生命周期方法前。关于onActivityResult如何使用这里就不展开了,我们主要介绍生命周期。
- onSaveInstanceState/onRestoreInstanceState
这两个方法,主要用于在Activity被意外杀死的情况下进行界面数据存储与恢复。什么叫意外杀死呢?
如果你主动点击返回键、调用finish方法、从多任务列表清除后台应用等等,这些操作表示用户想要完整得退出activity,那么就没有必要保留界面数据了,所以也不会调用这两个方法。而当应用被系统意外杀死,或者系统配置更改导致的activity销毁,这个时候当用户返回activity时,期望界面的数据还在,则会通过回调onSaveInstanceState方法来保存界面数据,而在activity重新创建并运行的时候调用。
onRestoreInstanceState方法来恢复数据。事实上,onRestoreInstanceState方法的参数和onCreate方法的参数是一致的,只是他们两个方法回调的实际不同。因此,判断是否执行的关键因素就是用户是否期望返回该activity时界面数据仍然存在。
这里需要注意几个点:
- 不同Android版本下,onSaveInstanceState方法的调用时机是不同的。在api30的官方注释中可以看到这么一句话:
/*If called, this method will occur after {@link #onStop} for applications
* targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}.
* For applications targeting earlier platform versions this method will occur
* before {@link #onStop} and there are no guarantees about whether it will
* occur before or after {@link #onPause}.
*/
翻译过来的意思就是,在api28及以上版本onSaveInstanceState是在onStop之后调用的,但是在低版本中,它是在onStop之前被调用的,且与onPause之间的顺序是不确定的。
- 当activity进入后台的时候,onSaveInstanceState方法则会被调用,而不是异常情况下才会调用onSaveInstanceState方法,因为并不确定在后台时,activity是否会被系统杀死,所以最保险的方法,先保存数据。当确实是因为异常情况被杀死时,返回activity用户期望界面需要恢复数据,才会调用onrestoreInstanceState来恢复数据。但是,activity直接按返回键或者调用finish方法直接结束activity的时候,是不会回调onSaveInstanceState方法,因为非常明确下一次返回该activity用户期望的是一个干净界面的新activity。
- onSaveInstanceState不能做重量级的数据存储。onSaveinstanceState存储数据的原理是把数据序列化到磁盘中,如果存储的数据过于庞大,会导致界面卡顿、掉帧等情况出现。
- 正常情况下,每个view都会重写这两个方法,当activity的这两个方法被调用的时候,会向上委托window去调用顶层viewGroup的这两个方法;而viewGroup会递归调用子view的onSaveInstanceState/onRestoreInstanceState方法,这样所有的view状态都被恢复了。
- onPostCreate
这个方法其实和onPostResume是一样的,同样的还有onContextChange方法。这三个方法都是不常用的,这里也点出其中一个来统一讲一下。
onPostCreate方法发生在onRestoreInstanceState之后,onResume之前,它代表着界面数据已经完全恢复,就差显示出来与用户交互了。在onStart方法被调用时这些操作尚未完成。
onPostResume是在Resume方法被完全执行之后的回调。
onContentChange是在setContentView之后的回调。
这些方法都不常用,仅做了解。如果真的遇到了具体的业务需求,也可以拿出来用一下。
- onNewIntent
这个方法涉及到的场景也是重复启动,但是与onRestart方法被调用的场景是不同的。
我们知道activity是有多种启动模式的,其中singletance、singleTop、singleTask都保证了在一定情况下的单例状态。如singleTop,如果我们启动一个正处于栈顶且启动模式为singleTop的activity,那么它并不会再创建一个activity实例,而是会回调该activity的onNewIntent方法。该方法接受一个intent参数,该参数就是新的启动Intent实例。
其它的情况如singleTask、singleInstance,当遇到这种强制单例情况时,都会回调onNewIntent方法。关于启动模式这里也不展开。