前言
想起来写这个是因为之前在开发过程中遇到在onStart()方法中使用getIntent()方法无法获取到启动activity时传入的数据,也是纳闷了很久,然后就决定好好看一下这个地方,本文章会结合简单的demo先说说四个启动模式,搞清楚启动模式之后,就明白为什么getIntent()得不到我们想要的数据了。
开发环境: Android Studio 3.0.1
设备: 逍遥安卓 5.1.1
解决办法
因为感觉文章写的非常啰嗦,但是又怕讲的不够清楚,如果想直接找到解决方案的可以直接看这里。重写目标Activity的onNewIntent方法。然后你就能在onStart中通过getIntent得到正确的值了。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
Android四种启动模式
1. standard
默认启动模式,每次激活Activity时都会创建Activity,并放入任务栈中。
2. singleTop
如果在任务的栈顶正好存在该Activity的实例, 就重用该实例,否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例)。
3. singleTask
如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中。
4. singleInstance
在一个新栈中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。
Activity启动模式设置:
<activity android:name=".MainActivity" android:launchMode="standard" />
DEMO
只是看概念可能会有一些抽象,所以就结合代码示例来理解下,当然其实我觉得在经过一段时间的开发后,我们应该要能通过官方文档就理解知识点,我们需要具备这种能力,才能提高学习效率。这都是题外话,接下来就顺着代码分析。
首先为了减少重复代码写一个基类Activity,加上我们需要的日志。这里我们有一点要注意的地方,不要用系统的返回按钮返回到主界面,因为那样会销毁当前的Activity的,所以在当前Activity上加一个按钮用来返回主界面。为了看看不同启动模式Activity的区别,所以就让启动的Activity到后台而不是销毁掉。
如图所示,点击BACK按钮返回主界面,而不是小三角。
/**
* Created by wanghang on 2018/5/17.
*/
public class BaseActivity extends Activity {
private String TAG = this.getClass().getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: ");
super.onCreate(savedInstanceState);
setContentView(R.layout.second);
Button back = findViewById(R.id.main);
//回到主界面的时候按这个按钮,不要使用系统的返回按键,那样会直接销毁当前activity,因为我这里没有对onBackPressed方法做处理。
back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(v.getContext(), MainActivity.class));
}
});
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String test = intent.getStringExtra("test");
if (test != null) {
Log.d(TAG, "onNewIntent: " + test);
}
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart: ");
}
@Override
protected void onStart() {
super.onStart();
Intent intent = getIntent();
String test = intent.getStringExtra("test");
if (test != null) {
Log.d(TAG, "onStart: " + test);
}
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
}
然后写几个BaseActivity的派生类,直接继承就行,什么也不要做,像下面这样。
/**
* Created by wanghang on 2018/5/17.
*/
public class SingleInstance extends BaseActivity {
}
然后我们需要在AndroidManifest.xml文件中注册我们的Activity,并且设置不同的启动方式。像下面这样就行。
<activity
android:name=".SingleTask"
android:launchMode="singleTask"/>
<activity
android:name=".SingleTop"
android:launchMode="singleTop"/>
<activity
android:name=".SingleInstance"
android:launchMode="singleInstance"/>
上面的步骤完成后,再写我们的主界面,主要就是添加几个按钮,用来启动上面的几个不同启动模式的Activity。
界面就是下图这样简陋,哈哈哈,Button的名字已经显示了对应的启动Activity的启动模式。
这个界面对应的activity的代码如下,两个按钮都是触发启动activity的,只是Intent携带的信息不同。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button top = findViewById(R.id.top);
Button task = findViewById(R.id.task);
Button instance = findViewById(R.id.instance);
top.setOnClickListener(this);
task.setOnClickListener(this);
instance.setOnClickListener(this);
Button top2 = findViewById(R.id.top2);
Button task2 = findViewById(R.id.task2);
Button instance2 = findViewById(R.id.instance2);
top2.setOnClickListener(this);
task2.setOnClickListener(this);
instance2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.top:
startActivity(SingleTop.class);
break;
case R.id.task:
startActivity(SingleTask.class);
break;
case R.id.instance:
startActivity(SingleInstance.class);
break;
case R.id.top2:
startActivity2(SingleTop.class);
break;
case R.id.task2:
startActivity2(SingleTask.class);
break;
case R.id.instance2:
startActivity2(SingleInstance.class);
break;
}
}
private void startActivity(Class targetActivity) {
Intent intent = new Intent(MainActivity.this, targetActivity);
intent.putExtra("test", "test");
startActivity(intent);
}
private void startActivity2(Class targetActivity) {
Intent intent = new Intent(MainActivity.this, targetActivity);
intent.putExtra("test", "test2");
startActivity(intent);
}
}
所有的代码工作就完成了,运行看看日志咯。
- SingleTop
点击主界面的SingleTop按钮(它绑定的是startActivity事件),进入到启动模式为SingleTop的Activity,然后点击BACK按钮,回到主界面,再点击SIngleTop2按钮(绑定的是startActivity2事件)进入Activity。
三个红色框代表三次点击事件的日志,可以看出来,即使在栈中已经存在了SingleTop的实例,但是他是在后台的,这时如果再次调用启动Activity的方法,是会重新再生成一个新的SingleTop示例的,两次都触发了onCreate()方法,然后是onNewIntent()方法都是没有触发的,在onStart()中接受到的数据也都是主界面传过来的正确数据。
- SingleTask
操作同上,直接看结果
很显然,即使当前Activity去到后台,但是从主界面通过startActivity2启动时,没有生成新的实例,而是直接让栈中的实例来到前台。并且onNewIntent()方法也是触发的,但是大家应该注意到了onStart()接收的数据是不匹配的,并没有更新。原因是为什么呢?
因为通过startActivity的方式启动一个Activity实例时,如果栈中不存在该Activity的实例时,会经过完成的生命周期,也会调用Activity的setIntent(Intent intent)方法,但是如果已经存在时就不会经过完整的生命周期了,而是调用onNewIntent()方法,而这个方法在Activity的源码中是没有做任何操作的,既没有改变这个实例中的mIntent的值
说了半天有点我自己都觉得拗口,上源码看一下吧。
第一次启动时的生命周期:onCreate–>onStart,会调用到setIntent()
第二次启动(实例在栈中只是不在前台):onNewIntent–>onRestart–>onStart,上文的日志是可以看出这些的。关键点就在于这个onNewIntent中什么都没干,实现是空的,源码中也写出了这一点。提醒你重写此方法并调用setIntent()方法。就是在最前面我提到的那样。
SingleInstance
对于这个问题,SingleInstance和SingleTask是完全一样的(当然我可没说两啥都一样)。然后对与这四种启动模式我这里并没有详细的解释,如果不是很了解摸这里,这个写的非常好。
结语
昨天晚上写了一晚上的简历,也没有写到自己满意,今天和导师好好的聊了一个小时,他跟我说了很多,也给了我后面该怎么做的意见,让我注重基础的学习,努力钻研技术,他说看得出来我对技术非常热情,交代了很多很多我要注意的知识点还特别强调了我要加强自己的沟通能力。非常感谢他,以后也不知道有没有机会见面了,明天他就离职了。希望自己不辜负他的希望,好好干。加油吧!