彻底明白Activity启动模式-SingleTop、SingleTask、SingleInstance具体使用场景[通俗易懂]

彻底明白Activity启动模式-SingleTop、SingleTask、SingleInstance具体使用场景[通俗易懂]启动模式启动模式是什么有这样的场景:当我们使用App的时候,呈现出一个Activity,按下返回键(不考虑重写返回键事件),常常就回退到上一个打开的Activity或者退出App。//重写返回按键事件publicbooleanonKeyDown(intkeyCode,KeyEventevent){if(keyCode==KeyEvent.KEYCODE_BA

大家好,又见面了,我是你们的朋友全栈君。

原创地址:http://blog.csdn.net/zivensonice/article/details/51569502

启动模式

启动模式是什么

有这样的场景:

  • 当我们使用App的时候,呈现出一个Activity,按下返回键(不考虑重写返回键事件),常常就回退到上一个打开的Activity或者退出App。
//重写返回按键事件
public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            // 这里重写返回键
            return true;
        }
        return false;
    }
  • 几个应用之间,例如我的App要使用拍照功能,我需要调用系统的相机App,这分明就是两个不同的应用程序,分别运行在不同的进程,但是当我调用完成相机后,按下返回键可以返回我的App
//调用相机
private void openCamera() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        uri = PhotoUtil.createImageFile();
        if (takePictureIntent.resolveActivity(mContext.getPackageManager()) != null) {
  
  // 相机被卸载时不会崩溃
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAMERA);
        }
    }
  • 还有例如在我们的App打开浏览器、微博之类的应用,然后跳转到浏览器,使用完成浏览器的功能,不断按下返回键,可以回到我们的应用。而不是像点击桌面上浏览器图标启动浏览器那样,不断返回键,最后回到的是桌面。
  • 生成重复页面。
    以前遇到过这样的Bug:
    1. 消息推送,通知栏弹出Notification,点击Notification跳转到指定Activity,但是如果我现在页面就停留在那个指定的Activity,会再次打开我当前的Activity,这样返回的时候回退的页面和当前页面一样,感官上就会很奇怪。
    2. 登录的时候,登录成功跳转到主页,按下两次登录按钮,生成了两个主页。一些有启动延迟的页面(往往是动画,网络造成)也会有这样的情况。

为什么要研究启动模式

  1. 有时候我们的App需要生成给其他App调用的Activity,例如浏览器应用,照相机应用
  2. 解决生成重复页面等等Bug
  3. 任务栈过深的时候,避免一直按返回键也退不回想要的页面

任务栈

任务栈Task,是一种用来放置Activity实例的容器,他是以栈的形式进行盛放,也就是所谓的先进后出,主要有2个基本操作:压栈和出栈,其所存放的Activity是不支持重新排序的,只能根据压栈和出栈操作更改Activity的顺序。

启动一个Application的时候,系统会为它默认创建一个对应的Task,用来放置根Activity。默认启动Activity会放在同一个Task中,新启动的Activity会被压入启动它的那个Activity的栈中,并且显示它。当用户按下回退键时,这个Activity就会被弹出栈,按下Home键回到桌面,再启动另一个应用,这时候之前那个Task就被移到后台,成为后台任务栈,而刚启动的那个Task就被调到前台,成为前台任务栈,手机页面显示的就是前台任务栈中的栈顶元素。

standard

Activity默认模式,所有的Activity遵循元素进栈出栈的特性,例如进栈序列为A->B->C->D,D呈现在页面上,按返回键出栈顺序久违D->C->B->A.

singleTop

栈顶复用模式,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onNewIntent() 方法。避免栈顶的activity被重复的创建。
在开始处,我们提到的2个Bug,可以用这种模式解决

消息推送

通知栏弹出Notification,点击Notification跳转到指定Activity,但是如果我现在页面就停留在那个指定的Activity,会再次打开我当前的Activity,这样返回的时候回退的页面和当前页面一样,感官上就会很奇怪。

这里写图片描述

        btnNotification.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                NoticationUtil.sendNotication(mContext);
            }
        });
public class NoticationUtil {

    @SuppressLint("NewApi")
    public static void sendNotication(Context context) {
        // 在Android进行通知处理,首先需要重系统哪里获得通知管理器NotificationManager,它是一个系统Service。
        NotificationManager manager = (NotificationManager) context
                .getSystemService(Context.NOTIFICATION_SERVICE);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
                new Intent(context, MainActivity.class), 0);
        // 通过Notification.Builder来创建通知,注意API Level
        // API16之后才支持
        Notification notify = new Notification.Builder(context)
                .setSmallIcon(R.drawable.ic_launcher)
                .setTicker("您有新短消息,请注意查收!").setContentTitle("我是标题")
                .setContentText("点我打开主页").setContentIntent(pendingIntent)
                .setNumber(1).build();

        notify.flags |= Notification.FLAG_AUTO_CANCEL; // FLAG_AUTO_CANCEL表明当通知被用户点击时,通知将被清除。
        manager.notify(1, notify);
    }
}
        <activity  android:name=".aty.MainActivity" android:launchMode="standard" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

