Android Activity启动模式分析

2019-04-14 20:04发布

概述: Activity是Android系统四大组件之一,向用户展示UI界面,在开发中及其重要,本文主要是介绍Activity的四种启动模式,其中涉及到进程,任务栈,任务亲和度的一些概念。
谈到Activity,相信大家都不陌生,Android世界的Hello World就是从Activity开始,没有Activity,用户看不到APP的任何东西,在日常开发中,启动Activity方式很简答,在mainifest.xml进行配置,在代码中 调用startActivity方法,即可启动一个Activity,启动一个Activity,会将Activity放入任务栈中。Activity有四种启动模式,standard, singleTask, singInstance, singleTop。一般情况下,我们默认使用的是standard,这种启动方式在大部分情况下是没有问题的,但是有些情况下,未必如此。下面我们先熟悉几个概念,然后分析Activity四种启动模式有什么不同,以及在什么情况下,需要更改默认的启动方式。
进程: Android系统是基于Linux内核,每启动一个新的应用程序,会启动一个新的进程,应用程序中的操作运行在该进程空间中,但是这不是绝对的,有些时候,我们为了应用程序能够获取一些特殊的权限,会设置APP运行在已经存在的进程中。一个应用程序中,如果要启动的新的组件在manifest中配置了android:process属性,那么该组件可以在新的进程中启动,利用这个机制,我们可以在新的进程中启动一个Service,完成一些功能相对独立的操作,降低程序的耦合性。
任务: Android task是一个相对抽象的概念,我们可以简单的理解为一系列连续操作的集合,一个task对应一个Activity栈,可以跨越不同应用,比如说:用户在自定义的Activity中通过startActivity方式启动浏览器的Activity,那么这两个Activity就可以看做一个任务,而且,这两个启动的Activity,默认情况下,是放在一个Activity栈中。
任务亲和度(taskAffinity): 任务亲和度对应到Android task,很多时候,任务亲和度相同的Activity组件属于同一个Task,放在同一个Activity stack。

Activity有四种启动模式
Standard: Standard是标准模式,manifest.xml配置一个Activity,如果没有指定launcherMode的话,默认是用该模式,该模式下Activity启动是怎样的行为呢。我们先看一张图

从图上看,对于Standard,每次启动一个Activity,都将该Activity放入到Activity栈中,不管栈中是否存在相同的Activity,这种情况下,可能一个栈中存在多个相同的Activity,一直按返回,一直显示相同的界面。
SingleTop: 从字面意思来理解singleTop,很简单,单个top,即启动一个Activity,将其放入到Activity栈的过程中,如果发现栈顶的activity是当前需要启动的activity的话,则不会重新创建新的Activity,放入Activity栈中,而是调用栈中现有的该Activity的onNewIntent方法。如果栈顶activity不是当前要启动的Activity,那么会将当前要启动的Activity放入栈中。和Standard比较而言,使用singleTop的Activity,栈顶不会存在多个实例,其他行为两者是一样的。
SingleTask:  还是从字面意思理解,设置了该LauncherMode的activity,在启动的过程中,会新建一个Task,然后将该Activity放入Task中,可实际上呢。先编个码来看一下。 public class MainActivity extends Activity { private Button mBtnStartSecond; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnStartSecond = (Button) findViewById(R.id.btn_startSecond); mBtnStartSecond.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); } }); } } SecondActivity: private Button mBtnStartThird; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); mBtnStartThird = (Button) findViewById(R.id.btn_startThird); mBtnStartThird.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(SecondActivity.this, ThirdActivity.class); startActivity(intent); } }); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.e("SecondActivity", "SecondActivity onNewIntent"); } private Button mBtnStartSecond; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); mBtnStartSecond = (Button) findViewById(R.id.btn_startSecondfromThird); mBtnStartSecond.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(ThirdActivity.this, SecondActivity.class); startActivity(intent); } }); }




