Android四大组件之Activity

一. Activity的生命周期

1. 正常情况

Android四大组件之Activity_第1张图片
Activity生命周期
  • 当用户打开新的Activity或者切换到桌面时,回调为:onPause > onStop。 假如新的Activity为采用透明主题时,则当前Activity不会回调onStop。

  • 当用户按back回退时,回调为:onPause > onStop > onDestroy。

  • 不能在onPause中做重量级的操作,因为onPause执行完成后新Activity才能启动。

2. 异常情况

  • 转屏
    默认情况下,Activity会被销毁然后重建。生命周期为:onPause > onSaveInstanceState(该方法在onStop之前调用,与onPause没有确定的时序关系,只会在Activity被异常终止即将被销毁并且有机会重新显示的情况下调用,正常状态不会调用 ) > onStop > onDestroy > onCreate > onStart > onRestoreInstanceState>onResume

  • 资源内存不足导致优先级低的Activity被kill
    该情况的数据存储和恢复过程与上面情况一致。

当系统配置发生改变后,Activity会被重建,系统配置中有很多内容,当某项内容发生改变后,可以通过给Activity指定configChanges属性,不让系统重建Activity。具体属性如下:

  • mcc :The IMSI mobile country code (MCC) has changed — a SIM has been detected and updated the MCC. (IMSI-国际移动用户识别码中 国家代码发生改变,该代码由三位数组成,中国为460)

  • mnc :The IMSI mobile network code (MNC) has changed — a SIM has been detected and updated the MCC.(IMSI中运营商代码,由两位数组成)

  • locale:The locale has changed — the user has selected a new language that text should be displayed in.(本地设置改变,一般指切换了系统语言)

  • touchscreen:The touchscreen has changed. (This should never normally happen.)

  • keyboard:The keyboard type has changed — for example, the user has plugged in an external keyboard. (键盘类型发生改变,例如,用户使用了外部键盘)

  • keyboardHidden:The keyboard accessibility has changed — for example, the user has revealed the hardware keyboard.)(键盘的可访问性发生改变,例如,用户调出了键盘)

  • navigation:The navigation type (trackball/dpad) has changed. (This should never normally happen.) (导航发生改变,这通常不应该发生。举例:连接蓝牙键盘,连接后确实导致了navigation的类型发生变化。因为连接蓝牙键盘后,我可以使用方向键来navigate了)

  • screenLayout:The screen layout has changed — this might be caused by a different display being activated. (屏幕的布局发生改变,这可能导致激活了另一个显示设备)

  • fontScale:The font scaling factor has changed — the user has selected a new global font size.

  • orientation:The screen orientation has changed — that is, the user has rotated the device. (设备旋转,横向显示和竖向显示模式切换。)

  • screenSize: 屏幕大小改变了,转屏也会改变,当minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启

  • smallestScreenSize: 屏幕的物理大小改变了,如:连接到一个外部的屏幕上,当minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启

  • layoutDirection(4.2新增):当改变语言设置后,该属性也会成newConfig中的一个mask位。所以ActivityManagerService(实际在ActivityStack)在决定是否重启Activity的时候总是判断为重启。需要在android:configChanges 中同时添加locale和layoutDirection。

如:android:configChangeds="orientation | keyboardHidden | screenSize" 添加在AndroidManifest.xml的Activity的声明里。Activity不会被重建,也没有调用onSaveInstanceState和onRestoreInstanceState方法,系统调用了Activity的onConfigurationChanged方法。

二. Activity的启动方式

默认情况下,当我们多次启动同一个activity时,系统会创建多个实例并一一放入任务栈中。任务栈的工作原理是后进先出。

什么是任务栈:

  1. task是存在于framework层的一个概念,用于控制界面的跳转和返回。这个task存在于一个称为back stack的数据结构中,所以framework是以栈的形式管理用户开启的activity。

  2. 当app中存在A,B,C三个Activity,点击图标启动主Activity A,接着A启动B,B启动C,这是栈中有三个Activity,并且这三个Activity默认在同一个task中,当一直按返回键时,C,B,A相继被弹出,task被移除。

  3. task是可以跨应用的,也可以跨进程。有的Activity虽然不在同一个app中,但为了保持用户操作的连贯性,可以把它们放在同一个task中。比如某个应用中Activity A点击发送短信,会启动短信app的一个Activity B来发送邮件,此时AB是在同一个task中的, 当发送完短信后,按back键返回,可以返回到Activity A中。

  4. 任务栈分为前台和后台任务栈,后台任务栈中的Activity处于暂停状态,用户可以通过切换讲后台任务栈再次调到前台。

TaskAffinity:

可以理解为任务相关性,通过设置该属性可以标识一个Activity所需要的任务栈名字。

  1. 当Activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被分配到相同的TaskAffinity的任务栈中。

  2. 默认情况下,所有Activity所需的任务栈名字应为应用的包名。也可以为每个Activity单独指定TaskAffinity属性,这个属性值不能和包名相同。

  3. 为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。

  4. 假如Activity A未设置taskAffinity,在Activity A中以singleTask方式启动Activity B,但Activity B未设置不同的taskAffinity,Activity A跟Activity B会在同一个任务栈中。因为Activity B所需的任务栈已经在启动Activity A时创建,所以不需要重新创建。

  5. 当跟allowTaskReparenting一起使用时,会产生特殊的效果。当应用A启动了应用B的某个Activity后,如果这个activity的allowTaskReparenting设为true,当应用B启动后,该activity会直接从应用A的任务栈转移到应用B的任务栈中。举个例子,在电子邮件中打开一个web网页,按home键回到主页界面后,再打开浏览器,就能看到之前的web网页。
    allowTaskReparenting的主要作用是activity的迁移,即从一个task迁移到另一个task,这个迁移跟activity的taskAffinity有关,必须是从一个跟该activity taskAffinity不同的task中迁移到跟它taskAffinity相同的task中,该属性在AndroidManifest中设置

