Activity的声明周期和启动模式

正常情况下Activity的声明周期

先来看看官方给出的声明周期图:
这里写图片描述
这里需要说明如下几点:

  1. 针对特定Activity,第一次启动,回调如下:onCreate->onStart->onResume
  2. 当用户打开新的activity或者切换到桌面的时候,会回调onPause->onStop. 如果新的activity采用了透明的主题,那么当前activity不会回调onStop
  3. 当用户再次回到原activity时候,会回调如下:onRestart->onStart->onResume
  4. 当用户按下back键回退的时候,回调如下:onPause->onStop->onDestroy
  5. 当两个activity之间切换的时候,会先调用旧activity的onPause,然后新activity才启动。所以我们不能再onPause中做重量级操作,因为旧的activity的onPause先执行完成,新activity才能resume

异常情况下activity的生命周期

很多时候我们的activity可能会由于系统资源的紧缺被系统杀死,或者系统配置发生变化等

系统配置发生改变导致activity被杀死并重新创建

如果当前activity从横屏直接旋转到竖屏,此时由于系统配置发生了变化,在默认情况下,activity就会被销毁并重新创建。

当系统配置发生变化之后,activity会被销毁,其onPause,onStop,onDestroy均会被回调,同时由于activity是在异常情况下终止的,系统会回调onSaveInstanceState来保存当前activity的状态。onSaveInstanceState是在onStop之前调用的,onRestoreInstanceState是在onStart之后调用的

我们来看下面的栗子:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 这里当activity正常启动的时候,savedInstanceState是null,所以需要做非空判断
        if (null != savedInstanceState) {
            String str = savedInstanceState.getString("killedStr");
            Log.d(TAG,"onCreate runs...str is :"+str);
        }
    }

    /**
     * 当activity被销毁,并重新创建时候,回调该方法获取之前保存的数据
     */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG,"onRestoreInstanceState runs...");
        String str = savedInstanceState.getString("killedStr");
        savedInstanceState.putString("killedStr","testString");
        Log.d(TAG,"onRestoreInstanceState runs...str is :"+str);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(TAG,"onSaveInstanceState runs...");

        outState.putString("killedStr","testString");
    }

}

可以看到,我们可以同时在onRestoreInstanceState和onCreate中获取之前保存的数据,区别在于在onRestoreInstanceState方法中,其参数一定不是null,而在onCreate中,如果当前activity正常启动的时候,其参数为null

onSaveInstanceState方法,系统只会在Activity即将被销毁,并且有机会重新显示的情况下才会去调用它

资源内存不足导致activity被杀死

通常情况下我们的activity可以分为下面三种优先级:

  1. 前台activity,表示正在和用户进行交互的activity
  2. 可见但非前台activity。
  3. 后台activity
    到现在为止,我们知道了,当系统配置发生更改以后,activity会重新创建,我们可以给activity指定configChanges属性,来防止activity重新创建。常用的有locale,orientation,keyboardHidden.

需要注意到的是,在sdkversion > 13时候,为了防止旋转屏幕activity重启,除了orientation还需要添加screenSize

activity的启动模式

activity的LaunchMode

activity有四种启动模式:

  • standard
    标准模式,也是系统默认模式,每次启动一个activity都会重新创建一个新的实例,不管该实例是否已经存在,在该模式下,谁启动了和这个activity,那么这个activity就运行在启动它的那个activity所在的任务栈中。比如我们在service中启动标准模式的activity,就会遇到下面这种错误。
  • singleTop
    在这种模式下,如果新activity已经位于任务栈的栈顶,那么此activity不会被重新创建,同时它的onNewIntent会被回调,该activity的onCreate,onStart,不会被系统调用。如果新的activity实例已经存在但不是位于栈顶,那么新的activity仍然会重新创建。
    比如下面的activity启动过程:
    这里写图片描述
  • singleTask
    singleTask是一种単例模式,在该种模式下,只要activity在一个栈中存在,那么多次启动该activity都不会重新创建实例。系统也会调用其onNewIntent。

当一个具有singleTask模式的activity A请求启动之后,系统会先寻找是否存在A想要的任务栈,如果不存在,就会重新创建一个任务栈,然后创建A的实例,并把A放到栈中,如果存在A所需要的任务栈,这时候要看A是否在栈中有实例存在,如果有实例存在,那么系统会把A调到栈顶并调用它的onNewIntent方法,如果实例不存在,就会创建A的实例,并把A放入栈中。

举下面栗子:

  • 目前任务栈S1中的情况为ABC,此时Activity D以singleTask模式请求启动,其所需要的任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后创建D的实例并将其放入S2中。
    这里写图片描述
  • 假设D所需要的任务栈是S1,其他情况和上面相同,由于S1已经存在,所以系统会直接创建D,并将其放入到栈S1
  • 如果D所需要的任务栈为S1,并且当前任务栈S1的情况为ADBC根据栈内服用原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop效果,会导致栈内所有D上面的activity全部出出栈,最小S1中的情况为AD.
    - singleInstance
    单实例模式,该模式除了具有singleTask模式的所有特性之外,另外需要注意的是,具有该种模式的activity只能够单独位于一个任务栈中。