说好的new task呢。我们这里设置了SecondActivity的启动模式为singleTask,怎么还是一个Task里面呢。 我们再来改一改,把SecondActivity的亲和度设置设置。 android:taskAffinity="com.duke.launchermode.taskAffinitysecond"
再来试一下。


启动SecondActivity
从日志上看,SecondActivity的onNewIntent调用
并没有新建一个SecondActivity对象,而是调用了onNewIntent方法,在Stack中,ThirdActivity被清除。这里注意:ThirdActivity和MainActivity是相同的taskAffinity,但是ThirdActivity由SecondActivity启动的时候,是和SecondActivity处于同一个Stack.
结论:singleTask模式启动的Activity,并不一定会从新的task中启动。对于一个singleTask启动模式的Activity 1:如果该Activity不存在任何task中(即未启动),先判断其taskAffinity是否和启动其的Activity的taskAffinity一样,如果一样,则在原来的Stack中启动(开始时SecondActivity和MainActivity在一个Stack),如果这连个Activity的taskAffinity不一样,则会在新的task中启动Activity 2,如果该Activity已经存在于某个Task中,这时候,启动Activity,不会创建新的Activity实例,而是调用其onNewIntent方法,并且清除该Activity上面所有的Activity实例(如果存在),即从ThirdActivity启动SecondActivity的情况。

singleInstance:从singleTask的分析看,设置了singleTask启动模式的Activity也只可能存在一个实例,singleInstance模式的Activity也只存在一个实例,二者有什么不同呢。
现在设置SecondActivity的launcherMode为singleInstance
SecondActivity使用了singleInstance,即使taskAffinity和MainActivity一样,也新建了一个task,并把这个对象放入到新的task中,再从SecondActivity启动ThirdActivity

由SecondActivity启动ThirdActivity,ThirdActivity和MainActivity处于同一个task。这点和singleTask也不同。从这里可以看到,使用singleInstance模式启动的Activity,一定要独占一个task。


再从ThirdActivity启动SecondActivity,没有创建新的SecondActivity对象,而是调用了onNewIntent方法。
结论:singleInstance和singleTask有相似的地方,二者都是指存在一个实例,如果已经存在了该实例,那么调用startActivity,会调用onNewIntent。但是singleInstance有其独特之处: 1,启动一个singleInstance模式的Activity,如果该Activity不存在一个实例,则会在一个new task中创建一个实例,不管taskAffinity是否和启动它的Activity的taskAffinity是否相同。 2,从一个singleInstance模式的Activity启动另外一个Activity,被启动的Activity一定会在其他的task中创建,singleInstance模式的Activity独占task。 3,启动一个singleInstance模式的Activity,如果该Activity存在一个实例,会调用实例的onNewIntent 方法。


通过以上分析,我们已经搞清楚了Activity四种启动模式的工作原理,下面谈谈具体应用
一般情况下,使用默认的启动模式可以满足开发需求,但是Standard模式并不是一直能那么给力。大家如果用过知乎APP就会发现一个问题。点击进入瞎扯:如何正确地吐槽,滑动到底部,有一个本文来自:瞎扯:合集。 点击进去,如果你再进入一个瞎扯,重复操作,点击文章底部的来自:瞎扯:合集,如此重复操作一直深入,你会发现,当你按返回的时候,会一直在文章和瞎扯列表中重复,其实我只是想退出程序而已,为什么要点这么多次呢。这个倒是没有什么错误,只是感觉操作不方便。
另外一个应用场景是基于Webkit开发的类似于浏览器的应用,一般只有一个Activity,从webkit js扩展对象启动第三方APP,第三方APP中,又通过startActivity方式启动了浏览器应用,我们希望看到的现象是已经存在的浏览器被转到前段,而不是又重新启动一个浏览器。这时候,standard就无法胜任该需求了。
总结:弄清楚Activity的启动模式是很重要,也很必要的,实际应用中,还需要结合程序的具体需求,选用合适的启动模式,才能达到理想的效果。