使用LaunchMode方式防止多次创建activity的总结

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用LaunchMode方式防止多次创建activity的总结相关的知识,希望对你有一定的参考价值。

参考技术A         根据对android启动activity的launchMode方式进行了解后,在将其设置为singleTop或者singleTask后,便可以防止在界面中重复点击之后启动多个activity的情况。然而,在公司项目中使用了该方式后发现了一个问题,当启动一个activity使用的方法为startActivityForResult时,使用singleTop或者singleTask并无法防止重复点击,仍旧会触发两个activity。

         在5.0之前的源码中,先将启动模式进行判断,判断为不是FLAG_ACTIVITY_NEW_TASK时, 会抛出“Activity is launching as a new task, so canceling activity result”, 之后并没有做任何处理,但是在5.0及5.0以后进行了处理,如图

5.0及5.0以后的源码为:

        我们可以看到,虽然启动模式被设置为singleTask,后面会将启动模式重新设置为FLAG_ACTIVITY_NEW_TASK。

        在startActivityForResult方法前有一段注释,写明了使用这个方法的相关协议。图中画框显示,假如一个activity的启动模式为FLAG_ACTIVITY_NEW_TASK,那么这个activity将不会在原先的task任务栈中被启动运行。

       然而一个新的task任务栈中不会存放有其他的activity,也就不会有刚才activity A。新的task任务栈中没有匹配到activity A,会重新创建一个activity A并且压入栈了。

       根据以上推断,我们可以得出以下结果:调用这个activity的启动方法为startActivityForResult,则相当于设置activity的启动模式为singleTask已经失效了,那么仍然会创建两个activity A。

1. 使用RxBinding,是Jake Wharton的开源库,将按钮的点击事件进行响应判断。

2. 在父类activity中对dispatchTouchEvent方法进行重写。当有一个触摸事件发生时,会直接到达根节点,即会调用子View的dispatchTouchEvent方法进行发送事件。获取当前事件,与上一次触摸事件进行比较,在一定时间范围内返回true,对该点击事件不做分发,按钮没有接受到点击事件,也就实现不再创建新的activity。

activity launchMode

一.Activity的四种启动模式:

以下观点全部来自网上诸大神,总结一下,留着自己用,错误地方请指正;

窃取别人的activity四种启动模式测试的Demo,免积分共享下载:http://download.csdn.net/detail/wk1063645973/9526197


当应用运行起来后就会开启一条线程,线程中会运行一个任务栈,当Activity实例创建后就会放入任务栈中。

可以根据实际的需求为Activity设置对应的启动模式,从而可以避免创建大量重复的Activity等问题。

Activity的启动模式可以通过AndroidManifest.xml文件中的<activity>标签/元素的属性Android:launchMode来指定/设置例如:

<activity android:name="ActivityMain" android:launchMode="singleTask"></activity>;
1.standard(默认启动模式)

来了intent,每次都创建新的实例。

Standard是默认的模式每开始一个activity,就会在栈中加一个activity,相同的也会加,所以加多少个,就要按多少次返回键才能回到最初的界面

默认启动模式,每次激活Activity时都会创建Activity,并放入任务栈中,永远不会调用onNewIntent()

我们平时直接创建的Activity都是这种模式的Activity,这种模式的Activity的特点是:只要你创建了Activity实例,一旦激活该Activity,则会向任务栈中加入新创建的实例,退出Activity则会在任务栈中销毁该实例。

对于使用standard 模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。

例如A启动AA再接着启动AA继续启动A,然后再分别出栈,如图所示

 

默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。例如:
若我有一个Activity名为A1,上面有一个按钮可跳转到A1。那么如果我点击按钮,便会新启一个Activity A1叠在刚才的A1之上,再点击,又会再新启一个在它之上……
back键会依照栈顺序依次退出。


2.singleTop

来了intent,每次都创建新的实例,仅一个例外:当栈顶的activity恰恰就是该activity的实例(即需要创建的实例)时,不再创建新实例。这解决了栈顶复用问题,想一想,你按两次back键,退出的都是同一个activity,这感觉肯定不爽。

如果任务栈的栈顶已经存在这个activity的实例,不会创建新的activity,而是利用旧的activity实例调用旧的activityonNewIntent()方法;

作用:避免一个糟糕的用户体验,如果这个界面已经被打开且在任务栈的栈顶,就不会重复开启了;

