《Android开发艺术探索》笔记总结——第一章:Activity的生命周期和启动模式

Activity的工作原理

启动Activity的请求会由Instrumentation来处理,然后它通过Binder向AMS发请求,AMS内部维护着一个ActivityStack并负责栈内的Activity的状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用。

Activity的生命周期

1)正常的生命周期

OnStart 和onResume、onPause和onStop从描述上来看差不多,他们有什么实质的不同呢?

onStart和onStop是从Activity是否可见的角度来回调的,而onResume和onPause是从Activity是否位于前台这个角度来回调的,除了这种区别,在实际使用中没有其他明显的区别

当在ActivityA中打开一个新的ActivityB,B的onResume和A的onPause哪个先执行?
从源码上来分析,是先执行A的onPause再去执行B的onResume

2)异常情况的生命周期

异常情况包括两种:

1:用户操作导致的生命周期的变化,比如横竖屏的变化导致Activity生命周期的变化。
2:当系统内存不足的时候,Activity被杀死的时候生命周期的变化

第一种情况:

第一种情况下Activity被终止的时候系统会调用onSaveInstanceState来保存一些Activity的状态。主要保存Activity页面上一些输入的内容,和View的状态等,比如横竖屏切换的时候,Activity虽然销毁重建了,但是重建以后,我们在页面上输入的内容还在,或者其他的状态都和销毁的时候一致,这些状态都是这个方法保存下来的。

onSaveInstanceState 方法的调用时机是在onStop之前,它和onPause没有既定的时序关系,可以在onPause之前调用,也可能在之后调用,这个方法在正常的情况下系统不会回调。

在Activity被重新创建后,系统会调用onRestoreInstanceState,并把Activity销毁时onSaveInstanceState保存的Bundle对象传递过来,同时传递给onCreate方法,这时可以取出之前自己需要保存的数据进行恢复。需要注意的是,onRestorInstanceState调用了以后,Activity肯定是由于异常情况销毁重建的,但是onCreate不一定,所以传递过来的参数可能为null

系统只有在Activity异常终止的时候才会调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,其他情况不会触发这个过程。

更改系统配置,比如切换横竖屏的时候不让系统销毁重建Activity
在清单文件中给Activity添加configChanges属性:android:configChanges="orientation" //表示横竖屏切换不销毁,多个属性值可以使用 " | " 连接起来
在这里插入图片描述
在这里插入图片描述

如果我们没有执行configChanges属性的话,当配置发生改变后就会导致Activity重新创建。其中比较常用的是locale、orientation和keyboardHidden这三个选项

当Activity在前台的时候,修改系统配置,比如切换横竖屏,Activity会销毁重建,那么当Activity处于onPause方法的时候或者Activity切换到后台处于onStop状态的时候,Activity会不会销毁重建呢?如果会,声明周期又是怎么调用的呢?

我先说结果:当Activity处于onPause状态的时候,切换横竖屏Activity也会销毁和重建,如下面的日志,Activity先回调用onSaveInstanceState,然后销毁,然后在重启,最后再次进入onPause状态。

当Activity出入onStop状态的时候,也就是Activity在后台的时候,切换横竖屏是不会销毁和重建的。

/com.emm.datastructure E/zh: onPause
-----
开始切换:
/com.emm.datastructure E/zh: onSaveInstanceState
/com.emm.datastructure E/zh: onStop
/com.emm.datastructure E/zh: onDestroy
/com.emm.datastructure E/zh: onRestoreInstanceState
/com.emm.datastructure E/zh: onResume
/com.emm.datastructure E/zh: onPause
第二种情况:

这种情况也会调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,系统会按照优先级来杀死Activity所在的进程,一个进程中如果有四大组件运行会提高这个进程的优先级,所以我们一般把后台任务放到后台Service中运行,这样不会轻易的被系统杀死。

Activity异常销毁重建的系统保存逻辑

当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前Activity的视图结构,在重建的时候会恢复,系统工作流程是这样的:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window再委托它上面的顶级容器去保存数据。顶层容器是一个ViewGroup,一般来说他很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样真个数据保存过程就完成了。这是一种典型的违规思想,上层委托下层,父容器委托子元素去处理事情,在Android中还有很多应用,比如View的绘制机制、事件分发机制等。

启动模式

四种启动模式:
standard:默认模式,每次启动一个Activity都会创建一个新的实例,不管是不是该实例已经存在,这种模式下,谁启动了这个Activity,这个Activity就会运行在启动它的那个Activity所在的栈中

singleTop:栈顶复用模式,当任务栈顶为启动的Activity的时候,就不会在创建新的,会直接复用,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以去除当前请求的信息

singleTask:栈内复用模式,就是当任务栈中存在被启动的Activity的实例的时候,不会在创建新的实例,栈内的这个Activity会移动到栈顶,之前该Activity上面的所有实例出栈,系统也会回调onNewIntent方法

singleInstance:单实例模式,加强版的singleTask模式,具有singleTask的所有特性,同时用这个模式启动的Activity会单独在一个新的任务栈中,由于栈内复用的特性,这个栈中也只有这个一个实例。

注意:当我们在非Activity页面启动Activity的时候要加一个标记FLAG_ACTIVITY_NEW_TASK,是因为在使用非Activity类型的Context并没有所谓的任务栈,这个标记位就是为这个Activity创建一个新的任务栈,启动模式是singleTask

指定启动Activity所在的任务栈

默认情况下,所有Activity所需的任务栈的名字为应用的包名,但是我们也可以指定启动Activity的时候进入的任务栈的名字,使用taskAffinity参数。主要和singleTask启动模式和allowTaskReparenting配对使用,其他情况下没有意义

