前言
上篇文章介绍了Activity的生命周期,Activity还有一个重要的知识点,就是Activity的启动模式,面对形形色色的启动模式,需要夯实启动模式的基础才能正确使用项目所需要的启动模式。
Activity的LaunchMode
你是否试过多次启动同一个Activity,如果你这样做了的话,会发现你创建了多个一模一样的Activity,这在大部分app中是没有意义的,但为什么会出现这个现象呢?这就与Activity的启动模式和任务栈有关系了。先说任务栈,就是一个后进先出的栈结构,当你每次按下返回键时,就会将栈顶部的Activity弹出栈。这个比较好理解,但启动模式却有四种:standard,singleTop,singleTask,singleInstance。下面分别说明这四种启动模式:
standard:标准模式,也是多实例模式,是Activity的默认启动模式,当要启动的Activity为这种模式时,会创建这个Activity并将该Activity放入开启该Activity的任务栈中。举例来说,比如Activity A 开启了Activity B,Activity的启动模式是stardard,那么会调用响应的生命周期方法,并将Activity B放入Activity A 所在的任务栈中。
singleTop:栈顶复用模式,当将要开启的Activity在任务栈的栈顶的栈顶,则不会重新创建新的Activity,只是会调用onNewIntent方法来接收新的数据,不会调用Activity创建时的相关生命周期方法。如果当前任务栈的栈顶没有该Activity的实例,则创建新的Activity并调用相关的生命周期方法。
举例来说,如果当前的任务栈里有ABCD(D在栈顶),此时开启Activity D,且D的启动模式为singleTop则D不会再次被创建,而是调用newIntent方法,当前的任务栈还是ABCD。如果D的启动模式为standard,则会创建新的Activity D 的实例到任务栈,则任务栈里为ABCDD。
singleTask:栈内复用模式,如果有当前Activity需要的任务栈且整个栈里有要开启的Activity实例,则不会创建新的Activity实例,而是调用newIntent方法。如果该实例没有处在栈顶的话,会将其上的实例全部出栈,而当前如果没有Activity所需要的任务栈,则会创建这个任务栈,并创建该Activity的实例,将该实例放入任务栈中,此时新创建的任务栈为为前台任务栈,之前的任务栈为后台任务。举例来说当前的的栈为ABC且该任务栈是C的任务栈,现在要开启C,则不会创建C的新实例,调用其onNewIntent方法。如果当前的栈为ABCD且该任务栈是C的任务栈 ,现在要开启C,则会将D出栈,C调用onNewIntent方法,此时任务栈为ABC。如果当前栈为ABC但不是D所需要的任务栈,则会创建D需要的任务栈,并创建D的实例放入到该任务栈,此时该任务栈里只有D,为前台任务栈,原先的任务栈仍为ABC,为后台任务栈。
singleInstance:单实例模式,属于singleTask的威力加强版,除了拥有singleTask的特性外,此种启动模式下的任务栈中只有一个Activity实例。这就是说如果一个Activity是singleInstance启动模式,当该Activity启动时会创建一个新的任务栈并将该实例单独放到这个任务栈中,后续开启的该Activity均不会创建新的实例,除非该任务栈被销毁了。
现在需要指出一个状况,假设有两个任务栈 ,一个任务栈里有AB,为前台任务栈,一个任务栈里有CD,为后台任务栈,此时要开启D,因为任务栈里有了D,所以不会再创建D额实例,任务栈CD为前台任务栈,任务栈AB为后台任务栈,Activity的后退列表为ABCD(D为栈顶)。
如果开启的是C,因为任务栈里已经有C,所以不会创建C的实例,将D出栈,此时任务栈C为前台任务栈,任务栈AB为后台任务栈,Activity的后退列表为ABC(C为栈顶)。
还有一点需要说的是任务栈,任务栈的指定需要一个属性taskAffinity,该值为一个字符串,该属性决定了activity所需要的任务栈的名字,在默认情况下,任务栈的名字为包名。如果制定了Activity的taskAffinity,任务栈的名字则为指定的字符串。该属性主要与singleTask和allowTaskReparenting配合使用,在其他场合下该属性没有意义。当与singleTask配合使用时,他是Activity所需要的任务栈名字,开启的Activity将在taskAffinity所指定的任务栈中。有了这些知识铺垫我们对上面的例子进行一下代码演示。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhqy.activitylifecycledemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
/>
<activity
android:name=".ThirdActivity"
android:launchMode="singleTask"
android:taskAffinity="com.zxl"
/>
<activity android:name=".FourthActivity"
android:launchMode="singleTask"
android:taskAffinity="com.zxl"
></activity>
</application>
</manifest>
MainActivity(记为A)和SecondActivity(记为B)的启动模式为standrad模式,任务栈为默认的任务栈,任务栈名字为包名,ThirdActivity(记为C)和FourthActivity(记为D)的启动模式为singleTask,任务栈为com.zxl。其中A打开B,B打开C,C打开D,D打开A,下面显示的是Activity后退列表:
测试结果.gif
首先A打开B,前台任务栈里有AB,当B打开C的时候,因为C的任务栈为com.zxl,而该任务栈还没有创建,所以会创建名字为com.zxl的任务栈,并创建C的实例放入该任务栈,此时任务栈com.zxl为前台任务栈,默认任务栈为后台任务栈。打开D,因为C与D的启动模式和任务栈都相同,所以创建D的实例后会直接进入com.zxl的任务栈。当D启动A时,由于A的启动方式是standard模式,该模式创建的实例会进入启动该Activity的任务栈,所以A会进入名为com.zxl的任务栈。所以前台任务栈中为CDA,后台任务栈为AB,所以Activity的后退列表为ABCDA(最右边为栈顶),上述分析与试验结果一致。
Activity的Flags
Activity的flags有很多,现在主要介绍一些常用的启动模式的Flag,大部分情况下不需要给Activity设置flag,对这些flag理解即可。
FLAG_ACTIVITY_NEW_TASK
这个标记位的作用是为Activity指定“singleTask”模式,该模式与xml设置的一致。但需要与FLAG_ACTIVITY_CLEAR_TOP一起使用。
FLAG_ACTIVITY_SINGLE_TOP
这个标记位的作用是为Activity指定“singleTop”模式,该模式与xml设置的一致。
FLAG_ACTIVITY_CLEAR_TOIOP
具有此标志位的Activity,当它启动时,在同一个任务栈中的在之上的所有Activity都要出栈,该标志位需要与FLAG_ACTIVITY_NEW_TASK一起使用,这这种情况下如果该Activity的实例已经存在,那么系统会调用他的onNewIntent方法。那么如果该Activity的启动模式为standard,则连同它及其之上的所有Activity实例全部出栈,并创建一个新的Activity实例放入堆栈。
**FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标志位的Activity不会出现在历史Activity列表中,当我们不希望用户通过历史列表会到我们的Activity的时候这个标记比较有用。它等同于在xml指定Activity的属性 android:excludeFromRecents="true"
IntentFilter的匹配规则
我们知道Activity的启动分为两种方式:显示启动和隐式启动两种方式。显示启动需要明确启动的包名和类名等组件信息,而隐式启动则不要需要组件信息。显示启动和隐式启动不能同时使用,如果同时使用的话,显示启动优先于隐式启动。这里主要介绍一下隐式启动,隐式启动主要是Intent能够匹配目标组件中的IntentFilter中的信息,如果不能匹配IntentFilter中的信息则无法启动该Activity。IntentFilter的过滤信息主要由action,category,data。实例如下:
<intent-filter>
<action android:name="com.zxl.action"></action>
<category android:name="com.zxl.category"></category>
<data android:mimeType="text/plain"></data>
</intent-filter>
为了匹配过滤列表,需要同时匹配过滤列表中的action,category,data信息,否则匹配失败。一个过滤列表中能够与多个action,category,data。只有一个Intent同时匹配action,category和data类别才算完全匹配。只有完全匹配才能启动目标activity。一个Activity能有多个IntentFilter,Intent只要能够匹配一个IntentFilter就能成功开启目标Activity。
action匹配规则
action是一个字符串,系统预设了一些action,当然用户也可以自定义一些action。一个过滤列表可以有多个action,而Intent只要完全匹配一个action即可,这里说的完全配置指的是action字符串完全一致。另外需要注意,action区分大小写,如果Intent没有设置action,则会匹配失败。
category匹配规则
category是一个字符串,系统已经预设了一些category,当然用户也可以自定义。洗个过滤列表可以有对个category。Intent如果设置了category则需要与过滤列表中的一个category匹配。当然Intent也可以指定category,那么则会走默认的category,前提是IntentFilter中有默认的category ="android.intent.category.DEFAULT"。
data匹配规则
如果IntentFilter设置了data,则Intent必须有一个data与IntentFillter设置的一致。data由一下成分构成
<data
android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string">
</data>
data由uri和mimetype组成,现在通过一个实际的例子来说明uri
http://www.baidu.com:8080/search/search.info,
scheme:URI的模式,比如http,file,content,如果没有指定scheme,该uri无效
host:URI的主机名,www.baidu.com,如果没有指定host,该uri无效
port:URI的端口号,8080,仅当URI指定了scheme和host时才有效
path,pathPattern,pathPrefix:表示uri路径信息,其中path表示完整路径信息,pathPattern也表示完整的路径信息,但是他可以有通配符“*”,表示一个或多个字符,pathPrefix表示路径前缀信息
mimeType:指定媒体类型例如“image/png”,如果没有指定uri则有默认值,默认值是uri的scheme必须为file和content开头的uri才能匹配。data的实例如下: intent.setDataAndType(Uri.parse("http://www.baidu.com:8080/search/search.info"),"image/png");其中setDataAndType会同时设置URI和MimeType,只设置URI或MimeType会清除设置的另一个数据。
下面以实例说明Activity的隐式启动
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhqy.activitylifecycledemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity">
<intent-filter>
<action android:name="com.zxl.cation"></action>
<category android:name="com.zxl.category"></category>
<data
android:scheme="http"
android:host="www.baidu.com"
android:port="80"
android:path="/search"
android:mimeType="image/png"
></data>
</intent-filter>
</activity>
</application>
</manifest>
SecondActivity设置了一个IntentFilter,当MainActivity开启SecondActivity需要满足IntentFilter的过滤条件,由于没有设置默认的category,所以category也需要设置。
MainActivity的代码如下
package com.zhqy.activitylifecycledemo;
import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG="MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_first=findViewById(R.id.btn_first);
btn_first.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(MainActivity.this,SecondActivity.class);
intent.setAction("com.zxl.action");
intent.addCategory("com.zxl.category");
intent.setDataAndType(Uri.parse("http://www.baidu.com/search/search.info"),"image/png");
startActivity(intent);
}
});
Log.e(TAG, "onCreate: ");
}
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart: ");
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume: ");
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG, "onPause: ");
}
@Override
protected void onStop() {
super.onStop();
Log.e(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: ");
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.e(TAG, "onSaveInstanceState: ");
outState.putInt("data",123);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.e(TAG, "onRestoreInstanceState: ");
int data=savedInstanceState.getInt("data");
Log.e(TAG, "恢复出来的数据"+data);
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
}
测试结果:
隐式启动.gif
当满足了IntentFilter的过滤添加后可以正确打开SecondActivity
网友评论