本文转载请联系Android开发编程公众号。前言:Activity作为Android四大组件之一,几乎是接触最多的;Android管理Activity,Android使用Task来管理多个Activity,当我们启动一个应用时,Android会为它创建一个Task,然后启动这个应用的入口Activity;在实际开发项目中,会存在多个Activity,系统使用任务栈来存放创建的Activity实例。任务栈是一个“后进先出”的栈结构。比如我们多次启动同一个Activity,系统会创建多个实例,一个一个的放入任务栈中。当按下返回键返回时,每按一次栈就会从栈中弹出一个Activity,直到栈为空。;没有任何Activity,系统会回收这个任务栈;因此,在Android基础中,Activity的启动方式非常重要;本文将全面详细介绍Activity的启动方式。指在执行特定工作时与用户交互的一系列活动。这些Activity按照打开的顺序排列在栈中(即返回栈)。设备主屏幕是大多数任务的起点。当用户触摸应用程序启动器中的图标(或主屏幕上的快捷方式)时,应用程序的任务将出现在前台。如果应用程序不存在任务(应用程序最近没有被使用过),将创建一个新任务,并将应用程序的“主”Activity作为堆栈中的根Activity打开;②当当前Activity启动另一个Activity时,新的Activity会被Push到栈顶,成为焦点。先前的活动仍保留在堆栈中,但处于停止状态。当活动停止时,系统会保持其用户界面的当前状态。当用户按下Back按钮时,当前的Activity从栈顶弹出(Activity被销毁),之前的Activity恢复执行(恢复其UI之前的状态)。堆栈中的Activity永远不会重新排列,只会推送和弹出:当前Activity启动时推送;当用户使用返回按钮退出时弹出。因此,返回栈以“后进先出”的对象结构运行;③任务是一个有机的整体,当用户开始新的任务或通过“主页”按钮进入主屏幕时,可以移动到“后台”。虽然在后台任务中的所有活动都停止了,但任务的返回堆栈保持不变,即当另一个任务发生时,任务只是失去焦点。然后,任务可以返回到“前台”,用户可以返回到他们离开的状态;④由于后台堆栈中的活动永远不会重新排列,如果应用程序允许用户从多个活动中启动特定活动,它会将创建的活动的新实例压入堆栈(而不是将活动的任何先前实例到顶部)。因此,应用程序中的活动可能会被实例化多次(即使活动来自不同的任务)。2.任务栈(1)当程序打开时,会创建一个任务栈来存放当前程序的活动,所有的活动都属于一个任务栈。(2)一个任务栈包含一个活动的集合,以有序地选择哪个活动与用户交互:只有任务栈顶部的活动才能与用户交互。(3)可以将任务栈移到后台,并保留每个activity的状态,并将它们的任务有序的列出给用户,它们的状态信息不丢失。(4)退出应用程序时:当所有任务栈中的所有活动都清空出栈时,任务栈将被销毁,程序退出。(5)每打开一个页面,都会向任务栈中添加一个Activity,只有当任务栈中的所有Activity都被清空出栈时,任务栈才会被销毁,程序才会执行exit,导致用户体验差,需要多次点击返回退出程序。(6)每打开一个页面,都会在任务栈中加入一个Activity,这样也会造成数据冗余。重复数据过多会导致内存溢出(OOM)。为了解决任务栈的不足,我们引入了launch模式。启动模式(launchMode)在多个Activity跳转的过程中起着重要的作用。它可以决定是否生成新的Activity实例,是否复用已有的Activity实例,是否与其他Activity实例共享一个任务;有一个启动模式的概念,即standard、singleTop、singleTask和singleinstance。二、启动方式详解1、standardstandard为标准启动方式。当我们不指定Activity的启动模式时,默认就是这种模式。在标准模式下,每启动一个Activity,都会创建一个新的实例,并调用它的onCreate、onStart、onResume。这个新创建的Activity将被放置在启动它的Activity所在的任务堆栈的顶部。比如ActivityA在栈S中,它启动了ActivityB(标准模式),那么B就会进入A所在的栈S。如果你在没有任务栈的标准模式下启动一个Activity,比如在Service中,此时新的Activity没有任务栈可以进入,就会出现异常:?应为此活动指定FLAG_ACTIVITY_NEW_TASK,以便创建新的任务堆栈。2.singleTopsingleTop为栈顶复用模式。在这种模式下,如果新启动的Activity已经在任务栈顶,则不会重新创建新的实例,而是调用这个Activity的onPause、onNewIntent、onResume方法。如果新启动的Activity不在栈顶,还是会重新创建。比如栈中当前的情况是ABCD四个活动,A在栈底,D在栈顶。如果D的启动模式是singleTop,那么D的实例不会再创建,栈还是ABCD。如果上面的D是标准的启动方式,那么栈就会变成ABCDD。3.singleTasksingleTask是一种栈内复用模式。这是最复杂的模式,因为它可能涉及多个堆栈。当一个singleTask模式的Activity启动时,比如ActivityA,系统会先查找需要的任务栈是否存在,如果不存在则重新创建一个任务栈,然后创建A的实例并将A放入栈中。如果有A需要的任务栈,则要看栈中是否有A的实例。如果是,系统会将其转移到栈顶并调用其onNewIntent方法。如果它不存在,则创建一个A实例的实例并将A压入堆栈。这里说的A需要的任务栈是什么意思?其实Activity可以指定它想要的任务栈的名字,通过一个参数:TaskAffinity,默认情况下,所有Activity需要的任务栈的名字都是应用的包名。如果任务栈S1中的情况是ABC,此时ActivityD请求以singleTask模式启动,它需要的任务栈是S2。由于S2和D实例都不存在,系统会先创建任务栈S2,然后再创建一个D的实例并推入S2。如果上面D需要的任务栈是S1,那么因为S1已经存在,所以系统直接创建一个D的实例,并推给S1。如果D需要的任务栈是S1,但是S1中的情况是ADBC,此时不会重新创建D,而是将D切换到栈顶,调用onNewIntent方法。B和C呢?它们都会被出栈,相当于clearTop效果。4.singleInstancesingleInstance是单实例模式。这种模式是singleTask的增强版。除了具有singleTask的所有特性外,还加强了一点,即该模式下的Activity只能位于单个任务栈中。例如,ActivityA处于singleInstance模式。当A启动时,系统会创建一个新的任务栈,然后在这个新的任务栈中只有A一个人。由于栈中的多路复用特性,后续请求不会创建新的Activity,除非堆栈被销毁;3、启动模式设置详解启动模式设置有两种方式:在AndroidMainifest中设置,通过Intent设置标志位。1.在AndroidMainifest的Activity配置中设置2.通过IntentIntentinten=newIntent(ActivityA.this,ActivityB.class);intent.addFlags设置标志位(意图,FLAG_ACTIVITY_NEW_TASK);启动活动(意图);FLAG_ACTIVITY_SINGLE_TOP:指定启动模式为栈顶复用模式(SingleTop)FLAG_ACTIVITY_NEW_TASK:指定启动模式为栈复用模式(SingleTask)FLAG_ACTIVITY_CLEAR_TOP:必须移动上层的所有activity除了,SingleTask模式默认有这个标志效果;FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:带有该标志的Activity不会出现在历史Activity列表中,即无法通过历史列表返回到本Activity;3、两种不同Intent设置方式的优先级>Manifest设置方式,即前者为准;Manifest设置方法不能设置FLAG_ACTIVITY_CLEAR_TOP;Intent设置方法不能设置单例模式(SingleInstance);四、启动模式的实际应用场景1、SingleTask模式的应用场景最常见的应用场景就是我们的应用打开后只保留一个Activity实例。最典型的例子就是应用程序中显示的主页(Homepage)。假设用户在首页跳转到其他页面,执行多次操作后想返回首页。如果不使用SingleTask模式,在点击返回的过程中会看到多次首页。这显然是一个不合理的设计。2、SingleTop模式的应用场景假设你想在当前Activity中启动同类型的Activity。这时候建议将此类Activity的启动方式指定为SingleTop,可以减少Activity的创建,节省内存!3.SingleInstance模式应用场景SingleInstance是一种启动Activity的模式。一般很少用于应用层开发。我通常使用此模式进行应用时间提醒。使用这种模式有很多陷阱。假设有3个activity,activityA,activityB,activityC。我们将activityB设置为SingleInstance。第一种情况,A打开B,B打开C,如果你完成了activityC,那么就会显示activityA,而不是我们想要的我需要的activityB,因为activityB和activityA、activityC的栈是不一样的。当C关闭时,会显示C栈上的下一个activity。有很多方法可以解决这个问题。我使用的方法是通过record打开activity,在关闭的activity的finish方法中重新打开activityB。第二种情况,A打开B,然后按home键,然后从左边点击应用,显示A。这是因为当launch启动我们的应用时,它会从默认栈中找到栈顶显示的activity。这个解决方案的思路和第一个差不多,就不秀丑了。第三种情况,A开启C,C开启B,B开启A,结果显示C,这是两个堆栈造成的。当B打开A时,其实是到达了A所在的栈,栈顶是C,所以显示的是C。解决办法是用flag清除默认的stackactivity,重启A,或者转回C再启动A。三种情况的解决方案都是基于page少的情况。如果页面太多,就会出现更多问题。为了避免这个问题,中间层最好不要使用SingleInstanceTIPS:(1)如果想让C和B是同一个栈,那就用taskinfinity,并且给两者设置相同的栈名(2)onActivityResult不能和SingleInstance一起使用,因为栈不同4、标准的应用场景,Activity的启动默认是这种模式。在标准模式下,每次启动一个Activity都会创建一个新的实例;在一个普通的应用中正常打开和关闭页面就足够了,退出整个应用后所有的页面都会关闭5.一个Activity的生命周期是不同的,因为当一个Activity设置为SingleTop或SingleTask模式或SingleInstance模式后,当跳转到这个Activity并复用原来的Activity时,这个Activity的onCreate方法就不会再运行了。onCreate方法只会在第一次创建Activity时运行。一般来说,onCreate方法会初始化页面的数据和UI。假设页面的显示数据与页面跳转传递的参数无关,则不用担心这个问题。如果页面显示的数据是通过getInten()方法获取的,那么就会出现问题:getInten()获取的总是旧数据,无法接收到跳转中传输的新数据!这时候我们就需要另一个回调onNewIntent(Intentintent)方法。这个方法会传入最新的intent,这样我们就可以解决上面的问题了。这里建议的方法是再次setIntent。然后再去初始化数据和UI/**复用Activity时的生命周期回调*/@OverrideprotectedvoidonNewIntent(Intentintent){super.onNewIntent(intent);setIntent(intent);initData();initView();}6.实际栈管理类管理Activity的类。一般在BaseActivity中会调用这个类,然后所有Activity都继承BaseActivity,从而管理整个项目的Activity/***activity栈管理*/publicclassActivityStackManager{privatestaticActivityStackManagerInstance;privatestaticStackmActivityStack;publicstaticActivityStackManagergetInstance(){if(null==mInstance){mInstance=newActivityStackManager();}returnmInstance;}privateActivityStackManager(){mActivityStack=newStack();}/***入栈**@paramactivity*/publicvoidaddActivity(Activityactivity){mActivityStack.push(activity);}/***弹出**@paramactivity*/publicvoidremoveActivity(Activityactivity){mActivityStack.remove(activity);}/***完全退出*/publicvoidfinishAllActivity(){Activityactivity;while(!mActivityStack.empty()){activity=mActivityStack.pop();if(activity!=null){activity.finish();}}}/***结束活动指定类名**@paramcls*/publicvoidfinishActivity(Class>cls){for(Activityactivity:mActivityStack){if(activity.getClass().equals(cls)){finishActivity(activity);}}}/***查找栈中是否存在指定的活动**@paramcls*@return*/publicbooleancheckActivity(Class>cls){for(Activityactivity:mActivityStack){if(activity.getClass().equals(cls)){returntrue;}}returnfalse;}/***结束束指定的Activity**@paramactivity*/publicvoidfinishActivity(Activityactivity){if(activity!=null){mActivityStack.remove(activity);activity.finish();activity=null;}}/***finish指定的活动之上所有的活动**@paramactCls*@paramisIncludeSelf*@return*/publicbooleanfinishToActivity(ClassactCls,booleanisIncludeSelf){Listbuf=newArrayList();intsize=mActivityStack.size();Activityactivity=null;for(inti=size-1;i>=0;i--){activity=mActivityStack.get(i);if(activity.getClass().isAssignableFrom(actCls)){for(Activitya:buf){a.finish();}returntrue;}elseif(i==size-1&&isIncludeSelf){buf.add(activity);}elseif(i!=size-1){buf.add(activity);}}returnfalse;}}总结1、以上是启动方式和应用场景的总结Activity的,除了singleTask有点复杂,其他的都很容易理解。2.其实启动模式是实际应用中必须要学习的一个知识点。如果不使用它而只是学习它,则无法抓住本质。只有当你真正使用它时,你才会改变这些变量。把它变成你自己的;3.有不懂的可以随时发信息问我