Activity是什么
四大组件之一,Activity 提供窗口来和用户进行交互,开发者可以通过setContentView把UI放到窗口上显示
Task
Android中的activity全都归属于task管理,task 是多个 activity 的集合,这些 activity 按照启动顺序排
队存入一个栈(即“back stack”)。android默认会为每个App维持一个task来存放该app的所有activity,task的默认name为该app的packagename。但是一个Task是可以由一个应用或者多个应用的Activity组成的。比如我们可以通过一个Activity跳转到另一个第三方的Activity,所以说这两个应用的Activity在一个Task中。
我们也可以在AndroidMainfest.xml中申明activity的taskAffinity属性来自定义task,但不建议使
用,如果其他app也申明相同的task,它就有可能启动到你的activity,带来各种安全问题。
Task是跨应用的,不属于任何一个应用
什么是Affinity
就是task的名称,表示activity到底要放在哪个task里面
如果不标注,表示就是包名,如果带了affinity,activity就会启动到这个名称下的栈中里面,下面红框中就是affinity
如何查看Task
- 通过手机的Menu按键,在recent列表中显示的就是正在运行的Task
- 通过命令
adb shell dumpsys activity activities | sed -En -e '/Stack #/p' -e '/Running activities/,/Run #0/p'
哪几种方式启动Activity
我们可以通过Launcher、通知、第三方应用scheme
Activity的生命周期
图片来自https://developer.android.google.cn/guide/components/activities/activity-lifecycle
打开一个Activty:onCreate、onStart、onResume
这时是运行状态
退出一个Activity: onPause、onStop、onDestory
其中可见的状态是在onStart和onStop之间,可交互的状态是onResume和onPause之间
需要注意的是:
-
onCreate与onStart的区别:
- 可见与不可见。onCreate是不可见状态,onStart是可见状态
- 执行次数。onStart可能会执行多次,onCreate只在创建的收执行一次
- onCreate中的代码完全可以放在onStart中(包括setContentView)但是,onStart中的代码可能不适合放入onCreate中,比如动画的初始化放在onStart方法中比较合适
-
onStart与onResume的区别
- 是否可以交互。onResume可以交互、onStart不可交互
- onStart一般用于动画的初始化,onResume一般用于打开独占设备(进程应互斥地访问这类设备,即系统一旦把这类设备分配给了某进程后,便由该进程独占,直到用完释放)或者访问单例资源
-
onPause与onStop的区别
- 是否可见。onPause可见,onStop不可见
- 在系统内存不足的时候,该Activity可能会被回收,回收的时候可能不会执行onStop方法,所以程序数据的保存、独占设备和动画的关闭,最好放在onPause中
-
onStop和onDestory的区别
- onStop Activity还没被销毁,可以切换回该Activity;onDestory阶段Activity被销毁
Activity的启动模式
为什么要有启动模式
为了实现一些默认启动(standard)模式之外的需求:
- 让某个 activity 启动一个新的 task (而不是被放入当前 task )
- 让 activity 启动时只是调出已有的某个实例(而不是在 back stack 顶创建一个新的实例)
- 你想在用户离开 task 时只保留根 activity,而 back stack 中的其它 activity 都要清空
启动模式分类
standard (默认模式)
当通过这种模式来启动Activity时,
Android为目标 Activity创建一个新的实例,并将该Activity
添加到当前Task栈中。这种方式不会启动新的Task,只是将新的 Activity添加到原有的Task中。
这里需要注意的是:
- 当从非Activity的context启动Activity时,需要添加NEW_TASK的flag(比如在广播中打开一个Activity会报错:android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag)
- standard模式的Activity并不是每次都会被创建。当从Launcher启动的Activity启动后,点击home键返回桌面,再次点击应用图标,该Activity将不会重复创建
而是走onRestart-onStart-onResume - 如果在应用内启动的Activity 启动模式是standard且affinity是默认的affinity,不管task中有没有该Activity的实例,则会创建一个新的Activity
- 如果在应用内启动的Activity 启动模式是standard且affinity不是默认的,task中如果有该Activity的实例,则不会创建新的Activity
singleTop
该模式和standard模式基本一致,但有一点不同:当将要被启动的Activity已经位于Task栈顶时,系统
不会重新创建目标Activity实例,而是直接复用Task栈顶的Activity。即:栈顶复用模式
应用场景:
- 点击通知栏的一个通知后,通知的展示页面。
这个情况主要看产品是否需要不重复打开Activity了,如果正在展示通知详情的Activity,再点击通知不重新创建新的Activity,则该Activity适用于SingleTop的启动模式(栈顶复用模式) - 耗时操作的返回页面。
如果从一个Activity中启动了一个Service进行耗时操作,然后点击home返回了桌面,经过一段时间耗时操作结束,返回该Activity。
singleTask
Activity在同一个Task内只有一个实例。
如果将要启动的Activity不存在,那么系统将会创建该实例,并将其加入Task栈顶;
如果将要启动的Activity已存在,且存在栈顶,直接复用Task栈顶的Activity。
如果Activity存在但是没有位于栈顶,那么此时系统会把位于该Activity上面的所有其他Activity全部移
出Task,从而使得该目标Activity位于栈顶。即:栈内复用模式
应用场景:
MainActivity: 当我们从MainActivity跳转到多级子页面后,想要跳回MainActivity并将之前打开的Activity出栈。
singleInstance
无论从哪个Task中启动目标Activity,只会创建一个目标Activity实例且会用一个全新的Task栈来装载
该Activity实例(全局单例).
如果将要启动的Activity不存在,那么系统将会先创建一个全新的Task,再创建目标Activity实例并将该
Activity实例放入此全新的Task中。
如果将要启动的Activity已存在,那么无论它位于哪个应用程序,哪个Task中;系统都会把该Activity所
在的Task转到前台,从而使该Activity显示出来。
应用场景:锁屏页面、来电页面等
定义启动模式的方法
在Manifest中定义
<activity android:launchMode="standard"/>
使用Intent.setFlags
Intent i = new Intent(this,NewActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
两种方式的区别:
在Manifest中定义是面向创建者的, 使用Intent.setFlags是面向调用者,调用者想让所启动的Activity按照自己的意愿启动
需要注意的是:
- 如果一个Activity的启动模式是SingleTop,启动这个Activity的时候
- 首先要判断是否存在和该affinity同名的task
- 如果存在,判断栈顶的Activity是要启动的Activity吗。如果是该Activity,不执行onCreate创建,执行onNewIntent;如果不是该Activity,在栈顶创建该Activity。
- 如果不存在,判断当前栈顶的Activity是要启动的Activity吗。如果是该Activity,不执行onCreate创建,执行onNewIntent;如果不是该Activity,则需要判断intent中是否携带了FLAG_ACTIVITY_NEW_TASK,如果携带了,就创建affinity定义的Task,并创建该Activity;如果没携带,在当前栈顶创建该Activity
- 使用intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)启动Activity。 如果该任务栈中已经有该 Activity,相当于给 Activity 配置的启动模式为 SingleTask,会使该Activity之上的其它Activity出栈
Activity之间跳转执行的生命周期
standard
A跳转B
A:onPause
B:onCreate-onStart-onResume
A:onStop
B返回A
B:onPause
A:onRestart-onStart-onResume
B:onStop-onDestory
singleTop
A是singleTop,A跳转A
onPause-onNewIntent-onResume
在栈顶时不会创建A的实例
singleTask
A是singleTask,A跳转A和 singleTop是一样的生命周期
onPause-onNewIntent-onResume
A跳转B再跳转A
A:onPause
B:onCteate-onStart-onResume
A:onStop
B:onPause
A:onRestart-onStart-onNewIntent-onResume
B:onStop-onDestory
可以看到当A在栈中存在时,再次跳转不会创建A的实例并使A上面的B出栈
singleInstance
无论从哪个Task中启动目标Activity,只会创建一个目标Activity实例且会用一个全新的Task栈来装载
该Activity实例(全局单例).
B是singleInstance A和C是standard
A跳转B再跳转C,生命周期和standard一样,需要注意的是:
- 如果在C中点返回,会直接返回到A中,因为B是单独一个task,A和C是同一个task
C:onPause
A:onRestart-onStart-onResume
C:onStop-onDestory - 再次执行A-B因为此时B已经存在,所以B不会重新创建
A:onPause
B:onRestart-onStart-onNewIntent-onResume
A:onStop
Activity回收和数据恢复
为了避免系统回收Activity造成数据丢失,Android提供了onSaveInstanceState(Bundle outState)和onRestoreInstanceState(Bundle savedInstanceState)用于保存和恢复数据
onSaveInstanceState 调用时机
onSaveInstanceState在Activity可能被系统回收的情况下调用
-
按home键返回桌面、查看最近用过的程序
-
按电源键锁屏
-
从当前Activity跳转另一个Activity
-
横竖屏切换
targetSdkVersion:28 (Android9.0)
targetSdkVersion:27
需要注意的是:
- onPause和onSaveInstanceState以及onStop的顺序是不一定的,
targetSdkVersion低于28的app,则会在onStop之前回调;28之后,onSaveInstanceState在onStop回调之后才回调。 - onSaveInstanceState方法并不是一定会被调用的,例如按返回键退出Activity时, 想要关闭这个Activity, 此时是没有必要保存数据,onSaveInstanceState方法不会被调用。
onRestoreInstanceState 调用时机
onRestoreInstanceState只有在Activity被系统回收的了,重新创建Activity的时候才调用
比如上一节横竖屏切换的生命周期显示在onStart后调用了onRestoreInstanceState,因为横竖屏切换时伴随着Activity的回收和创建
onCreate和onRestoreInstanceState都可以用来恢复数据,区别是什么
- onRestoreInstanceState被调用的时候一定是Activity被销毁了。此时只要在onSaveInstanceState中在B
undle中存入了数据,一定可以取出数据 - 当从其它页面打开这个Activity的时候也会走onCreate,这时候取onSaveInstanceState时存入Bundle中的数据肯定是为空的
- 使用onRestoreInstanceState恢复数据,可以决定是否调用父类的onRestoreInstanceState方法即:super.onRestoreInstanceState(savedInstanceState);使用onCreate必须调用super.onCreate(savedInstanceState);
intent-filter
Activity、services、BroadcastReceiver都是通过intent传递消息的。
在 Android 的 AndroidManifest.xml 配置文件中可以通过 intent-filter 节点为一个 Activity 指定其
Intent Filter的过滤信息,以便告诉系统该 Activity 可以响应什么类型的 Intent。
intent-filter的过滤信息有:action、category、data。
需要注意的是
- 只有Intent同时匹配了action、category、data,才能跳转目标Activity
- 一个Activity中可以有多组intent-filter,一个Intent只要匹配任何一组intent-filter,就可以跳转目标Activity
intent-filter 的匹配规则
action
一个 Intent Filter 可以包含多个 Action,Action 列表用于标示 Activity 所能接受的“动作”,它是一个用
户自定义的字符串。
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<action android:name="com.himi.myaction"/>
……
</intent-filter>
在代码中就可以使用,
action 列表中包含了“com.himi.myaction”的 Activity 都将会匹配成功
Intent intent=new Intent();
intent.setAction("com.himi.myaction");
- 需要注意的是:
如果设置了exported = "false"的化,是不能设置intent-filter的,可以防止因为相同的action被其它activity唤醒
<activity
android:exported="false" />
action的匹配要求是:intent中必须有一个action,而且这个action必须和intent-filter中的一个action相同
category
category也是一个字符串。
它的匹配规则是:intent中如果有category,不管有几个category,那么必须是intent-filter中定义的category。
intent中可以不设置category,系统会默认在startActivity或者startActivityForResult的时候添加category:“android.intent.category.DEFAULT”
所以只有在intent-filter中配置了
才会接受到隐式调用
data
data匹配要求是:如果intent-filter中定义了data,intent中必须有一个data,而且这个data必须和intent-filter中的一个data相同
data有两部分构成:URI和mimeType
URI
URI的结构是:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
比如:
content://com.himi.project:8888/folder
- scheme:URI的模式,比如http、file、content等,如果没有指定scheme,那么剩下的其它参数是无效的,URI也是无效的。
- host:URI的主机名。比如www.baidu.com,如果没有指定host,剩下的其它参数也是无效的,URI也是无效的
- port: URI的端口号,只有指定了scheme和host后,端口号才有效
- path: 完成的路径信息
- pathPrefix:表示路径的前缀信息
- pathPattern:也表示完整的路径信息,但是它里面可以包含通配符” * “
下面进行使用举例
<activity android:name=".MyActivity">
<intent-filter>
<action android:name="com.himi.action.module1"/>
<action android:name="com.himi.action.myActivity"/>
<category android:name="com.himi.category.c1"/>
<category android:name="com.himi.category.c2"/>
<!-- 下面这个category一定要加 否则会报 android.content.ActivityNotFoundException: No
Activity found to handle Intent-->
<category android:name="android.intent.category.DEFAULT" />
<data android:host="himi" android:scheme="test"/>
<data android:host="himi" android:scheme="test1"/>
</intent-filter>
<intent-filter>
<action android:name="com.himi.action.module2"/>
<action android:name="com.himi.action.myActivity2"/>
<category android:name="com.himi.category.c12"/>
<category android:name="com.himi.category.c22"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:host="gogogo" android:scheme="test"/>
</intent-filter>
</activity>
Intent intent = new Intent("com.himi.action.module1");
intent.addCategory("com.himi.category.c1");
intent.setData(Uri.parse("test1://himi"));
startActivity(intent);
或者使用
Intent intent = new Intent("com.himi.action.module2");
intent.addCategory("com.himi.category.c22");
intent.setData(Uri.parse("test://gogogo"));
startActivity(intent);
mimeType
mimeType是指媒体类型,比如image/jpeg、video/*等
Activity的进程优先级
前台进程>可见进程>service进程>后台进程>空进程
- 前台进程:
- 当前进程activity正在与用户进行交互
- 当前进程service正在与activity进行交互或者当前service调用了startForground()属于前台进程或者当前service正在执行生命周期(onCreate(),onStart(),onDestory())
- 进程中有一个BroadcastReceiver,这个BroadcastReceiver正在执行onReceive()方法
- 可见进程:
- 进程中的activity,处于onPouse()状态下(比如当前覆盖的activity是以dialog形式存在)
- 进程有一个service,这个service和一个可见的Activity进行绑定。
- service进程:当前开启startSerice()启动一个service服务就可以认为进程是一个服务进程
- 后台进程:Activity的onStop()被调用,但是onDestroy()没有调用的状态。该进程属于后台进程
- 空进程:该进程没有任何运行的数据了,且保留在内存空间,属于空进程,该进程很容易被杀死。
结束
本文用于总结和学习
如有错误,欢迎指正
如果本文对你有帮助,麻烦点个赞或收藏哦
感谢