Activity的生命周期

从活动一跳转到活动二:

image.png

在跳转中执行完onPause之后马上就执行活动二的onCreate方法,等活动二的onResume执行完后才执行活动一的onStop方法,所以不能再onPause方法里执行耗时操作


image.png

当Activity异常终止的时候onSaveInstanceState和onRestoreInstanceState就会被调用,用来储存和恢复数据,其他情况下不会触发这个过程

  • 在活动异常终止不可见时马上调用储存数据的方法,当活动要运行到前台可见之前调用恢复数据的方法
  • 每个View都有这两个方法,了解他们的具体实现后就知道系统能够自动为每个view恢复哪些数据
  • 注:onSaveInstanceState一定是在onStop方法前调用,但和onPause方法没有既定的时序关系,有可能在这个方法之前调用,也有可能在这个方法之后调用
  • 什么情况为异常终止:
  1. 资源相关的系统配置发生改变导致Activity被杀死并重新创建
    例如从横屏状态突然带竖屏状态
  2. 资源内存不足导致优先级低的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);
    }
}

结果:

image.png

可以用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);
        }
    }
}

image.png

控制屏幕翻转后活动不重新创建
1.在AndroidManifest.xml文件里配置

android:configChanges="orientation|screenSize"

如下:

<activity android:name=".MainActivity"
            android:configChanges="orientation|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

2.在活动里从写onConfigurationChanged方法

 @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d(TAG, "onConfigurationChanged: 最新的位置"+newConfig.orientation);
    }

这样就不会再调用onSaveInstanceState和onRestoreInstanceState方法了

image.png

从图中可以看出这个方法被调用了两次!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);
            }
        });
  • 每点击一次按钮就会重新创建一个相同的活动


    image.png

修改

android:launchMode="singleTop"

点击了两次,back了一次的执行情况

image.png
补充说明任务栈

重要参数:TaskAffinity(任务相关性)

  • 每个 Activity 运行时都有一个其归属的 task栈,我们可以用 activity.getTaskId() 的方法得到当前 activity 的taskId。如果两个 activity 的 taskId 不同,则他们肯定不会属于同一个 task。
  • Android 手机的任务列表就是根据不同 task 弹出的,我们可以根据任务管理器有几个 item 图标,来知道我们开启了几个 task。
  • 指定活动2的栈测试使用方法:
<activity android:name=".Main2Activity"
            android:taskAffinity="com.example.task1">
        </activity>
  • 如上图所示,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());

结果:

image.png

结论:

  • 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);

结果:


image.png

完整测试及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():


image.png

没加:
修改:

 intent2.putExtra("music","hey kong2");

结果:

image.png

还是上一个intent结果,没有改变
加了setIntent():

image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容