修改为:

        <activity  android:name=".aty.MainActivity" android:launchMode="singleTop" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

这里写图片描述

登录的时候

登录成功跳转到主页,按下两次登录按钮,生成了两个主页。一些有启动延迟的页面(往往是动画,网络造成)也会有这样的情况。

        btnLogin.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // 延迟1000秒,模拟网络超时
                v.getHandler().postDelayed(new Runnable() {

                    @Override
                    public void run() {
                        // 登录成功,跳转到主页
                        LoginSuccessActivity.actionStart(mContext);
                    }
                }, 3000);

            }
        });

XML清单配置

       <activity android:name=".aty.LoginSuccessActivity" />

这里写图片描述
把他修改为singleTop

        <activity  android:name=".aty.LoginSuccessActivity" android:launchMode="singleTop" />

这里写图片描述

耗时操作返回页面

还有一种场景 从activity A启动了个service进行耗时操作,或者某种监听,这个时候你home键了,service收集到信息,要返回activityA了,就用singleTop启动,实际不会创建新的activityA,只是resume了。不过使用standard又会创造2个A的实例。

singleTask

栈内复用模式

  1. 如果要启动的Activity在当前栈内启动,activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,调用 onNewIntent() 方法,并且清空这个activity任务栈上面所有的activity。
        <activity android:name=".aty.MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity  android:name=".aty.Test1Activity" android:launchMode="singleTask" />
        <activity android:name=".aty.Test2Activity" />

XML配置 MainActivitiy,Test2Activity为standard模式,Test1Activity为singleTask模式。
入栈情况:
MainActivity—>Test1Activity—>Test2Activity—>MainActivity,如果此时启动Test1Activity,会清空Test1顶部元素 栈内情况变为MainActivity—>Test1Activity;
这里写图片描述
2. 主要就是在清单文件中配置android:taskAffinity="新的包名",因为android:taskAffinity这个字段默认指定的包名为本应用的包名,表示在本应用包名的任务栈内创建应用。如果设置了这个字段,而且还和本应用包名不同,就会在新的任务栈创建任务。例如,修改刚才的代码Test1Activity的配置清单:

        <activity  android:name=".aty.Test1Activity" android:launchMode="singleTask" android:taskAffinity="com.test.newlaunch" />

指定android:taskAffinity包名为com.test.newlaunch
这里写图片描述

06-03 14:47:46.556: D/displayTask(22127): id:--69
06-03 14:47:46.556: D/displayTask(22127): numActivities:--1
06-03 14:47:46.556: D/displayTask(22127): topActivity:--com.example.testlaunch.aty.MainActivity
06-03 14:47:46.556: D/displayTask(22127): baseActivity:--com.example.testlaunch.aty.MainActivity
06-03 14:47:48.428: D/displayTask(22127): id:--69
06-03 14:47:48.428: D/displayTask(22127): numActivities:--2
06-03 14:47:48.428: D/displayTask(22127): topActivity:--com.example.testlaunch.aty.Test2Activity
06-03 14:47:48.428: D/displayTask(22127): baseActivity:--com.example.testlaunch.aty.MainActivity
06-03 14:47:50.080: D/displayTask(22127): id:--69
06-03 14:47:50.080: D/displayTask(22127): numActivities:--3
06-03 14:47:50.080: D/displayTask(22127): topActivity:--com.example.testlaunch.aty.MainActivity
06-03 14:47:50.080: D/displayTask(22127): baseActivity:--com.example.testlaunch.aty.MainActivity
06-03 14:47:55.164: D/displayTask(22127): id:--71
06-03 14:47:55.164: D/displayTask(22127): numActivities:--1
06-03 14:47:55.164: D/displayTask(22127): topActivity:--com.example.testlaunch.aty.Test1Activity
06-03 14:47:55.164: D/displayTask(22127): baseActivity:--com.example.testlaunch.aty.Test1Activity
06-03 14:47:56.688: D/displayTask(22127): id:--71
06-03 14:47:56.688: D/displayTask(22127): numActivities:--2
06-03 14:47:56.688: D/displayTask(22127): topActivity:--com.example.testlaunch.aty.Test2Activity
06-03 14:47:56.688: D/displayTask(22127): baseActivity:--com.example.testlaunch.aty.Test1Activity
06-03 14:47:58.140: D/displayTask(22127): id:--71
06-03 14:47:58.140: D/displayTask(22127): numActivities:--3
06-03 14:47:58.140: D/displayTask(22127): topActivity:--com.example.testlaunch.aty.MainActivity
06-03 14:47:58.140: D/displayTask(22127): baseActivity:--com.example.testlaunch.aty.Test1Activity

最开始的栈id为69,然后在06-03 14:47:55.164: D/displayTask(22127): topActivity:--com.example.testlaunch.aty.Test1ActivityTest1Activity启动的时候又新建了一个栈id为71.同时在查看后台程序,看到出现了2个后台任务都是我们的应用—TestLanuch。
这里写图片描述