四种启动模式(launchMode)

  • standard:标准模式即默认模式。每次启动一个activity都会重新创建一个实例,不管该实例是否存在。谁启动了这个Activity,这个Activity就运行在启动它的那个Activity的任务栈中。假如用Application Context去启动standard模式的activity,则会报错,因为非Activity类型的context(如Application context)并没有所谓的任务栈。

  • singleTop:栈顶复用模式。该模式下,如果新Activity已经位于任务栈的栈顶,那么该Activity不会被重新创建,同时它的onNewIntent()方法会被回调,通过此方法的参数可以取出当前请求的信息。该Activity的onCreate和onStart不会被调用,因为它并没有发生改变。适用界面如搜索界面。

  • singleTask:栈内复用模式。这是一种单实例模式。该模式下,只要Activity在一个栈中存在,那多次启动该Activity都不会重新创建实例,跟singleTop一样,系统也会回到onNewIntent()方法。
    eg:
    1.任务栈S1中的情况为ABC,Activity D以singleTask模式请求启动,所需任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例将其入栈到S2。
    2.假如D所需的任务栈为S1,其它如1,则系统会直接创建D的实例并入栈到S1,S1的则变成ABCD。
    3.如果D所需的任务栈为S1,S1为ADBC,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent()方法,同时singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,所以S1变为AD。

  • singleInstance:单实例模式。具有此种模式的Activity只能单独得位于一个任务栈中。如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中。比如闹钟响起后弹出的提示界面。

还有一种情况,假设现在有2个任务栈,前台任务栈的情况为AB,后台任务栈的情况为CD,且CD的启动模式均为singleTask。现在请求启动D,则整个后台任务栈都会被切换到前台,此时整个后退列表变成了ABCD,当用户按back键时,ABCD会依次出栈,假如请求启动C,则整个后退列表变成ABC。

Flags

  • FLAG_ACTIVITY_NEW_TASK
    当Intent对象包含这个标记时,系统会寻找或创建一个新的task来放置目标Activity,寻找时依据目标Activity的taskAffinity属性进行匹配,如果找到一个task的taskAffinity与之相同,就将目标Activity压入此task中,如果查找无果,则创建一个新的task,并将该task的taskAffinity设置为目标Activity的taskActivity,将目标Activity放置于此task。效果不完全等同于设置“singleTask”。singleTask启动模式默认具有该效果。

  • FLAG_ACTIVITY_SINGLE_TOP
    效果同在xml中设置Activity启动模式为singleTop。

  • FLAG_ACTIVITY_CLEAR_TOP
    具有此标志位的Activity,当它启动时,在同一个任务栈中所有位于它上面的activity都要出栈。一般需要和FLAG_ACTIVITY_NEW_TASK配合使用。singleTask启动模式默认具有该效果。

  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    具有该标记的Activity不会出现在历史Activity的列表中。等同于Activity属性android:excludeFromRecents="true"。

三. Activity的调用方式

Activity的启动分为显式调用和隐式调用两种。

显式调用
直接指定需要打开的activity对应的类,明确指定被启动对象的组件信息,包括包名和类名。

  • 构造方法传入Component
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
  • setComponent方法
ComponentName componentName = new ComponentName(this, SecondActivity.class); 
Intent intent = new Intent();
intent.setComponent(componentName); 
startActivity(intent);
  • setClass/setClassName方法
Intent intent = new Intent();
intent.setClass(this, SecondActivity.class); 
startActivity(intent);

隐式调用
需要Intent能匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity,会抛出ActivityNotFoundException,可以使用try catch,当然最好的提前判断,可以使用PackageManager的resolveActivity方法或Intent的resolveActivity方法,如果找不到匹配的Activity就会返回null,通过判断返回值就可以避免异常的出现。

Intent intent = new Intent(Intent.ACTION_XXX);
ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null) {
  startActivity(intent);
}

只有一个Intent同时匹配action(动作)、category(类别)、data(数据)才算完全匹配。

一个Activity中可以有多个intent-filter,一个intent只要能匹配任一组intent-filter就能启动对应的Activity。

  • action的匹配规则
  1. intent中action必须能够和过滤规则中的任一个action相同(字符串值一样,区分大小写)即匹配成功。

  2. 若intent中缺少action,则不会匹配。

  • category的匹配规则
  1. intent中如果有category,那么所有的category必须和过滤规则中的其中一个category相同。

  2. 若intent中没有category,intent也可能匹配成功。因为系统在调用startActivity或startActivityForResult时,会默认为intent加上“android.intent.category.DEFAULT”,此时必须在intent-filter中指定category为“android.intent.category.DEFAULT”

  • data的匹配规则
    data由两部分组成,mimeType和URI。
    mimeType:指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,表示图片,文本,视频等不同的媒体格式。
    URI:由三部分组成。scheme://host:post/path ( 模式://主机:端口/路径 )
  1. 如果intent-filter中只有mimeType,那intent中的mimeType必须一致才能匹配。虽然过滤规则没有指定URI,但URI的默认值为content和file,所以intent中URI部分的scheme必须为content或file才能匹配。

  2. 如果intent-filter有两组data规则,那只要intent的data满足其中某一组就可以匹配成功。

注:为intent指定完整的data时,必须调用setDataAndType方法,不能先调用setData再调用setType,反之也不行,因为这个两个方法会相互消除对方的值。

内容参考:《Android开发艺术探索》

你可能感兴趣的:(Android四大组件之Activity)