如果在任务的栈顶正好存在该Activity的实例, 就重用该实例,并调用其onNewIntent(),否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例,而不会调用onNewIntent(),此时就跟standard模式一样)  

这种模式会考虑当前要激活的Activity实例在任务栈中是否正处于栈顶,如果处于栈顶则无需重新创建新的实例,会重用已存在的实例,否则会在任务栈中创建新的实例。

 

可以有多个实例,但是不允许多个相同Activity叠加。即,如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法。例如:
若我有两个Activity名为B1,B2,两个Activity内容功能完全相同,都有两个按钮可以跳到B1或者B2,唯一不同的是B1standardB2singleTop
若我意图打开的顺序为B1->B2->B2,则实际打开的顺序为B1->B2(后一次意图打开B2,实际只调用了前一个的onNewIntent方法)
若我意图打开的顺序为B1->B2->B1->B2,则实际打开的顺序与意图的一致,为B1->B2->B1->B2
3.singleTask

来了intent后,检查栈中是否存在该activity的实例,如果存在就把intent发送给它

如果要激活的那个Activity在任务栈中存在该实例,则不需要创建,只需要把此Activity放入栈顶,并把该Activity以上的Activity实例都pop移除

在任务栈里面只允许一个实例存在,假如02singletask,栈里是:01 02 01 03 若此时开启02,则会复用这个已经存在的activity,并且把当前activity上面其他的activity从任务栈里清空!

相当于条用 onNewIntent()+ClearTop

1.设置了"singleTask"启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了"singleTask"启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。

2.如果设置了"singleTask"启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。

应用场景:浏览器:底层使用的是webkit c内核,初始化一次需要申请很多的内存资源,占用cpu时间,所以使用singletask,保证在任务栈里只会有一个实例存在;

否则就创建一个新的该activity的实例,放入一个新的task栈的栈底。肯定位于一个task的栈底,而且栈中只能有它一个该activity实例,但允许其他activity加入该栈。解决了在一 个task中共享一个activity

如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中(此时不会调用onNewIntent())。 

如果任务栈中存在该模式的Activity实例,则把栈中该实例以上的Activity实例全部移除,调用该实例的newInstance()方法重用该Activity,使该实例处於栈顶位置,否则就重新创建一个新的Activity实例。

  

大家遇到一个应用的Activity供多种方式调用启动的情况,多个调用希望只有一个Activity的实例存在,这就需要ActivityonNewIntent(Intent intent)方法了。只要在Activity中加入自己的onNewIntent(intent)的实现加上Manifest中对Activity设置lanuchMode=“singleTask”就可以。
onNewIntent()非常好用,Activity第一启动的时候执行onCreate()---->onStart()---->onResume()等后续生命周期函数,也就时说第一次启动Activity并不会执行到onNewIntent().而后面如果再有想启动Activity的时候,那就是执行onNewIntent()---->onResart()------>onStart()----->onResume(). 如果android系统由于内存不足把已存在Activity释放掉了,那么再次调用的时候会重新启动Activity即执行onCreate()---->onStart()---->onResume()等。
当调用到onNewIntent(intent)的时候,需要在onNewIntent()中使用setIntent(intent)赋值给ActivityIntent.否则,后续的getIntent()都是得到老的Intent

当活动的启动模式指定为 singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。

 

只有一个实例。在同一个应用程序中启动他的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。
如果是在别的应用程序中启动它,则会新建一个task,并在该task中启动这个ActivitysingleTask允许别的Activity与其在一个task中共存,也就是说,如果我在这个singleTask的实例中再打开新的Activity,这个新的Activity还是会在singleTask的实例的task中。例如:
若我的应用程序中有三个Activity,C1,C2,C3,三个Activity可互相启动,其中C2singleTask模式,那么,无论我在这个程序中如何点击启动,如:C1->C2->C3->C2->C3->C1-C2C1,C3可能存在多个实例,但是C2只会存在一个,并且这三个Activity都在同一个task里面。
但是C1->C2->C3->C2->C3->C1-C2,这样的操作过程实际应该是如下这样的,因为singleTask会把task中在其之上的其它Activity destory掉。
操作:

1.C1->C2;

2.C1->C2->C3;

3.C1->C2->C3->C2;

4.C1->C2->C3->C2->C3->C1;

5.C1->C2->C3->C2->C3->C1-C2;
实际:

1.C1->C2;

2.C1->C2->C3;

3.C1->C2;

4.C1->C2->C3->C1;

