Android开发艺术探索读书笔记 第一章 Activity的生命周期和启动模式

1. Activity生命周期全面分析
  1. 生命周期
    • onCreate:表示Activity正在被创建
    • onRestart:表示Activity正在重新启动
    • onStart:表示Activity正在启动,Activity已经可见,但是还没有出现在前台
    • onResume:表示Activity已经可见,并且出现在前台开始活动
    • onPause:表示Activity正在停止,可以存储数据、停止动画等,不能太耗时
    • onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,不能太耗时
    • onDestroy:表示Activity即将被销毁,可以做一些回收工作和最终的资源释放
  2. Activity生命周期切换过程
    1. 针对一个特定的Activity,第一次启动,回调如下:onCreate-onStart-onResume
    2. 当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause-onStop。如果新的Activity采用了透明主题,那么当前Activity不会回调onStop。
    3. 当用户再次回到原Activity,回调如下:onRestart-onStart-onResume。
    4. 当用户按back回退键时,回调如下:onPause-onStop-onDestroy
    5. onStart和onStop是从Activity是否可见这个角度来回调的,而onResume和onPause是从Activity是否位于前台这个角度来回调的。
    6. 从Activity A跳转到Activity B,Activity A的onPause先执行,Activity B的onResume在执行。
    7. onActivityResult() 和onResume()的调用顺序问题,回到调用activity时会马上执行onActivityResult方法,然后才是onResume()方法。
  3. 异常情况下的生命周期分析

    1. 资源相关的系统配置发生改变导致Activity被杀死并重新创建
      在Activity异常终止的情况下,Activity被重新创建后,系统会调用 onSaveInstanceState ,并且把Activity销毁时onSaveInstanceState 方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate()方法,我们可以通过上述两个方法判断Activity是否被重建了,如果重建了,我们可以取出数据并恢复,从时序上来讲,onRestoreInstanceState的调用时机在onStart之后。
      在onSaveInstanceState 和onRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作,系统会默认为我们保存当前Activity的视图结构,并且在Activity重启后为我们恢复这些数据,比如文本框输入的数据、ListView滚动的位置等。
      onRestoreInstanceState和onCreate()方法的区别是,onRestoreInstanceState一旦被调用,其参数一定是有值的,官方建议使用这种方法去恢复数据,onCreate()`不行。
    2. 资源内存不足导致低优先级的Activity被杀死
      Activity的优先级从高到低:
      1. 前台Activity — 正在和用户交互的Activity,优先级最高
      2. 可见但非前台的Activity — 比如Activity中弹出一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
      3. 后台Activity — 已经被暂停的Activity,比如执行了onStop,优先级最低。

        当系统内存不足的时候,系统会按照上述优先级杀死Activity所在的进程,并通过onSaveInstanceStateonRestoreInstanceState方法来存储和恢复数据。如果一个进程没有在4大组件执行,进程很快会被系统杀死。
        当系统配置发生改变后,Activity会被重新创建,我们可以给Activity指定configChanges属性,Activity不会被重新创建,没有调用onSaveInstanceStateonRestoreInstanceState方法,取而代之的是调用onConfigurationChanges方法,如下所示:
      android:configChanges="orientation | keyboardHidden
      主要有如下三个重要属性:
      local 设备的本地位置发生改变,一般指切换了系统语言
      orientation屏幕方向发生了改变,比如旋转了屏幕
      keyboardHidden键盘的可访问性发生了改变,比如用户调出键盘

  4. 横竖屏切换时候Activity的生命周期:

    1. 不设置Activity的android:configChanges,或设置Activity的android:configChanges="orientation",或设置Activity的android:configChanges="orientation|keyboardHidden",切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行一次。
    2. 配置 android:configChanges="orientation|screenSize",不会销毁 activity,且只调用 onConfigurationChanged方法。
2. Activity的启动模式
  1. standard:标准模式
      系统的默认模式,每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的生命周期符合典型情况下的生命周期,它的onCreate、onStart、onResume都会调用。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity栈中。比如Activity A启动了Activity B(B是标准模式),那么B就会进入到A所在的栈中。使用场景大多数Activity。
  2. singleTop:栈顶复用模式
      如果新Activity已经位于任务栈的栈顶,那么次Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法我们可以取出当前请求的信息。这个Activity的onCreate、onStart不会被调用,因为它并没有发生改变。使用场景如新闻类或者阅读类App的内容页面。
  3. singleTask:栈内复用模式
      一种单实例模式,只要Activity在一个栈中存在,多次启动Activity都不会重新创建实例,系统会回调onNewIntent方法。当设置此模式后,比如Activity A会首先寻找是否存在A想要的任务栈,如果不存在就重新创建一个任务栈,如果存在,就看A是否在栈中有实例存在,如果有实例存在那么系统就会把A调到栈顶并调用onNewIntent方法,同时由于singleTask默认具有clearTop的效果,就会导致栈内所有在A上面的Activity全部出栈,如果不存在,就创建A的实例并把A压入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
  4. singleInstance:单实例模式
      这是一种加强的singleTask模式,除了具有singleTask所有特性外,还具有此种模式的Activity A只能单独的位于一个新的任务栈,然后A单独在这个栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。
  5. Activity的Flags
    1. 设置启动模式的两种方式
      通过AndroidMenifest为Activity指定启动模式
      android:launchMode="singleTask"
      Intent中设置标记为来为activity指定启动模式
      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    2. 常用标记位
      FLAG_ACTIVITY_NEW_TASK
      为Activity指定singleTask启动模式
      FLAG_ACTIVITY_SINGLE_TOP
      为Activity指定singleTop启动模式
      FLAG_ACTIVITY_CLEAN_TOP
      具有此标记位的Activity,当它启动时,同一个任务栈中所有位于它上面的Activity都要出栈。
      FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
      不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用,等同于在XML中指定Activity的属性android:excludFromRecents="true".
3. IntentFilter的匹配规则

为了匹配过滤列表,需要同时匹配过滤列表中的action、category、data信息,否则匹配失效。一个过滤列表中的action、category和data可以有多个。一个Intetn同时匹配action类别、category类别、data类别才算完全匹配,只有完全匹配才能启动目标Activity。一个Activity中可以有多个intent-filter,一个Intent只要匹配其中任何一组intent-filter即可成功启动对应的Activity。
1. action的匹配规则
  action是一个字符串,系统预定义了一些action,我们也可以自定义action,匹配规则是Intent中的action必须能够和过滤规则中的action匹配,一个过滤规则中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功,Intent中的action存在且必须和过滤规则中的其中一个action相同。
2. category的匹配规则
  category是一个字符串,系统预定义了一些category,我们也可以在应用中定义自己的category。category和action的匹配规则不同,要求Intent中如果含有category,那么所有的category都必须和过滤规则中(即intent filter中设置的category)的其中一个相同。Intent也可以没有category,如果没有category的话,在startActivity或者startActivityForResult的时候会默认为Intent加上"android.intent.category.DEFAULT"
3. data的匹配规则
  如果过滤规则中添加了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由两部分组成,mineType和URI。mineType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示图片、文本、视频等不同媒体格式,而URI包含的数比较多,一个URI结构如下所示

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

下面举个实际的例子大家就明白了:

content://com.example.project:200/folder/subfolder/etc

其中:
- Scheme:URI的模式,比如http、file、content等,如果URI中没有指定scheme,那么整个URI的其他参数无效,即URI也是无效的;
- Host:URI的主机名,如com.example.project,如果host未指定,那么整个URI中的其他参数无效,即URI也是无效的;
- Port:URI中的端口号,如200,只有URI中指定了scheme和host参数的时候port参数才有意义;
- Path、PathPattern和PathPrefix:这三个参数表示路径信息,如folder/subfolder/etc,其中path表示完整的路径信息;PathPattern也表示完整的路径信息,但是它里面可包含通符“*”,表示0个或多个任意字符;PathPrefix表示路径的前缀信息。
data匹配规则:

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

这种规则制定了媒体类型为所有类型的图片,那么Intent中mimeType属性必须为”image/*”才能匹配,这种情况下没有指定URI,但是Intent中的URI部分的schema必须为content或者file才能匹配,我们需要在Intent中这样指定:
intent.setDataAndType(Uri.parse("file://abc"), "image/png")
如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法彼此会清除对方的值。
怎么判断是否有Activity是否能够匹配我们的隐式Intent?
1. 采用PackageManagerresolveActivity方法或者Intent的resolveActivity方法,如果找不到就返回null。
2. PackageManager方法还提供了queryIntentActivities方法,这个方法和resolveActivity方法最大的不同是,它不是返回最佳匹配的Activity的信息,而是返回的是所有成功匹配的Activity信息。我们可以看一下方法的原型:

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

上述两个方法的第二个参数注意,我们要使用MATCH_DEFAULT_ONLY这个标记位,只能匹配intent-filter中声明了<category android:name="android.intent.category.DEFAULT"/>这个category的Activity。使用这个标记位最大的意义是上述两个方法不返回null,那么startActivity一定可以成功。如果不使用这个标记位,就可以吧intent-filter中category不含DEFAULT的那些Activity匹配出来,startActivity有可能失败,不含有DEFAULT这个category的Activity是无法接受隐式Intent的。
3. 在action和category中有一类比较重要,用来标注一个入口:

<action android:name="android.intent.action.MAIN" />

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

针对Service和BroadcastReceiver等组件,PackageManager同样提供了类似的方法去获取成功匹配的组件信息。
关于隐式Intent更多信息可以参考这两篇官方翻译的博文,也可以直接去看官方的文档:
Android Intent最全面的解析
史上最全的隐式Intent解析

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值