zoukankan      html  css  js  c++  java
  • Android中Activity的启动模式(LaunchMode)和使用场景

    一、为什么需要启动模式

    在Android开发中,我们都知道,在默认的情况下,如果我们启动的是同一个Activity的话,系统会创建多个实例并把它们一一放入任务栈中。当我们点击返回(back)键,这些Activity实例又将从任务栈中一一移除,遵循的原则是“后进先出”(先进后出)。

    这里我们考虑一个问题,当我们多次启动同一个Activity,系统也会创建多个实例放入任务栈中,这样岂不是很耗费内存资源?为了解决这一问题,Android为Actiivty提供了启动模式。

    Activity的启动模式有四种:standard、singleTop、singleTask和singleInstance

    二、启动模式的分类

    1、standard:标准模式
    这种启动模式为标准模式,也是默认模式。每当我们启动一个Activity,系统就会相应的创建一个实例,不管这个实例是否已经存在。这种模式,一个栈中可以有多个实例,每个实例也都有自己的任务栈。而且是谁启动了此Activity,那么这个Activity就运行在启动它的Activity所在的栈中。

    Manifest中配置:

    • 对于标准模式,android:launchMode="standard"可以不写,因为默认就是standard模式。
    <activity
         android:name=".StandardActivity"
         android:launchMode="standard" >
    </activity>
    
    • 使用案例:
      MainActivity有一个按钮,点击按钮会打开StandardActivity。打开StandardActivity也有一个按钮,点击也是启动一个StandardActivity。并且我们在onCreate()方法中打印TaskId和hashCode值。

    打开步骤:MainActivity->StandardActivity->StandardActivity->StandardActivity

    MainActivity:

    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = MainActivity.class.getSimpleName();
        
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main_demo);
    
            findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    StandardActivity.open(MainActivity.this);
                }
            });
    
            Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
    
        }
      
    }
    

    StandardActivity :

    /**
     * 启动模式:Standard(标准模式)
     */
    
    public class StandardActivity extends AppCompatActivity {
    
    
        private static final String TAG = StandardActivity.class.getSimpleName();
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_launch_mode);
    
            findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    open(StandardActivity.this);
                }
            });
    
            Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
        }
    
        public static void open(Context context) {
            context.startActivity(new Intent(context, StandardActivity.class));
        }
    
    }
    

    控制台打印log如下:

    通过案例的log分析,可以得出标准模式下,每当打开一次Activity就会创建一个新的实例,因为hashCode值都不同,而且都创建在启动它的Activity所属的任务栈中,也就是MainActivity所在的任务栈中,因为它们的任务栈Id一致。

    分析总结:

    标准模式下,只要启动一次Activity,系统就会在当前任务栈新建一个实例。
    使用场景:
    正常的去打开一个新的页面,这种启动模式使用最多,最普通 。

    2、singleTop:栈顶复用模式

    这种启动模式下,如果要启动的Activity已经处于栈的顶部,那么此时系统不会创建新的实例,而是直接打开此页面,同时它的onNewIntent()方法会被执行,我们可以通过Intent进行传值,而且它的onCreate(),onStart()方法不会被调用,因为它并没有发生任何变化。

    • Manifest中配置:
     <activity
           android:name=".SingleTopActivity"
           android:launchMode="singleTop">
    </activity>
    
    • 使用案例:
      MainActivity仍然是一个按钮,点击按钮打开SingleTopActivity,SingleTopActivity有两个按钮,一个是打开SingleTopActivity,一个是打开OtherActivity,OtherActivity有一个按钮,点击按钮可以打开SingleTopActivity。而且我们在onCreate()、onNewIntent()打印taskId和hashCode值。

    打开步骤:MainActivity->SingleTopActivity->SingleTopActivity->OtherActivity->SingleTopActivity->SingleTopActivity

    MainActivity:

    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = MainActivity.class.getSimpleName();
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main_demo);
    
            findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    SingleTopActivity.open(MainActivity.this);
                }
            });
    
            Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
    
    }
    

    SingleTopActivity :

    /**
     * 启动模式:栈顶复用模式
     */
    
    public class SingleTopActivity extends AppCompatActivity {
    
        private static final String TAG = SingleTopActivity.class.getSimpleName();
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_launch_mode);
    
            findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    open(SingleTopActivity.this);
                }
            });
            findViewById(R.id.btn_other).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    OtherActivity.open(SingleTopActivity.this);
                }
            });
    
            Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
        }
    
        @Override
        protected void onNewIntent(Intent intent) {
            super.onNewIntent(intent);
            Log.e(TAG, "———onNewIntent(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
        }
    
        public static void open(Context context) {
            context.startActivity(new Intent(context, SingleTopActivity.class));
        }
    
    }
    

    OtherActivity:

    public class OtherActivity extends AppCompatActivity {
    
        private static final String TAG = OtherActivity.class.getSimpleName();
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_other);
    
            findViewById(R.id.btn_singleTop).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    SingleTopActivity.open(OtherActivity.this);
                }
            });
    
            Log.e(TAG, "———onCreate(): TaskId: " + getTaskId() +",  hashCode: " + hashCode());
        }
    
        public static void open(Context context) {
            context.startActivity(new Intent(context, OtherActivity.class));
        }
    
    }
    

    控制台打印log如下:

    首先,因为它们的taskId值都相同,所以它们同属于一个任务栈。其次,第一次启动SingleTopActivity的时候会执行onCreate()方法新建一个实例,然后再次启动SingleTopActivity页面会回调onNewIntent(),说明没有创建新的实例,而且hashCode值没有发生改变。此时我们继续打开另一个Activity,这时OtherActivity处于栈顶,我们继续启动SingleTopActivity,这时发现又是执行了onCreate(),说明又重新创建了新的实例,当我们继续启动SingleTopActivity,发现回调了onNewIntent(),同样hashCode值没有发生改变,证明没有重新创建实例。

    • 分析总结:
      通过上述案例归纳为以下三点:
    1. 当前栈中已有该Activity的实例并且该实例位于栈顶时,不会创建实例,而是复用栈顶的实例,并且会将Intent对象传入,回调onNewInten()方法;
    2. 当前栈中已有该Activity的实例但是该实例不在栈顶时,其行为和standard启动模式一样,依然会创建一个新的实例;
    3. 当前栈中不存在该Activity的实例时,其行为同standard启动模式。
    • 使用场景:
      这种模式应用场景的话,假如一个新闻客户端,在通知栏收到了3条推送,点击每一条推送会打开新闻的详情页,如果为默认的启动模式的话,点击一次打开一个页面,会打开三个详情页,这肯定是不合理的。如果启动模式设置为singleTop,当点击第一条推送后,新闻详情页已经处于栈顶,当我们第二条和第三条推送的时候,只需要通过Intent传入相应的内容即可,并不会重新打开新的页面,这样就可以避免重复打开页面了。

    3、singleTask:站内复用模式

    在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,因为singleTask本身自带clearTop这种功能。并且会回调该实例的onNewIntent()方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。不设置taskAffinity属性的话,默认为应用的包名。

    使用案例:
    MainActivity仍然是一个按钮,点击按钮打开SingleTaskActivity,SingleTaskActivity有两个按钮,一个是打开SingleTaskActivity,另一个同样是打开OtherActivity,OtherActivity有一个按钮,点击按钮可以打开SingleTaskActivity。同样我们在onCreate()、onNewIntent()打印taskId和hashCode值。

    打开步骤:MainActivity->SingleTaskActivity->SingleTaskActivity->OtherActivity->SingleTaskActivity->SingleTaskActivity

    代码和SingleTop基本一样,只有Manifest中配置不同,这里不再赘述。

    1、不设置taskAffinity属性,也就是默在同一个任务栈中。

    <activity
         android:name=".SingleTaskActivity"
         android:launchMode="singleTask">
    </activity>
    

    控制台打印log如下:

    首先,因为发现它们的taskId值都相同,所以它们同属于一个任务栈。其次,第一次启动SingleTaskActivity的时候会执行onCreate()方法新建一个实例,然后再次启动SingleTaskActivity页面会回调onNewIntent(),说明没有创建新的实例,而且hashCode值没有发生改变。此时我们继续打开另一个Activity,然后继续启动SingleTaskActivity,这时发现仍然只回调onNewIntent(),说明没有创建新的实例,当我们继续启动SingleTaskActivity,仍然只是回调了onNewIntent(),此过程中发现hashCode值始终没有发生改变,证明引用都是同一个的实例。

    2、设置taskAffinity属性,singleTask所在的Activity与启动它的Activity处于不同的任务栈中。

    <activity
         android:name=".SingleTaskActivity"
         android:launchMode="singleTask"
         android:taskAffinity="${applicationId}.singleTask">
    </activity>
    


    指定了taskAffinity后,我们发现除了taskId有区别外,其他调用基本没有什么区别。因为MainActivity没有指定taskAffinity属性,默认为包名,与SingleTaskActivity不同,所以在启动SingleTaskActivity时,发现这个栈不存在,系统首先会创建这个栈然后将SingleTaskActivity压入栈中。之后我们发现只要栈中存在SingleTaskActivity这个实例,就会直接引用。

    3、通过adb shell dumpsys activity activities查看当前运行的Activity

    执行完上面的步骤,我们通过上面的信息得出,发现只有MainActivity和SingleTaskActivity在运行,而且也可以看出确实有1909和1910两个任务栈。那OtherActivity哪去了?那是因为SingleTaskActivity具有ClearTop的功能,当复用SingleTashActivity的时候会将栈中SingleTaskActivity之上的Activity全部清掉,所以OtherActivity其实是被销毁了。

    • 分析总结:
      在复用的时候,首先会根据taskAffinity去找对应的任务栈:
    1. 如果不存在指定的任务栈,系统会新建对应的任务栈,并新建Activity实例压入栈中。
    2. 如果存在指定的任务栈,则会查找该任务栈中是否存在该Activity实例
    3. 如果不存在该实例,则会在该任务栈中新建Activity实例。
    4. 如果存在该实例,则会直接引用,并且回调该实例的onNewIntent()方法。并且任务栈中该实例之上的Activity会被全部销毁。
    • 使用场景:
      SingleTask这种启动模式最常使用的就是一个APP的首页,因为一般为一个APP的第一个页面,且长时间保留在栈中,所以最适合设置singleTask启动模式来复用。

    4、singleInstance:单实例模式

    单实例模式,顾名思义,只有一个实例。该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

    • Manifest中配置:
    <activity
        android:name=".SingleInstanceActivity"
        android:launchMode="singleInstance">
    </activity>
    
    • 使用案例:
      使用上面同样的代码进行测试:

      通过测试发现,在第一次打开SingleInstanceActivity的时候,由于系统不存在该实例,所以系统会新建一个任务栈来存放该Activity实例,而且只要打开过一次该Activity,后面无论什么时候再次启动该Activity,都会直接引用第一次创建的实例,而且会回调该实例的onNewIntent()方法。

    • 分析总结:
      启动该模式Activity的时候,会查找系统中是否存在:

    1. 不存在,首先会新建一个任务栈,其次创建该Activity实例。
    2. 存在,则会直接引用该实例,并且回调onNewIntent()方法。
      特殊情况:该任务栈或该实例被销毁,系统会重新创建。
    • 使用场景:
      很常见的是,电话拨号盘页面,通过自己的应用或者其他应用打开拨打电话页面 ,只要系统的栈中存在该实例,那么就会直接调用。

    三、总结

    在使用APP过程中,不可避免页面之间的跳转,那么就会涉及到启动模式。其实在对界面进行跳转时,Android系统既能在同一个任务中对Activity进行调度,也能以Task(任务栈)为单位进行整体调度。在启动模式为standard或singleTop时,一般是在同一个任务中对Activity进行调度,而在启动模式为singleTask或singleInstance是,一般会对Task进行整体调度。

    作者:Shawpoo的
    链接:https://www.jianshu.com/p/c4cba9c94fa8

  • 相关阅读:
    git(1)-git关联GitHub-windows-转载
    jenkins(4)-jenkins配置邮件通知
    jenkins(3)-linux下安装jenkins(yum install方式)
    【PAT甲级】1090 Highest Price in Supply Chain (25 分)(DFS)
    【PAT甲级】1087 All Roads Lead to Rome (30 分)(MAP【int,string】,邻接表,DFS,模拟,SPFA)
    【PAT甲级】1018 Public Bike Management (30 分)(DFS,SPFA)
    Educational Codeforces Round 61 (Rated for Div. 2) G(线段树,单调栈)
    Atcoder Grand Contest 032C(欧拉回路,DFS判环)
    Educational Codeforces Round 62 (Rated for Div. 2)E(染色DP,构造,思维,组合数学)
    Atcoder Grand Contest 031C(构造,思维,异或,DFS)
  • 原文地址:https://www.cnblogs.com/sishuiliuyun/p/14517524.html
Copyright © 2011-2022 走看看