Activity基础知识点
1. 生命周期
1.1 常见方法
1) onCreate(): Activity正在创建,进行相关的初始化工作,如加载布局等;
2) onStart(): Activity可见但没有焦点,即不可交互;
3) onResume(): Activity获取焦点,可交互;
4) onPause(): Activity可见但不在前台,和onResume()是配对的;
5) onStop(): Activity不可见,如退到后台,和onStart()是配对的;
6) onDestroy(): Activity被销毁,资源回收、释放,和onCreate() 是配对的
7) onRestart(): Activity重新启动,如退到后台之后,再次启动;
8) onNewIntent(): 跟启动模式、Task任务栈有关,后面会讲;
9) onSaveInstanceState(): 由于系统原因(如横竖屏切换)导致activity需要被销毁和重建时,在销毁前调用,保存被销毁Activity的状态;
10) onRestoreInstanceState(): 和onSaveInstanceState()对应,在Activity重建时调用,带之前保存的状态信息;
1.2 常规启动
1) 从桌面启动(之前进程已死,创建新进程): onCreate() → onStart() → onResume()
2) 按HOME键返回桌面,再从桌面启动: onPause() → onStop() → onNewIntent() → onRestart() → onStart() → onResume()
3) 按BACK键: onPause() → onStop() → onDestroy()
4) Activity1界面进行锁屏,然后解锁: onPause() → onStop() → onRestart() → onStart() → onResume()
1.3 启动另一个activity
在Activity1中跳转到Activity2: Activity1的onPause() → Activity2的onCreate() → onStart() → onResume() → Activity1的onStop()
1.4 有Dialog弹框
大部分人的误解: 在Activity中弹出Dialog,会触发onPause(),不会触发onStop(),因为这个时候Activity被覆盖不在前台,但部分可见;
其实不然: 在Activity中弹出一般的Dialog,既不会触发onPause(),也不会触发onStop();
Google官方对onPause的解释:
/**
* Called as part of the activity lifecycle when an activity is going into
* the background, but has not (yet) been killed. The counterpart to
* {@link #onResume}.
*
* <p>When activity B is launched in front of activity A, this callback will
* be invoked on A. B will not be created until A's {@link #onPause} returns,
* so be sure to not do anything lengthy here.
*/
即当Activity B在Activity A前面时,会触发Activity A中的onPause方法。 B直到A的onPause方法执行完后才会被创建,所以建议不要在onPause方法中做耗时操作。
而且,被覆盖指的是被其他Activity不完全覆盖,并不是其他其他东西(如一般的Dialog)。
那到底什么时候只触发onPause(),不会触发onStop()呢?
1) 启动另外一个Activity2,并将Activity2的主题设置为Dialog
<activity android:name=".Activity2"
android:theme="@android:style/Theme.Dialog">
效果如下图所示:
生命周期如下,Activity1只触发onPause(),不会触发onStop():
2) 启动另外一个Activity2,并将Activity2的主题设置为半透明
<activity android:name=".Activity2"
android:theme="@android:style/Theme.Translucent">
生命周期如下,Activity1只触发onPause(),不会触发onStop():
1.5 横竖屏切换
1) 先销毁,再重建(默认): onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume()
2) 那有没有办法不销毁不重建Activity呢?
答案是有的。比如视频播放界面就是这样做的,否则横竖屏旋转一下视频就会从播放状态回到初始的未播放状态。
对activity进行配置,这里要注意orientation和screenSize必须一起使用才有效果:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
这样Activity就不会重走生命周期,而系统会通过回调onConfigurationChanged()方法,可以复写此方法进行相关操作:
2. 任务栈(Task Stack)
2.1 概念
1) Task其实就是一组有相互关联的Activity的一个集合,是一个后进先出的栈,有个Task id作为唯一的标识;
2) Task Stack表示任务栈,任务栈的基本单位就是Task;Stack以链表的形式管理Task;
2.2 taskAffinity
1) 简单说就是指定任务栈的名称,相关的Activity会入此栈;
2) 该属性只有singleTask模式下有用(或配合allowTaskReparenting使用,后面会讲);
3) 默认为应用包名;如果第一个入栈的activity设置了此属性,则taskAffinity值为该activity的taskAffinity;
① 如下图所示,虽然设置了Activity2的taskAffinity,但Activity2为standard模式,所以taskAffinity为包名:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Activity2"
android:taskAffinity="www.www">
如果将MainAcitivity的taskAffinity设置为www.www,则这个TACK的taskAffinity的就是www.www;
② 如下所示,App2的Activity2设置launchMode为singleTask,且设置taskAffinity为www.app2.affinity2:
<activity android:name=".Activity2"
android:launchMode="singleTask"
android:taskAffinity="www.app2.affinity2">
操作:App2的MainAcitivity启动Activity2再启动Activity3,测试的栈情况为:
可以看出,Activity2根据自己的affinity开辟了自己的Task,后续启动的Activity3也入此栈。
2.3 Task相关属性
1) allowTaskReparenting
① 标记此属性的Activity实例所在的当前应用在退居后台后,决定是否将标记的Activity从启动的Task移动到相同affinity的Task中;
② 配合taskAffinity使用;
③ 默认为false;
④ 引用网上比较生动的比喻:你捡到一条狗,在家里喂养几天觉得不错,当自己家的了;但是突然有一天他的主人找上门来了,小狗还是乖乖和主人走了;
2) alwaysRetainTaskState
① 只对根Activity生效;
② App如果长期在后台,系统会对应用的Task进行清理,而清理过程中,如果对根Activity标记了这个属性,那么他会保留根Activity的状态;
3) clearTaskOnLaunch
① 只对根Activity生效;
② 唤醒应用时,是否清除根Activity之上的Activity;
<activity android:name=".MainActivity"
android:clearTaskOnLaunch="true">
验证:App2的MainActivity设置clearTaskOnLaunch=“true”,启动Activity2,接着启动Activity3,然后按HOME键退到后台,然后再重新启动,会发现Activity2和Activity3都被销毁,进入的是MainActivity:
4) finishOnTaskLaunch
这个属性跟clearTaskLaunch很像,只不过他只是销毁标记的Activity,而clearTaskOnLaunch是除了根Activity全部销毁;
4) noHistory
和Intent.FLAG_ACTIVITY_NO_HISTORY一样:带此Flag启动Activity,在切换到其他任务(或back返回时)时会被销毁,即没有在Task中留下历史记录;
3. 启动模式
3.1 Activity的显示启动与隐式启动
1) 显示启动
直接指定Activity的类名,一般用于同一个应用内,因为可以确切地知道要启动的组件名称。如:
Intent intent = new Intent(MainActivity.this, Activity2.class);
startActivity(intent);
也可以使用setClass()/setClassName/setComponent(),其实最终都是构建ComponentName的。
public Intent(Context packageContext, Class<?> cls) {
mComponent = new ComponentName(packageContext, cls);
}
public @NonNull Intent setClassName(@NonNull String packageName, @NonNull String className) {
mComponent = new ComponentName(packageName, className);
return this;
}
//注意以下这个方法的Context指的是要启动应用的context,因此该方法一般用于本应用内启动,因为其他应用的Context获取较复杂,
//还不一定能获取;而且最终构建ComponentName时,这个context是用来获取包名的。
public @NonNull Intent setClassName(@NonNull Context packageContext, @NonNull String className) {
mComponent = new ComponentName(packageContext, className);
return this;
}
2) 隐式启动
无需指定要启动组件的具体名称,使用intent-filter进行匹配,一般在启动其他应用的组件时使用,因为它能较好地实现应用间解耦;如:
<activity android:name=".Activity2">
<intent-filter>
<action android:name="com.www.app2.android.intent.test"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Intent intent = new Intent("com.www.app2.android.intent.test");
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
注意:隐式启动必须得设置category为DEFAULT,否则组件会找不到,导致应用崩溃(所以建议本应用最好做好防呆措施,try catch或者用if (intent.resolveActivity(getPackageManager()) != null) 事先判断指定Intent的Activity是否存在,尤其是启动其他应用的组件时,因为其他应用是不可控的,有一定的崩溃风险),包括显示启动时也需要做好防呆措施;
3.2 静态配置四种启动模式
<activity android:name=".MainActivity"
android:launchMode="standard | singleTop | singleTask | singleInstance" > <!--四个里面其中一个-->
1) standard(默认): 无论启动多少次,每一次都创建新的Activity
2) singleTop: 栈顶复用
3) singleTask: 栈内复用
4) singleInstance:独占Task
注:以上四张图片是从网上借鉴来的,暂时没找到原创作者;并不是本人画的;且这四张图已经画得非常形象生动了,非常感谢原创。
3.3 常用的Intent Flag
1) Intent.FLAG_ACTIVITY_NEW_TASK
简单来说,就是根据taskAffinity来决定需不需要创建新的Task来存放目标Activity;
验证1:从App1(包名为com.www.myapplication2)的MainActivity中启动App2的Activity2
① 没有Flag
可以看出,App2的Activity2并没有单独在一个Task中,而是和App1的主Activity在一个Task中,近期任务中也没有App2;
② App2的Activity2设置singleTask
可以看出,App2的Activity2单独成栈,近期任务中也有App2。
③ 去掉singleTask,Intent加FLAG_ACTIVITY_NEW_TASK
可以看出,效果和singleTask一样,App2的Activity2也是单独成栈。
那FLAG_ACTIVITY_NEW_TASK和singleTask有什么区别呢?
验证2:App2的MainActivity启动App2的Activity2,接着启动Activity3,此时App2的Task栈为:
① 在App1中启动App2的Activity2,Intent带FLAG_ACTIVITY_NEW_TASK,发现测试的Task栈为:
可以看出,Activity2重新创建了实例,没有复用之前的实例。这也能很好的诠释FLAG_ACTIVITY_NEW_TASK的含义:如果已经有taskAffinity的Task存在,则往里面压入目标Activity。
② Activity2设置launchMode为singleTask,在App1中启动App2的Activity2,此时App2的栈情况为:
可以看出,Activity3被出栈,Activity2至于栈顶,即singleTask的栈内复用效果;此时有没有FLAG_ACTIVITY_NEW_TASK效果都一样。
2) Intent.FLAG_ACTIVITY_SINGLE_TOP
该Flag与静态设置中的singleTop效果相同。
3) Intent.FLAG_ACTIVITY_CLEAR_TOP
① 测试:MainActivity 启动Activity2 启动Activity3,然后在 Activity3中带CLEAR_TOP启动Activity2,发现Activity2被销毁并重建:
② 测试:MainActivity 启动Activity2 启动Activity3,然后在 Activity3中带CLEAR_TOP加SINGLE_TOP启动Activity2,发现Activity2被复用,即CLEAR_TOP加SINGLE_TOP就相当于静态配置的singleTask:
4) Intent.FLAG_ACTIVITY_CLEAR_TASK
① 此Flag启动的目标Activity会清空与之相关的Task,并重新创建Task,将目标Activity重建后压入栈,作为新栈的根Activity;
② 只能与FLAG_ACTIVITY_NEW_TASK一起使用;
还未用Flag启动App2的Activity2前:
带FLAG_ACTIVITY_CLEAR_TASK和FLAG_ACTIVITY_NEW_TASK启动后:
5) Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
带该Flag的Intent会把目标Activity调到栈顶,不会销毁之上的Actitivty。
验证:App2的Activity1启动Activity2启动Activity3,再启动Activity2(带_REORDER_TO_FRONT)
6) Intent.FLAG_ACTIVITY_NO_HISTORY
带此Flag启动Activity,在切换到其他任务(或back返回时)时会被销毁,即没有在Task中留下历史记录;
验证:Activity1启动Activity2(带FLAG_ACTIVITY_NO_HISTORY),再启动Activity3,按返回键,发现此时返回的是 Activity1,而Activity2以被出栈销毁:
7) Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
在后台管理中,不显示该Activity的近期任务。
4. 其他拓展点
4.1 Intent
1) 概念
① 是传递消息的对象;
② 是连接Activity、Service、Broadcast之间的桥梁;
③ 是进程内/进程间组件通信的媒介;
2) 相关属性
① component(组件):目标组件(3.1小结中已经讲了显示启动与隐式启动)
② action(动作):用来表现意图的行动(隐式启动,通过intent-filter来匹配)
③ category(类别):用来表现动作的类别(隐式启动,通过intent-filter来匹配)
④ data(数据):表示与动作要操纵的数据
⑤ type(数据类型):对于data范例的描写
⑥ extras(扩展信息):扩展信息
⑦ Flags(标志位):期望这个意图的运行模式(参考3.3小结中的“常用的Intent Flag”)
推介文章:https://www.cnblogs.com/qianguyihao/p/3959204.html
4.2 intent-filter匹配规则
原则:只有action、category、data三个属性都匹配,Intent才算匹配成功。
1) action
① action是字符串,区分大小写;
② intent-filter中可以声明多个action,Intent中的action与其中的任一个action在字符串形式上完全相同即可匹配成功;
③ 隐式Intent必须指定action(如不指定action则必须指定data或mimetype。这种情况下,只要Intent-Filter 至少含有一个action就可以匹配),如果没有指定,则匹配失败;
2) category
① category也是字符串,也区分大小写;
② intent中带的category必须都要在intent-filter中声明;而intent-filter中声明的category,intent中可以不带;
③ 系统在启动activity时,会默认加上category “android.intent.category.DEFAULT”,所以我们在隐式启动时,必须在intent-filter中声明category android:name=“android.intent.category.DEFAULT” ,否则会异常;
3) data
① data由mimeType和URI两部分组成;
② mimeType指定媒体类型,如image/jepeg、video/等;
③ URI格式为:<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
scheme:URI的模式,比如http、file、content等;
host:URI的主机名,比如 www.baidu.com;
port:URI中的端口号;
path:完整的路径信息;
pathPattern:路劲信息,正则表达式,比如有通配符 “”;
pathPrefix:路径的前缀信息;
④ Intent的URI可通过setData方法设置,mimetype可通过setType方法设置,但是这两个方法是互斥的,即互相擦除:设置了其中一个,另一个就会被置为null;如果想两个一起设置,则可以使用setDataAndType;
⑤ 类似action,Intent的data只要与Intent-Filter中的任一个data声明完全相同,data就能匹配成功;
如:
<activity android:name=".Activity2">
<intent-filter>
<action android:name="com.www.app2.android.intent.test" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MY" />
<data android:mimeType="video/*" android:scheme="http" android:host="www.baidu.com"/>
<data android:mimeType="image/jepeg" android:scheme="http" android:host="adc"/>
</intent-filter>
</activity>
Intent intent = new Intent();
intent.setAction("com.www.app2.android.intent.test");
intent.setDataAndType(Uri.parse("http://www.baidu.com"),"video/*");
startActivity(intent);
4.3 组件安全
1) 组件暴露
当隐式启动组件时,组件原本默认为false的android:exported就会自动变为true,即该组件对外暴露,表示该组件允许外部的应用调用该组件,那么随之而来的就会有相关的安全隐患,比如:
App2隐式启动配置:
<activity android:name=".Activity2">
<intent-filter>
<action android:name="com.www.app2.android.intent.test" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
代码里面有获取数据:
CharSequence label = getIntent().getStringExtra("label");
Log.i(TAG, "-----onCreate-----label: " + label.toString());
App1隐式启动App2的Activity2,不带label数据:
Intent intent = new Intent("com.www.app2.android.intent.test");
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
结果就是,因为label为空,而导致app2因空指针而崩溃:
因此,其他应用就可以通过这个漏洞疯狂的攻击App2,使其崩溃。当然这里有两个问题:
① 代码严谨性问题:一般来说,在使用外部数据时,一定要做好校验等措施,比如以上例子的label,应该先对其进行判空处理,就算是App2内部自己使用,也需要进行判空;
② 组件暴露问题:如果组件只是App2自己内部使用,则最好不要对外暴露(android:exported=“false”):
<activity android:name=".Activity2"
android:exported="false">
<intent-filter>
<action android:name="com.www.app2.android.intent.test" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
这样,其他应用就会无法启动App2的该组件:
2) 权限限制
① 如果组件确实需要对外暴露,那么一方面需要做好完备的校验措施,保证代码的健壮性;一方面则需要加入权限限制;
② 权限设置,如针对以上例子:
自定义权限:
<permission
android:name="com.www.app2.permission.Activity2"
android:protectionLevel="signature"
android:label="permission for activity2" />
限制权限:
<activity android:name=".Activity2"
android:permission="com.www.app2.permission.Activity2">
<intent-filter>
<action android:name="com.www.app2.android.intent.test" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
如此一来,只有签名一样且有com.www.app2.permission.Activity2权限的才能启动Activity2。除了signature,权限等级protectionLevel还有很多其他等级,如system、signatureOrSystem、dangerous等;