5.C1->C2;
若是别的应用程序打开C2,则会新启一个task
如别的应用Other中有一个activitytaskId200,从它打开C2,则C2taskIdI不会为200,例如C2taskId201,那么再从C2打开C1C3,则C2C3taskId仍为201
注意:如果此时你点击home,然后再打开Other,发现这时显示的肯定会是Other应用中的内容,而不会是我们应用中的C1 C2 C3中的其中一个。


4.singleInstance

肯定位于一个task的栈底,并且是该栈唯一的activity。解决了多个task共享一个activity

singleInstance的启动模式更加极端,开启新的activity,会给自己创建一个单独的任务栈不管是从应用内部打开还是通过其他应用调用TaskId是单独的,已存在的则只需调用onNewIntent();

应用场景:

在整个手机操作系统里面只会有一个该activity的实例存在,所以多个应用程序共享这个activity的实例,有线程安全问题!

例如闹铃提醒,将闹铃提醒与闹铃设置分离

 

在一个新栈中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。

当该模式Activity实例在任务栈中创建后,只要该实例还在任务栈中,即只要激活的是该类型的Activity,都会通过调用实例的newInstance()方法重用该Activity,此时使用的都是同一个Activity实例,它都会处于任务栈的栈顶。此模式一般用于加载较慢的,比较耗性能且不需要每次都重新创建的Activity

使用singleInstance 模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。

假设B启动AA启动C,其中A的启动模式为singleInstance,则:

 

返回的页面顺序是C-B-A

只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。例如:
程序有三个ActivityD1,D2,D3,三个Activity可互相启动,其中D2singleInstance模式。那么程序从D1开始运行,假设D1taskId200,那么从D1启动D2时,D2会新启动一个task,即D2D1不在一个task中运行。假设D2taskId201,再从D2启动D3时,D3taskId200,也就是说它被压到了D1启动的任务栈中。
若是在别的应用程序打开D2,假设OthertaskId200,打开D2D2会新建一个task运行,假设它的taskId201,那么如果这时再从D2启动D1或者D3,则又会再创建一个task,因此,若操作步骤为other->D2->D1,这过程就涉及到了3task了。

二.小结:
     这4中模式又分两类,standardsignleTop属于一类,singleTasksignleInstance属于另一类。
    standardsingleTop属性的Activity的实例可以属于任何任务(Task),并且可以位于Activity堆栈的任何位置。比较典型的一种情况是,一个任务的代码执行startActivity(),如果传递的Intent对象没有包含FLAG_ACTIVITY_NEW_TASK属性,指定的Activity将被该任务调用,从而装入该任务的Activity堆栈中。standardsingleTop的区别在于:standard模式的Activity在被调用时会创建一个新的实例,所有实例处理同一个Intent对象;但对于singleTop模式的Activity,如果被调用的任务已经有一个这样的Activity在堆栈的顶端,那么不会有新的实例创建,任务会使用当前顶端的Activity实例来处理Intent对象,换句话说,如果被调用的任务包含一个不在堆栈顶端的singleTop Activity,或者堆栈顶端为singleTopActivity的任务不是当前被调用的任务,那么,仍然会有一个新的Activity对象被创建。
    singleTasksingleInstance模式的Activity仅可用于启动任务的情况,这种模式的Activity总是处在Activity堆栈的最底端,并且一个任务中只能被实例化一次。两者的区别在于:对于singleInstance模式的Activity,任务的Activity堆栈中如果有这样的Activity,那它将是堆栈中的唯一的Activity,当前任务收到的Intent都由它处理,由它开启的其他Activity将在其他任务中被启动;对于SingleTask模式的Activity,它在堆栈底端,其上方可以有其他Activity被创建,但是,如果发给该ActivityIntent对象到来时该Activity不在堆栈顶端,那么该Intent对象将被丢弃,但是界面还是会切换到当前的Activity
在多Activity开发中,有可能是自己应用间的activity跳转,或者夹带其他应用的可复用activity。可能会希望跳转到原来某个activity实例,而非产生多个重复的activity。我们可借助activity四种启动模式来实现不同的需求:


以上是关于使用LaunchMode方式防止多次创建activity的总结的主要内容,如果未能解决你的问题,请参考以下文章

详解Android开发中Activity的四种launchMode

始终重新创建具有 launchMode singleTop 或 singleTask 的 Activity

android:launchMode的四种方式

activity launchMode

全面解析Activity启动模式(LaunchMode)

Android基础之Activity 运行模式与回退栈