《Android开发艺术探索》学习笔记(一)

本文深入剖析了Android中Activity的生命周期,包括典型情况及异常情况下的生命周期回调,并详细介绍了四种启动模式:standard、singleTop、singleTask和singleInstance的特点与应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Activiy的生命周期和启动模式


1. Activty的生命周期全面分析


1.1 典型情况下的生命周期分析

Activity生命周期的切换过程
1. onCreate: 表示Activity正在被创建,这是生命周期的第一个方法。在这个方法中,我们可以做一些初始化的工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等。
2. onRestart: 表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前Activity就会暂停,也就是onPause和onStop被执行了,接着用户又回到了这个Activity,就会出现这种情况。
3. onStart: 表示Activity正在被启动,即将开始,这时Activity已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候可以理解为Activity已经显示出来了,但是我们还看不到。
4. onResume: 表示Activity已以可见了,并且出现在前台并开始活动,要注意这个和onStart的对比,onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时候Activity才显示到前台
5. onPause:表示Activity正在停止,正常情况下,紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地再回到当前Activity,那么onResume会被调用。此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新Activity的显示,onPause必须先执行完,新Activity才会执行
6. onStop: 表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。
7. onDestory: 表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。

从整个生命周期来说,onCreate和onDestroy是配对的,分别标识着Activity的创建和销毁,并且只可能有一次调用。从Activity是否可见来说,onStart和onStop是配对的。随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次;从Activity是否在前台来说,onResume和onPause是配对的,随着用户操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。

不能在onPause中做重量级的操作,因为必须onPause执行完成以后新Activity才能Resume。通过分析这个问题,我们知道onPause和onStop都不能执行耗时操作,尤其是onPause,这也意味着,我们应当尽量在onStop中做操作,从而使得新Activity尽快显示出来并切换到前台。


1.2 异常情况下的生命周期分析
Activity除了受用户操作所导致的正常的生命周期方法调度,还有一些情况,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。下面我们具体分析这两种情况。
  • 情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建

    比如说当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity.
    

    异常情况下Activity的重建过程


public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i(TAG, "onCreate");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.i(TAG,"onStart");
    }

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

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

    @Override
    protected void onStop() {
        super.onStop();
        Log.i(TAG,"onStop");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.i(TAG,"onRestart");
    }

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

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.i(TAG,"onRestoreInstanceState");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG,"onDestroy");
    }
}

当MainActivity启动后旋转屏幕,打印如下Log:
    10-22 15:13:54.652 13405-13405/? I/MainActivity: onCreate
    10-22 15:13:54.652 13405-13405/? I/MainActivity: onStart
    10-22 15:13:54.652 13405-13405/? I/MainActivity: onResume
    10-22 15:13:57.182 13405-13405/? I/MainActivity: onPause
    10-22 15:13:57.182 13405-13405/? I/MainActivity: onSaveInstanceState
    10-22 15:13:57.182 13405-13405/? I/MainActivity: onStop
    10-22 15:13:57.182 13405-13405/? I/MainActivity: onDestroy
    10-22 15:13:57.232 13405-13405/? I/MainActivity: onCreate
    10-22 15:13:57.232 13405-13405/? I/MainActivity: onStart
    10-22 15:13:57.242 13405-13405/? I/MainActivity: onRestoreInstanceState
    10-22 15:13:57.242 13405-13405/? I/MainActivity: onResume

当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机是在onStop之前,它和onPause没有既定的时序关系,它即可能在onPause之前调用,也可能在onPause之后调用。需要强调的一点是,这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。
  • 情况2:资源内存不足导致低优先级的Activity被杀死

     Activity按照优先级从高到低,可以分为如下三种:
       1. 前台Activity——正在和用户交互的Activity,优先级最高。
       2. 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
       3. 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
    

    当系统内存不足时,系统会按照上述优先级去杀死目标Activity所在的进程,并后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。

    为了避免系统配置发生改变后Activity重建,可以给Activity指定configChanges属性。比如不想让Activity在屏幕旋转的时候重新创建,就可以给configChanges属性添加orientation这个值,如下所示。
    android:configChanges="orientation"
    
项目含义
mccSIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由三位数字组成,中国为460.此项标识mmc代码发生了改变
mncSIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03.此项标识mnc发生改变
locale设备的本地位置发生了改变,一般指切换了系统语言
touchscreen触摸屏发生了改变,这个很费解,正常情况下无法发生,可以忽略它
keyboard键盘类型发生了改变,比如用户使用了外插键盘
keyboardHidden键盘的可访问性发生了改变,比如用户调出了键盘
navigation系统导航方式发生了改变,比如采用了轨迹球导航,这个有点费解,很难发生,可以忽略它
screenLayout屏幕布局发生了改变,很可能是用户激活了另外一个显示设备
fontScale系统字体缩放比例发生了改变,比如用户选择了一个新的字号
uiMode用户界面模式发生了改变,比如用户开启夜间模式
orientation屏幕方向发生了改变,这个是最常用的,比如旋转了手机屏幕
screenSize当屏幕尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化,这个选项比较特殊,它和编译选项有关,当编译选项中的minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API13新添加)
smallestScreenSize设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没有关系,仅仅表示在实际物理屏幕的尺寸改变的时候发生,比如用户切换到了外部显示设备,这个选项和screenSize一样,当编译选项中的minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API13新添加)
layoutDirection当部局方向发生变化时,这个属性用的比较少,正常情况下无须修改部局的layoutDirection属性(API17新添加)