<activity
        android:name="com.ryg.chapter_1.SecondActivity"
        android:label="@string/app_name"
        android:taskAffinity="com.ryg.task1" //指定任务栈的名称
        android:launchMode="singleTask" />

TaskAffinity 和singleTask 启动模式配对使用的时候,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。

TaskAffinity和allowTaskReparenting结合使用的时候,情况比较复杂。当应用A启动应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。比如现在有两个应用A和B,A启动了B的一个Activity C,然后Home键回到桌面,然后再单击B的桌面图标,这个时候并不是启动B的主Activity,而是重新显示了已经被应用A启动的Activity C

注意:经过测试上面的情况,当在B应用中的Activity C设置了allowTaskReparenting 为true 的时候,会出现上面描述的情况,但是当Activity C的启动模式为 singleTask的时候,点击B的桌面图标启动Activity C以后,再按返回键返回的是桌面,当Activity C的启动模式为默认standard的时候,启动Activity C以后,再按返回键返回的是B应用的主Activity,具体原因未知。

为什么TaskAffinity知识和singleTask启动模式或者allowTaskReparenting结合的时候使用才有意义,其他的没有意义,感觉 TaskAffinity 作用就是指定任务栈,指定了以后,当该Activity被启动的时候,只要没有对应任务栈就新建任务栈,如果存在就直接使用即可,为什么和其他的没有意义呢?

经过测试,只有当启动模式设置为singleTask或者singleInstance的时候 taskAffinity指定的任务栈才有效。

Activity的Flags
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_SINGLE_TOP
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

主要记录下第四个flag——FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS被这个标记以后,该Activity不会出现在历史Activity列表中
经过测试,这个标记只有设置给主Activity(即启动Activity)的时候,才会生效,历史列表中不会出现此应用。当此标记设置到非主Activity的时候,不会生效。

InterFilter 的匹配规则

使用隐式意图来启动Activity

<activity
        android:name="com.ryg.chapter_1.ThirdActivity"
        android:configChanges="screenLayout"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:taskAffinity="com.ryg.task1">
        <intent-filter>
                <action android:name="com.ryg.charpter_1.c"/>
                <action android:name="com.ryg.charpter_1.d"/>
                <category android:name="com.ryg.category.c"/>
                <category android:name="com.ryg.category.d"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="text/plain"/>
         </intent-filter>
</activity>

intentFilter中主要有这三个标签,action、category、data,intent启动对应的Activity的时候,只有同时匹配action、category、data才算完全匹配,只有完全匹配才能启动目标Activity,一个Activity可以有多个intentFilter,只要完全匹配一个就可以成功启动对应的Activity。

action 的匹配规则:

一个intent - filter 标签中可以有多个acting,只要intent中设置的action能够和过滤规则中的任何一个action相同即可匹配成功

category 的匹配规则:

intent中设置的 category 必须是intent - filter中定义的 category ,intent中可以有多个category也可以没有,如果没有系统在调用startActivity或者startActivityForResult的时候会默认给intent加上“android.intent. category.DEFAULT”这个category,所以如果一个intent-filter中如果没有指定“android.intent. category.DEFAULT”这个category,就不能通过隐式调用开启这个Activity

data的匹配规则:

如果intent-filter中定义了data,那么intent中也必须要定义可匹配的data,data有两部分组成,mimeType和URI。mimeTyep指媒体类型,比如image/jpeg、audio/mpeg4-generic和 video/* 等,可以表示图片、文本、视频等不同的媒体格式。URI的结构是:

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
比如:
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info    

URI不一定按照上面的格式都配置全,可以单独配置scheme,如果没有scheme那么整个URI其他参数无效,可以scheme和host一起配置

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

<intent-filter>
         <data android:mimeType="video/mpeg" android:scheme="http" ... />
         <data android:mimeType="audio/mpeg" android:scheme="http" ... />
         ...
    </intent-filter>

如上的data过滤规则,只有mimeType,但是URI是有默认值的,默认值为content和file,所以intent中的URI部分的schema必须为content或者file才能匹配intent.setDataAndType(Uri.parse("file://abc"),"image/png")

指定完整的data必须要用setDataAndType,不能先调用setData再调用setType,因为这两个方法彼此会清除对方的值,下面是两种data的写法,所用是一样的

<intent-filter . . . >
         <data android:scheme="file" android:host="www.baidu.com" />
         . . .
    </intent-filter>
    <intent-filter . . . >
         <data android:scheme="file" />
         <data android:host="www.baidu.com" />
         . . .
</intent-filter>

开头例子的使用下面的intent来匹配

Intent intent = new Intent("com.ryg.charpter_1.c");
intent.addCategory("com.ryg.category.c");
intent.setDataAndType(Uri.parse("file://abc"),"text/plain");
startActivity(intent);

判断是否有Activity能够匹配隐式Intent

1:PackageManager的resolveActivity
public abstract ResolveInfo resolveActivity(Intent intent, int flags)
2:Intent的resolveActivity
public ComponentName resolveActivity(PackageManager pm)

两个方法只要不返回 null 就能够启动该Activity,第一个方法中的flags传入MATCH_DEFAULT_ONLY,表示只匹配category设置了default的Activity,因为只有设置了default的Activity才能隐式启动。

也可以使用PackageManager的quaryIntentActivities方法,同样传入一个intent和一个flag,只不过个方法返回一个list集合,返回手机中所有匹配到的Activity
public abstract List<ResolveInfo> queryIntentActivities(Intent intent,intflags)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值