从活动一跳转到活动二:
在跳转中执行完onPause之后马上就执行活动二的onCreate方法,等活动二的onResume执行完后才执行活动一的onStop方法,所以不能再onPause方法里执行耗时操作
当Activity异常终止的时候onSaveInstanceState和onRestoreInstanceState就会被调用,用来储存和恢复数据,其他情况下不会触发这个过程
- 在活动异常终止不可见时马上调用储存数据的方法,当活动要运行到前台可见之前调用恢复数据的方法
- 每个View都有这两个方法,了解他们的具体实现后就知道系统能够自动为每个view恢复哪些数据
- 注:onSaveInstanceState一定是在onStop方法前调用,但和onPause方法没有既定的时序关系,有可能在这个方法之前调用,也有可能在这个方法之后调用
- 什么情况为异常终止:
- 资源相关的系统配置发生改变导致Activity被杀死并重新创建
例如从横屏状态突然带竖屏状态 - 资源内存不足导致优先级低的Activity被杀死
测试数据储存和恢复
测试代码如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//onCreate如果是正常启动参数为null
if (savedInstanceState!=null){
String test=savedInstanceState.getString("extra_text");
Log.d(TAG, "onCreate恢复数据:"+test);
}
Button button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent=new Intent(MainActivity.this,Main2Activity.class);
startActivity(intent);
}
});
Log.d(TAG, "onCreate: ");
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart: ");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG, "onSaveInstanceState: ");
outState.putString("extra_text","text");
}
//onRestoreInstanceState一旦被调用其参数一定是有值的
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String test=savedInstanceState.getString("extra_text");
Log.d(TAG, "onRestoreInstanceState恢复数据:"+test);
}
}
结果:
可以用Bundle传输数据
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//用Bundle传输数据
Intent intent=new Intent(MainActivity.this,Main2Activity.class);
Bundle bundle=new Bundle();
bundle.putString("name","Tom");
bundle.putInt("age",20);
intent.putExtras(bundle);
//用intent传输数据
intent.putExtra("nickname","lkl");
startActivity(intent);
}
});
public class Main2Activity extends AppCompatActivity {
private static final String TAG = "Main2Activity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
//获取数据
Intent intent1=getIntent();
//接收intent的传输
String nickName=intent1.getStringExtra("nickname");
Log.d(TAG, "onCreate别名[intent]):"+nickName);
//接收Bundle的传输
Bundle bundle=intent1.getExtras();
if (bundle!=null){
String name=bundle.getString("name");
int age=bundle.getInt("age");
Log.d(TAG, "onCreate名字[bundle]: "+name);
Log.d(TAG, "onCreate年龄[bundle]:"+age);
}
}
}
控制屏幕翻转后活动不重新创建
1.在AndroidManifest.xml文件里配置
android:configChanges="orientation|screenSize"
如下:
2.在活动里从写onConfigurationChanged方法
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(TAG, "onConfigurationChanged: 最新的位置"+newConfig.orientation);
}
这样就不会再调用onSaveInstanceState和onRestoreInstanceState方法了
从图中可以看出这个方法被调用了两次!Why?
1.当推出键盘的时候,会触发硬件的改变,使手机竖屏变成了横屏
2.当推进键盘的时候,合上手机的一刻,触发的是同一样的固件,由于,固件是无法辨别那个是推进,和推出,发出的是一样的信号,然后系统就会认为这是一个横屏改变,等到合上手机的时候系统再接受到一个信号,然后切换成竖屏.这样系统,认为横屏切换竖屏改变了两次,这样就导致调用了两次onConfigurationChanged();
再加一个Button控件测试不同的启动模式
在对应的活动中添加:
android:launchMode="standard"
在活动中的代码:
Button button2=findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent2=new Intent(MainActivity.this,MainActivity.class);
startActivity(intent2);
}
});
-
每点击一次按钮就会重新创建一个相同的活动
修改
android:launchMode="singleTop"
点击了两次,back了一次的执行情况
补充说明任务栈
重要参数:TaskAffinity(任务相关性)
- 每个 Activity 运行时都有一个其归属的 task栈,我们可以用 activity.getTaskId() 的方法得到当前 activity 的taskId。如果两个 activity 的 taskId 不同,则他们肯定不会属于同一个 task。
- Android 手机的任务列表就是根据不同 task 弹出的,我们可以根据任务管理器有几个 item 图标,来知道我们开启了几个 task。
- 指定活动2的栈测试使用方法:
- 如上图所示,taskaffinity 可以单独对一个 activity 使用,代表该 activity 所想归属的 task;
也能对application 使用,代表该 application 内声明的所有 activity 都归属于这个task。这个属性值必须和包名不同,否则就相当于没有指定 - 如果 activity 组件没有声明 taskAffinity 的话,该 activity 的 taskAffinity 属性也是有默认值的。如果 application 指定了 taskAffinity 值,默认值就是 application 指定的 taskAffinity 值;如果 application 未指定的话,默认值就是 manifest 中声明的包名(package 对应的字符串)
- 是不是我指定了一个 Activity 的 taskAffinity 值(跟包名不同),运行该 Activity 时,是否就会新开这个 task栈呢?答案是否定的,一个 Activity 运行时所归属的task,默认是启动它 的那个Activity 所在的 task
在活动1和2分别打印栈的id
Log.d(TAG, "活动1的栈: "+this.getTaskId());
Log.d(TAG, "活动2的栈: "+this.getTaskId());
结果:
结论:
- taskAffinity 单独使用并不会生效。
要想其生效,需要配合其他属性使用,或者配合 Intent.FLAG_ACTIVITY_NEW_TASK,或者配合allowTaskReparenting 。使用时用其中的一个就行
测试:
在跳转的intent中加入:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
总体主要代码:
Intent intent=new Intent(MainActivity.this,Main2Activity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
结果:
完整测试及log日志:
我们假定都是 Activity A 跳转到 Activity B 中,A没有指定 taskAffinity 属性,B 的launchMode 为standard。
case1: A、B 属同一App, intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 未指定 taskAffinity 属性
D/MyApplication: onActivityResumed+MainActivity####taskid = 61
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 61
- 可以验证:一个Activity 归属的task 是由 启动它的 Activity 所决定的。
case2: A、B 属同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 指定 taskAffinity 属性,但与包名相同
D/MyApplication: onActivityResumed+MainActivity####taskid = 62
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 62
- 可以验证,一个 Activity 的默认 task 值就是 manifest 定义的包名。
case3: A、B 属同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 指定 taskAffinity 属性,但与包名不同
D/MyApplication: onActivityResumed+MainActivity####taskid = 63
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 63
- 可以验证:不指定 FLAG_ACTIVITY_NEW_TASK的话, 即使 taskAffinity 不同,一个Activity 归属的task 仍然是由 启动它的 Activity 所决定的。
case4: A、B 属同一App,intent 指定 FLAG_ACTIVITY_NEW_TASK,B 未指定 taskAffinity 属性
D/MyApplication: onActivityResumed+MainActivity####taskid = 64
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 64
- 可以验证:即使 使用了 FLAG_ACTIVITY_NEW_TASK,但由于两者的 taskAffinity 相同,所以仍然不会开启一个新的task。
case5: A、B 属同一App,intent 指定 FLAG_ACTIVITY_NEW_TASK,B 指定 taskAffinity 属性,且和包名不同
D/MyApplication: onActivityResumed+MainActivity####taskid = 65
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 66
- 可以验证:开启一个新task 的条件是 FLAG_ACTIVITY_NEW_TASK 和 taskAffinity 不同 缺一不可。
case6: A、B 属同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 未指定 taskAffinity 属性,B启动模式为 singletask or singletop
D/MyApplication: onActivityResumed+MainActivity####taskid = 67
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 67
- 可以得出:未指定 FLAG_ACTIVITY_NEW_TASK 和 新的 taskAffinity 时,这两种启动模式对task 没有影响
case7: A、B 属同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK,B 未指定 taskAffinity 属性,B启动模式为 singleinstance
D/MyApplication: onActivityResumed+MainActivity####taskid = 70
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 71
- 可以得出:
singleinstance 启动模式本身就是会开启一个新的task 装载 这个Activity,且task 中只有这一个 Activity。 - 经判断得知,AMS 是先对 launchMode 做判断 再处理 FLAG_ACTIVITY_NEW_TASK 的,如果是 singleinstance ,则会直接开启一个task。
onNewIntent方法与启动模式
前提:ActivityA已经启动过,处于当前应用的Activity任务栈中;
当ActivityA的LaunchMode为Standard时:
由于每次启动ActivityA都是启动新的实例,和原来启动的没关系,所以不会调用原来ActivityA的onNewIntent方法
当ActivityA的LaunchMode为SingleTop时:
如果ActivityA在栈顶,且现在要再启动ActivityA,这时会调用onNewIntent()方法 ,生命周期顺序为:
onCreate--->onStart--->onResume---onPause>onNewIntent--->onResume
当ActivityA的LaunchMode为SingleInstance,SingleTask:
如果ActivityA已经在堆栈中,那么此时会调用onNewIntent()方法,生命周期调用顺序为:
onCreate--->onStart--->onResume---按下Home键>onPause--->onstop--->onNewIntent--->onRestart--->onstart--->onResume
测试代码:
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent2=new Intent(MainActivity.this,MainActivity.class);
intent2.putExtra("music","hey kong");
startActivity(intent2);
}
});
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
String musicIs=getIntent().getStringExtra("music");
Log.d(TAG, "onNewIntent:1 "+musicIs);
}
注意:
有时候,我们在多次启动同一个栈唯一模式下的activity时,在onNewIntent()里面的getIntent()得到的intent感觉都是第一次的那个数据。对,这里就是这个陷阱。因为它就是会返回第一个intent的数据。就是这么坑。
原因就是我们没有在onNewIntent()里面设置setIntent(),将最新的intent设置给这个activity实例。
加了setIntent():
没加:
修改:
intent2.putExtra("music","hey kong2");
结果:
还是上一个intent结果,没有改变
加了setIntent():