2.Activity的启动模式

2.1Activity的LaunchMode

目前有四种启动模式:standard,singleTop,singleTask,singleInstance.
1. standard: 标准模式,这也是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例的生命周期符合典型情况下Activity的生命周期。这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。比如Activity A 启动了Activity B(B是标准模式),那么B就会进入到A所在的任务栈中。
2. singleTop:栈顶利用模式。在这种情况下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个Activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。如果新Activity的实例已存在但不是位于栈顶,那么新Activity仍然会重新重建。举个例子,假设目前栈内的情况为ABCD,其中ABCD为四个Activity,A位于栈底,D位于栈顶,这个时候假设再次启动D,如果D的启动模式为singleTop,那么栈内的情况仍然为ABCD;如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况就变为ABCDD。
3. singleTask:栈内复用模式。这是一种单实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其onNewIntent方法。具体一点,当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放到栈中。如果存在A所需的任务栈,这时要看A是否在栈中有实例存在,如果有实例存在,那么系统就会把A调到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的实例并把A压入栈中。
- 比如目前任务栈S1中的情况为ABC,这个时候Activity D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例并将其入栈到S2。
- 另外一种情况 ,假设D所需的任务栈为S1,其他情况如上面例子1所示,那么由于S1已经存在,所以系统会直接创建D的实例并将其入栈到S1.
- 如果D所需的任务栈为S1,并且当前任务栈S1的情况为ABCD,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。
4. singleInstancd:单实例模式。这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中,换句话说,比如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。


Activity的Flags
  • FLAG_ACTIVITY_SINGLE_TOP
    这个标记位的作用是为Activity指定singleTask启动模式,其效果和在XML中指定该启动模式相同。
  • FLAG_ACTIVITY_SINGLE_TOP
    这个标记位的作用是为Activity指定singleTop启动模式,其效果和在XML中指定该启动模式相同。
  • FLAG_ACTIVITY_CLEAR_TOP
    具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般需要和FLAG_ACTIVITY_NEW_TASK配合使用,在这种情况下,被启动的Activity的实例如果已经存在,那么系统就会调用它的onNewIntent。如果被启动的Activity采用standard模式启动,那么它连同之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    具有这个标记位的Activity不会出现在历史Activity的列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。它等同于在XML中指定Activity的属性android:excludeFromRecents=”true”。

3.IntentFilter的匹配规则

  1. action的匹配规则
    action是一个字符串,系统预定义了一些action,同时我们也可以在应用中定义自己的action。action的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,这里说的匹配是指action的字符串完全一样。一个过滤中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。action区分大小写。
  2. category的匹配规则
    category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。category的匹配规则和action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同。换句话说,Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义了的category。当然,Intent中可以没有category,如果没有category的话,按照上面的描述,这个Intent仍然可以匹配成功。这里要注意下它和action匹配过程不同,action是要求Intent中必须有一个action且必须能够和过滤规则中的某个action相同,而catetory要求Intent可以没有category,但是如果你一旦有catetory,不管有几个,每个都要能够和过滤规则中的任何一个catetory相同。为什么不设置category也可以匹配呢?原因是系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上”android.intent.category.DEFAULT”这个category,所以这个category就可以匹配匹配前面的过滤规则中的第三个category。同时,为了我们的activity能够接收隐式调用,就必须在intent-filter中指定”android.intent.category.DEFAULT”这个category。
  3. data的匹配规则
    data的匹配规则和action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。
    data的语法如下所示。
<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathPrefix="string"
      android:mimeType="string" />

data由两部分组成,mimeType和URI。mimeType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示图片、文本、视频等不同的媒体格式,而URI中包含的数据就比较多了,下面是URI的结构

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

几个实例,如下所示

content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info

下面介绍一下每个数据的含义。
Scheme: URI的模式,比如http、file、content等。如果URI中没有指定scheme,那么整个URI的其它参数无效,这也意味着URI是无效的。
Host: URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数无效,这也意味着URI是无效的。
Port: URI中的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。
Path、pathPattern和pathPrefix: 这三个参数表述路径信息,其中path表示完整的路径信息;pathPattern也表示完整的路径信息,但是它里面可以包含通配符“*”,“*”表示0个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“*”要写成“\\*”,”\”要写成”\\\\”;pathPrefix表示路径的前缀信息。

分情况说明data的匹配规则
1. 如下过滤规则:

<intent-filter>
    <data android:mimeType="image/*"/>
</intent-filter>

这种规则指定了媒体类型为所有类型的图片,那么Intent中的mimeType属性必须为“image/*”才能匹配,这种情况下虽然过滤规则没有指定URI,但是却有默认值,URI的默认值为content和file。也就是说,虽然没有指定URI,但是Intent中的URI部分的schema必须为content或者file才能匹配,这点是需要注意的。为了匹配1中的规则,我们可以写出如下示例:

intent.setDataAndType(Uri.parse("file://abc"),"image/png")

另外,如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法彼此会清除对方的值,比如setData:

public Intent setData(Uri data) {
        mData = data;
        mType = null;
        return this;
    }
  1. 如下过滤规则:
<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
 </intent-filter>

这种规则指定了两组data规则,且每个data都指定了完整的属性值,即有URI又有mimeType。为了匹配2中规则,我们可以写出如下示例:

intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg")

或者

intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值