Android 开发艺术探索 1
Activity的生命周期
不要在onPause()中做重量级的操作,因为必须就得Activity的onPause()执行完之后,新Activity的onCreate(),onStart(),onResume()才能执行,执行好了,就Activity的onStop()才执行,onStop()中也不应做太耗时的操作
Activity被杀死之后会调用 onPause, onSaveInstanceState, onStop, onDestroy方法,onSaveInstanceState在onStop之前,有可能在onPause之前,也有可能在onPause之后(个人实验下来基本上都是全部是在onPause之后,况且onSaveInstanceState是和onRestoreInstanceState相对应的,onRestoreInstanceState调用在onStart之后onResume之前,那按理onSaveInstanceState也应该在onPause之后)。只要onSaveInstanceState方法调用了,不管你有没有对Bundle写入数据,它都会将Bundle发送出去,即使Bundle不携带信息
Activity被重新创建后,系统会调用onRestoreInstanceState方法,并把之前在onSaveInstanceState中保存的Bundle对象同时传给onCreate方法和onRestoreInstanceState方法,onRestoreInstanceState调用在onStart之后onResume之前。这两个的区别使用onCreate恢复数据时需要判断Bundle是否为空(因为正常启动时Bundle为空),而onRestoreInstanceState则不用判断,只要这个方法被调用,Bundle是一定有值的
在onSaveInstanceState和onRestoreInstanceState方法中,系统会自动帮我们做一些当前Activity(有id)视图结构的保存、恢复工作。工作流程大致是这样的:每个View也有onSaveInstanceState和onRestoreInstanceState方法,当Activity被意外结束时,Activity会调用onSaveInstanceState去保存数据,随后Activity会委托Window去保存数据,Window再委托它上面的顶层容器去保存数据,顶层容器是一个ViewGroup,一般很可能是DecorView,顶层容器再去一一通知它的子元素保存数据。这是一种典型的委托思想,View的绘制过程、事件分发都是采用类似的思想。
onSaveInstanceState在Activity正常销毁时不会调用,但是当按Home键或者启动新的Activity时,会单触发onSaveInstanceState的调用
资源相关的系统配置发生改变会导致Activity被杀死并重新创建。通过对android:configChanges进行设定,指定当这些系统配置发生变化时,Activity不被杀死。当指定属性发生变化时,系统不会调用onSaveInstanceState和onRestoreInstanceState来存储或恢复数据,取而代之调用onConfigurationChanged方法
<activity android:name=".MainActivity" android:configChanges="locale|orientation|screenSize|keyboardHidden">
实验后表明,即使指定了相关属性,屏幕还是可以旋转,Activity也会跟着旋转,但是Activity不会注销并重新启动
Activity的启动模式
standard 标准模式 系统默认为此模式,每次启动一个Activity会重新创建一个新的实例,而不去管这个实例是否已经存在,多次启动同一个Activity,系统会创建多个实例。standard模式下,谁启动了这个Activity,这个Activity就运行在启动它的那个Activity所在的栈中(经个人实试TaskAffinity对standard模式下Activity有效,但优先程度没有谁启动去谁那儿高。只有在程序的入口或者与带有Flag的Intent配合时才有效)。所以如果在程序中用ApplicationContext去启动standard模式的Activity会报错,因为ApplicationContext非Activity类型,没有任务栈
singleTop 栈顶复用模式 这种模式下,如果新Activity已经位于任务栈的栈顶,那么这个Activity不会被重新创建,栈顶Activity的onNewIntent方法会被调用
singleTask 栈内复用模式 这种模式下,只要Activity在目标栈中存在,则多次启动此Activity都不会重新创建实例,而会调用onNewIntent方法
将singleTask和TaskAffinity分开来讲的都是耍流氓啊!TaskAffinity用来指定Activity启动时想去的任务栈,如果没有指定,则默认的TaskAffinity为当前应用的包名,TaskAffinity属性主要和singleTask或者allowTaskReparenting配对使用,其他情况没有意义。TaskAffinity的值必须为字符串,且必须带有"."
具有singleTask模式的Activity A请求启动后,系统首先寻找A的目标栈是否存在,如果不存在,则创建目标栈,创建A的实例放入栈中;如果目标栈存在,同时里面不存在A的实例,则创建A的实例并放入栈中;如果目标栈存在,且里面已经有A的实例存在了,则系统直接将A的实例调到栈顶(A实例的上面全部出栈),并调用它的onNewIntent方法singleInstance 单实例模式,这种模式下,系统会为Activity单独创建一个独立的任务栈,这个任务栈中只能有这个Activity的实例存在
TaskAffinity与allowTaskReparenting配合使用,如果程序A启动了程序B的Activity C,并且C的allowTaskReparenting设为ture,则C会在A的任务栈里面,但是当B应用启动之后,B会创造原本C想要的那个任务栈(默认的包名呗),那么系统就会直接将C从A的任务栈转移到B的任务栈,所以B应用启动之后不是B的入口Activity,而是C
尽管singleTask和SingleTop模式的Activity有时系统不会为其创建新的实例,而是调用onNewIntent方法,但是Activity的onPause和onResume方法还是会调用的。调用循序依次为onPause,onNewIntent,onResume
除了通过launchMode来设置启动模式,还可以通过在intent中addFlags来设置,且通过Intent的优先级高于manifest
Intent intent = new Intent(DActivity.this,AActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);Activity几种常用的Flags
FLAG_ACTIVITY_NEW_TASK//指定为singleTask模式启动
FLAG_ACTIVITY_SINGLE_TOP//指定为singleTop模式启动
FLAG_ACTIVITY_CLEAR_TOP//?
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS//不出现在历史列表中,等同于android:excludeFromRecents="true"
IntentFilter的匹配规则
Intent分两种,显示Intent和隐式Intent,如果同时存在,显示Intent的优先级高于隐式Intent。为了匹配隐式Intent,需要同时匹配intent-filter中的action,category,data信息才算匹配成功。如果一个Activity有多个intent-filter,那么一个Intent只要能够匹配任意一组intent-filter即可启动Activity
action的匹配规则:一个intent-filter中可以有多个action,Intent中的action只要与其中任意一个action相同即可匹配成功
category的匹配规则:Intent中可以没有category,但是如果一旦有category,不管有几个,每个都要能够与intent-filter中的任意一个category相同。不过在调用startActivity或startActivityForResult方法时,系统会默认为Intent加上DEFAULT这条category,所以为了能够接受隐式调用,intent-filter中也要加上DEFAULT这一条
data的匹配规则:一个intent-filter中可以有多个data,Intent中的data数据必须其中任意一条匹配才能匹配成功
data的语法:data由mimeType和URI两部分组成,mimeType指媒体类型,URI结构为 <scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>],在mimiType指定后, android:scheme有可能有默认的值content或file
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"//表示完整路径
android:pathPattern="string"//表示完整路径,可以包含通配符"*",表示0或多个任意字符
android:pathPrefix="string"//表示路径前缀
android:mimeType="string"
/>
如果要为Intent指定完整的data,使用setDataAndType方法,因为setData方法和setType方法会互相清除对方的值
对于Service尽量使用显示调用
使用隐式调用时,可以实现使用PackageManager的resolveActivity方法或者Intent的resolveActivity来检查是否有Activity能够响应我们的隐式调用
abstract ResolveInfo resolveActivity(Intent intent, int flags)
flags使用MATCH_DEFAULT_ONLY,因为category不含default的是不能响应隐式调用的