美文网首页
Activity的启动模式

Activity的启动模式

作者: 取了个很好听的名字 | 来源:发表于2019-08-06 11:30 被阅读0次

    前言

    上篇文章介绍了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

    相关文章

      网友评论

          本文标题:Activity的启动模式

          本文链接:https://www.haomeiwen.com/subject/xlimdctx.html