作用

  1. 做浏览器、微博之类的应用,比如其他App需要打开我们的浏览器页面,就可以配置他为singleTask模式,保证他只有一个唯一实例,节约内存同时按下返回键后的感官也更顺畅。但是需要注意,提供给人调用的页面最好是栈底元素。因为,如果自己的客户端处于运行状态,按下Home键后台挂起。此时如果使用如果其他应用(比如说QQ)调起自己的客户端某个页面,不做任何处理的情况下,按下回退或者当前 Activity.finish(),页面都会停留在自己的客户端(因为自己的Application回 退栈不为空),这明显不符合逻辑的。
    对于我的应用TestLaunch XML清单配置
        <activity android:name=".aty.MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />


                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity  android:name=".aty.Test1Activity" android:launchMode="singleTask" >
            <intent-filter>
                <action android:name=".aty.Test1Activity" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity android:name=".aty.Test2Activity" />

在其他应用 “我是QQ”中打开指定页面Test1Activity

        btnSkip.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction(".aty.Test1Activity");
                try {
                    startActivity(intent);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

错误重现

a. 模拟操作TestLanuch
这里写图片描述
b. 使用我是QQ打开TestLanuch的Test1Activity,然后按返回键退栈
这里写图片描述
要解决这个问题有两种方式:

利用singleTask清除这个activity任务栈上面所有的activity特性

我们提供的分享页面始终是我们栈底的元素,只要他一启动就会清空任务栈内其他Activity,保证只有他一个实例。

android:taskAffinity=”要打开本应用的其他应用包名”

例如在本应用真就设置:

        <activity  android:name=".MainActivity" android:label="@string/app_name" android:taskAffinity="com.test.newlaunch" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

这样在Test1Activity启动的时候,如果com.test.newlaunch没启动就新建一个任务栈,如果com.test.newlaunch启动了就直接加入它的任务栈。

SingleInstance

单一实例模式,整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。
可以得出以下结论:
1. 以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例。
2. 以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
3. 以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中。
4. 被singleInstance模式的Activity开启的其他activity,能够在新的任务中启动,但不一定开启新的任务,也可能在已有的一个任务中开启。

换句话说,其实SingleInstance就是我们刚才分析的SingleTask中,分享Activity为栈底元素的情况。

代码下载地址

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/153244.html原文链接:https://javaforall.net

(0)
上一篇 2022年6月26日 上午11:46
下一篇 2022年6月26日 下午12:00


相关推荐

  • 宽字节注入原理学习

    宽字节注入原理学习0x01开篇本题用到考点是宽字节注入,遇到这种注入类型学习记录。推荐两篇链接:浅析白盒审计中的字符编码及SQL注入|离别歌Von的博客|VonBlog为方便自我下次忘记,总结一下:1.宽字节涉及到编码问题,便于理解需要看一看2.宽字节注入现在已经很少见,因为如今的编码大多使用utf-8常见url编码:空格–%20′–%27#–%23\–%5c0x02原理我们注入时都会简单输入一个’或者”,进行测试,如果数据库过滤不严格就会产生报错

    2022年10月14日
    4
  • GBDT算法原理_boosting算法

    GBDT算法原理_boosting算法本文对GBDT算法原理进行介绍,从机器学习的关键元素出发,一步一步推导出GBDT算法背后的理论基础,读者可以从这个过程中了解到GBDT算法的来龙去脉。对于该算法的工程实现,本文也有较好的指导意义,实际上对机器学习关键概念元素的区分对应了软件工程中的“开放封闭原则”的思想,基于此思想的实现将会具有很好的模块独立性和扩展性。

    2022年10月12日
    6
  • JavaScript 页面跳转的几种方式

    JavaScript 页面跳转的几种方式第一种 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp window location href login jsp backurl window location href nbsp nbsp nbsp nbsp 第二种 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp alert 返回 nbsp nbsp nbsp nbsp nbsp nbsp window history back 1 nbsp nbsp nbsp nbsp nbsp nbsp nbsp 第三种 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp window navigate top jsp

    2026年3月18日
    1
  • 频谱分析仪原理学习

    频谱分析仪原理学习 虽是电子专业出身,但在学生期间用频谱仪的次数比较少,连使用都不顺畅更加不会想到去研究它的原理。但现在的工作主要就是检测接收机,每天和频谱仪接收机各种设备打交道,有必要也很乐意的研究下各个设备的工作原理。讲解频谱仪原理的书籍有很多,读的第一本是师傅给我的安捷伦的《频谱分析原理》接着又自己看了《R&amp;S的频谱分析原理》,相较于安捷伦R&amp;S 更加注重从理论分析,个人…

    2022年8月11日
    10
  • 还在用android.support?该考虑迁移AndroidX了!

    还在用android.support?该考虑迁移AndroidX了!

    2021年10月1日
    69
  • C/C++程序员必须熟练应用的开源项目

    作为一个经验丰富的C/C++程序员,肯定亲手写过各种功能的代码,比如封装过数据库访问的类,封装过网络通信的类,封装过日志操作的类,封装过文件访问的类,封装过UI界面库等,也在实际的项目中应

    2021年12月27日
    46

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号