TaskAffinity

默认情况下,所有的activity所需要的任务栈的名字为当前的包名,我们可以单独为每个activity指定TaskAffinity属性,TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting配对使用。

  • 当TaskAffinity和singleTask启动模式配对使用的时候,此时待启动的singleTask模式的activity所在的任务栈,和TaskAffinity是相同的。
  • 当TaskAffinity和allowTaskReparenting结合使用的时候,当一个应用A启动了应用B的某一个activity,如果B的allowTaskReparenting属性为true,那么当应用B被启动以后,此activity会直接从应用A的任务栈转移到应用B的任务栈中。

为activity指定启动模式

我们可以分别在代码中或者AndroidManifest.xml里为activity指定启动模式。

// 在AndroidManifest.xml中指定
android:launchMode="singleTask"

// 在代码中指定,优先级较高
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
singleTask实栗
为MainActivity添加singleTask
// 为MainActivity指定启动模式为singleTask
 <activity
            android:name="com.example.launchmode.MainActivity"
            android:launchMode="singleTask"
            android:label="@string/app_name" >
</activity>

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.id_text).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                Intent intent = new Intent(MainActivity.this,MainActivity.class);
                MainActivity.this.startActivity(intent);
            }
        });
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.d(TAG,"onNewIntent runs....");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"onPause runs....");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"onResume runs....");
    }

此时我们连续点击三次,启动当前MainActivity。
这里写图片描述
此时由于我们指定当前MainActivity的启动模式为singleTask,所以在默认的栈中,只有一个MainActivity实例,所以此时我们虽然启动了三次,但是按下back键盘的时候,会直接返回到桌面。
通过adb shell dumpsys activity 来查看当前的activity和其任务栈
这里写图片描述

为MainActivity去掉singleTask

现在我们为MainActivity去掉singleTask,做相同的操作。
这里写图片描述

Activity的Flags

  • FLAG_ACTIVITY_NEW_TASK
    这个标记位的作用是为Activity指定”singleTask”启动模式。
  • FLAG_ACTIVITY_SINGLE_TOP
    这个标记位的作用是为Activity指定”singleTop”启动模式。
  • FLAG_ACTIVITY_CLEAR_TOP
    当启动具有该标记为的activity,在同一个任务栈中,所有位于它上面的Activity都要出栈,该模式一般需要和 FLAG_ACTIVITY_NEW_TASK配合使用,在这种情况被启动activity如果已经存在,那么系统就会调用它的onNewIntent,如果被启动的activity采用standard模式启动,那么连同他之上的activity都要出栈,系统会创建activity实例,并放入到栈顶。

IntentFilter的匹配规则

我们都知道,隐式启动一个activity时候,就需要匹配当前activity的IntentFilter设置,如果匹配不成功,将无法启动目的activity,IntentFilter中设置的过滤信息有action,category,data。
为了匹配过滤列表,需要同时匹配action,category,data信息,否则会匹配失败,只有一个intent同时匹配action类别,category类别,data类别才算完全匹配,另外一个activity可能有多个intent-filter,一个intent只要能够匹配任何一组intent-filter即可以成功启动对应的activity

action的匹配规则

一个过滤规则中可以有多个action,只要intent中的action只要能够和其中的一个action相同即可匹配成功。action的匹配而规则要求intent中的action存在且必须和过滤规则中的一个action相同。

category的匹配规则

我们的代码中可以没有category,但是XML中要加上”android.intent.category.DEFAULT”这句。

如果你在代码中定义了一个或者多个category,那么你必须跟XML文件中定义的一样。比如你定义了一个category,那么要在XML文件中匹配到一个,,如果你定义了多个category,那么要在XML文件中全部匹配

data的匹配规则

data的匹配规则和action类似,它也要intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个data,这里的完全匹配是指过滤规则中出现的data部分也出现在了Intent的data中。

如果要为intent指定完整的data,必须要调用setDataAndType方法,因为setData和setType方法中,会彼此清除掉对方设置的值

对intent的匹配结果做判断

如果我们使用intent匹配了一个activity,并且会启动当前的activity,那么如果当前的activity不存在即匹配失败的话,就会出现ActivityNotFoundException这样的错误。

我们可以采用PackageManager的resolveActivity或者intent的resolveActivity方法,如果找不到匹配的activity,就会返回null,我们通过返回值可以规避上面的错误。

另外PackageManager还提供了queryIntentActivities方法,这个方法和resolveActivity不同之处在于:它返回的不是最佳匹配的activity,而是所有成功匹配的activity。

public abstract List<ResolveInfo> queryIntentActivities(Intent intent,int flags);

public abstract ResolveInfo resolveActivity(Intent intent,int flags);

上面两个方法第二个参数,使用时候需要注意,我们需要使用MATCH_DEFAULT_ONLY这个标记为,该标记为的含义是仅仅匹配在intent-filter中声明了

<category android:name="android.intent.category.DEFAULT"/>

这样的activity。这样做的好处在于只要上面两个方法返回值不是null,则startActivity一定可以成功。

ok,今天就到这里了,本篇博客来自android开发艺术探索的学习笔记。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值