使用dumpsys命令学Activity启动模式.
引言
我们知道,在默认情况下,我们在创建多个Activity的时候,系统会创建多个实例并放在任务栈中,当切换Activity时,会多次启动同一个Activity,重复创建多个实例,是非常浪费资源的不明智的行为.启动模式则是一种好的解决方案.
启动模式种类
- standard:标准模式,即默认模式.一个任务栈可有多个实例,每个实例可以属于不同任务栈.每启动一个Activity就重新创建一个新的实例,不管实例是否已经存在.谁启动了这个Activity,这个Activity就会运行在启动它的哪个Activity所在的栈中.若目前有四个Activity,ABCD,现在新建D,则变为ABCDD.
- singleTop:栈顶复用该模式.ABCD新建D->ABCD.
- singleTask:栈内复用模式.当一个具有singleTask模式的Activity启动后,系统首先寻找是否存在A想要的任务栈,如果不存在,就创建一个任务栈,创建activity实例并压入栈中;如果存在想要的任务栈,就看任务栈里是否存在此activity的实例,存在的话,就把A调到栈顶(并调用onNewIntent方法),不存在就创建A实例并压入栈中.总之,这个模式的最终结果为,activity在其想要的任务栈的栈顶(事实上,并不是将其位置移动,而是将其之上的activity全部出战,实现clearTop效果).
- singleInstance:加强的singleTask模式.具有此种启动模式的activity启动后,系统为它创建一个新的任务栈,然后A独自在这个新的 任务栈中,且栈内复用不会再创建新的activity.
启动模式操作
怎样给Activity指定启动模式?
1.第一种方法是在配置清单中设置.
2.第二种方法是在Intent中设置标志位(flags).
两种方法比较,后一种优先级高;后一种无法指定singleInstance模式.
启动模式实践方法
最好的学习方法是实际操作.我们用adb shell来真切的感受下.这里以standard模式为例.
首先添加一个button控件,点击则从当前Activity跳转的新的当前Activity实例,
代码:
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mContext = this;
setContentView(R.layout.activity_main);
Button btn_jump = (Button) findViewById(R.id.btn_jump);
btn_jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, MainActivity.class);//跳转到新的当前类型的activity
intent.putExtra("time;", System.currentTimeMillis());
startActivity(intent);
}
});
}
}
启动app.连续点击五次跳转按钮.
命令:
adb devices//显示设备列表
adb -s xxx shell//连接指定设备并进入shell环境
dumpsys activity activities//查看activity信息
显示的内容很多.我们只关注必要信息,其他以省略号省略
generic_x86_64:/ $ dumpsys activity activities
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #1:
...
* TaskRecord{5cc63d3 #32 A=com.example.libin.activitytest U=0 StackId=1 sz=6}
...
Running activities (most recent first):
TaskRecord{5cc63d3 #32 A=com.example.libin.activitytest U=0 StackId=1 sz=6}
Run #5: ActivityRecord{e4f15da u0 com.example.libin.activitytest/.MainActivity t32}
Run #4: ActivityRecord{46de964 u0 com.example.libin.activitytest/.MainActivity t32}
Run #3: ActivityRecord{c35ee1e u0 com.example.libin.activitytest/.MainActivity t32}
Run #2: ActivityRecord{8bfce88 u0 com.example.libin.activitytest/.MainActivity t32}
Run #1: ActivityRecord{9204122 u0 com.example.libin.activitytest/.MainActivity t32}
Run #0: ActivityRecord{caf14e5 u0 com.example.libin.activitytest/.MainActivity t32}
...
Stack #0:
...
* TaskRecord{1bef863 #22 I=com.android.launcher3/.Launcher U=0 StackId=0 sz=1}
...
Task id #31
...
* TaskRecord{a43f609 #31 A=com.android.systemui U=0 StackId=0 sz=1}
...
Running activities (most recent first):
TaskRecord{1bef863 #22 I=com.android.launcher3/.Launcher U=0 StackId=0 sz=1}
Run #1: ActivityRecord{775727d u0 com.android.launcher3/.Launcher t22}
TaskRecord{a43f609 #31 A=com.android.systemui U=0 StackId=0 sz=1}
Run #0: ActivityRecord{f0f0003 u0 com.android.systemui/.recents.RecentsActivity t31}
...
...
我们只需关注其中的几行,
其中,每个TaskRecord可以看做是一个任务栈.
可以看出,系统给出了包括launcher(桌面)的任务栈和我们自己的应用的任务栈.我们有六个重复的Activity.这是因为在standard模式下,每次启动Activity都会创建一个新的实例放入任务栈中.
如果是栈内复用模式(SingleTask)呢?
修改配置清单
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.libin.activitytest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:launchMode="singleTask">//修改为栈内复用模式
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
重复上述步骤,注意连点五次跳转,,输出为:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #1:
...
Task id #34
...
* TaskRecord{7e6eec0 #34 A=com.example.libin.activitytest U=0 StackId=1 sz=1}
...
Running activities (most recent first):
TaskRecord{7e6eec0 #34 A=com.example.libin.activitytest U=0 StackId=1 sz=1}
Run #0: ActivityRecord{b611c37 u0 com.example.libin.activitytest/.MainActivity t34}
...
Stack #0:
...
Task id #33
...
* TaskRecord{85ff59f #33 I=com.android.launcher3/.Launcher U=0 StackId=0 sz=1}
...
Running activities (most recent first):
TaskRecord{85ff59f #33 I=com.android.launcher3/.Launcher U=0 StackId=0 sz=1}
Run #0: ActivityRecord{f934282 u0 com.android.launcher3/.Launcher t33}
与上文比对,发现在我们的任务栈中,activity只存在一个实例.验证了singleTask模式.
singleTask的复杂情况
几种模式都很好验证,但是singTask模式有更复杂的情况.
这里给出一个新的配置参数:taskAffinity.
taskAffinity暂时即可以理解为任务栈.
试想,现有FirstActivity,SecondActivity,ThirdActivity,(分别简称为A,B,C),分别为standard,singleTask,singleTask模式,现在从A跳转到B,再从B跳转到C,再从C跳转到A,载跳转到B,现在按返回键,返回到哪里?
解答:
1. 如果启动模式为standard,那么其taskAffinity则继承自Application的taskAffinity,默认为包名
2. 从A跳转到B,由于B是singleTask模式,所以单独创建其想要的任务栈即com.test.task1.入栈.
3. 从B跳转到C,C是singleTask模式,启动时发现已经存在想要的com.test.task1,于是直接入栈.此时栈结构为A和BC,C在栈顶.
4. 从C跳转到A,A为standard模式,会在启动它的任务栈中创建实例,也就是说现在的栈结构为A和BCA,A在栈顶.
5. 再从A跳转到B,B为singleTask模式,由于已经存在于com.test.task1中,要到达栈顶,这就意味着其上方的CA需要出栈.出栈去哪里了?去了后台任务栈.
6. B到达栈顶,此时按返回键.会到达哪里?B在栈顶,但也是所在栈的栈底.B按返回键之后,会回到后台任务栈的栈A.
实验结果:
启动A:
Running activities (most recent first):
TaskRecord{a8502c #40 A=com.example.libin.activitytest U=0 StackId=1 sz=1}
Run #0: ActivityRecord{8caeffc u0 com.example.libin.activitytest/.FirstActivity t40}
A->B:
Running activities (most recent first):
TaskRecord{c896c92 #41 A=com.test.task1 U=0 StackId=1 sz=1}
Run #1: ActivityRecord{a604318 u0 com.example.libin.activitytest/.SecondActivity t41}
TaskRecord{a8502c #40 A=com.example.libin.activitytest U=0 StackId=1 sz=1}
Run #0: ActivityRecord{8caeffc u0 com.example.libin.activitytest/.FirstActivity t40}
B->C
Running activities (most recent first):
TaskRecord{c896c92 #41 A=com.test.task1 U=0 StackId=1 sz=2}
Run #2: ActivityRecord{3332319 u0 com.example.libin.activitytest/.ThirdActivity t41}
Run #1: ActivityRecord{a604318 u0 com.example.libin.activitytest/.SecondActivity t41}
TaskRecord{a8502c #40 A=com.example.libin.activitytest U=0 StackId=1 sz=1}
Run #0: ActivityRecord{8caeffc u0 com.example.libin.activitytest/.FirstActivity t40}
C->A
Running activities (most recent first):
TaskRecord{c896c92 #41 A=com.test.task1 U=0 StackId=1 sz=3}
Run #3: ActivityRecord{d421e42 u0 com.example.libin.activitytest/.FirstActivity t41}
Run #2: ActivityRecord{3332319 u0 com.example.libin.activitytest/.ThirdActivity t41}
Run #1: ActivityRecord{a604318 u0 com.example.libin.activitytest/.SecondActivity t41}
TaskRecord{a8502c #40 A=com.example.libin.activitytest U=0 StackId=1 sz=1}
Run #0: ActivityRecord{8caeffc u0 com.example.libin.activitytest/.FirstActivity t40}
A->B:
Running activities (most recent first):
TaskRecord{c896c92 #41 A=com.test.task1 U=0 StackId=1 sz=1}
Run #1: ActivityRecord{a604318 u0 com.example.libin.activitytest/.SecondActivity t41}
TaskRecord{a8502c #40 A=com.example.libin.activitytest U=0 StackId=1 sz=1}
Run #0: ActivityRecord{8caeffc u0 com.example.libin.activitytest/.FirstActivity t40}
B->back:
Running activities (most recent first):
TaskRecord{b1b4aa4 #43 A=com.example.libin.activitytest U=0 StackId=1 sz=1}
Run #0: ActivityRecord{bce10d9 u0 com.example.libin.activitytest/.FirstActivity t43}
通过比对序列号可以发现,此时显示的A并不是最开始启动的A,而是后台切换回来的A.
Intent Flags
本节转载自链接:http://blog.csdn.net/vipzjyno1/article/details/25463457
Flags: 表示Intent的标志位,常用于Activity的场景中,它和Activity的启动模式有着密切的联系。
下面列举的是和本文主题相关的Flags属性:
Intent.FLAG_ACTIVITY_NEW_TASK (默认)
默认的跳转类型,它会重新创建一个新的Activity,不过与这种情况,比如说Task1中有A,B,C三个Activity,此时在C中启动D的话,如果在AndroidManifest.xml文件中给D添加了Affinity的值和Task中的不一样的话,则会在新标记的Affinity所存在的Task中压入这个Activity。如果是默认的或者指定的Affinity和Task一样的话,就和标准模式一样了启动一个新的Activity.
FLAG_ACTIVITY_SINGLE_TOP
这个FLAG就相当于启动模式中的singletop,例如:原来栈中结构是A B C D,在D中启动D,栈中的情况还是A,B,C,D。
FLAG_ACTIVITY_CLEAR_TOP
这个FLAG就相当于启动模式中的SingleTask,这种FLAG启动的Activity会把要启动的Activity之上的Activity全部弹出栈空间。例如:原来栈中的结构是A B C D ,从D中跳转到B,栈中的结构就变为了A B了。(这个方法可以用来关闭多个Activity,之后的一篇博文里面会提到)
FLAG_ACTIVITY_BROUGHT_TO_FRONT
这个网上很多人是这样写的。如果activity在task存在,拿到最顶端,不会启动新的Activity。这个有可能会误导大家! 他这个FLAG其实是这个意思!比如说我现在有A,在A中启动B,此时在A中Intent中加上这个标记。此时B就是以FLAG_ACTIVITY_BROUGHT_TO_FRONT方式启动,此时在B中再启动C,D(正常启动C,D),如果这个时候在D中再启动B,这个时候最后的栈的情况是 A,C,D,B。如果在A,B,C,D正常启动的话,不管B有没有用FLAG_ACTIVITY_BROUGHT_TO_FRONT启动,此时在D中启动B的话,还是会变成A,C,D,B的。
FLAG_ACTIVITY_NO_USER_ACTION
onUserLeaveHint()作为activity周期的一部分,它在activity因为用户要跳转到别的activity而要退到background时使用。比如,在用户按下Home键,它将被调用。比如有电话进来(不属于用户的选择),它就不会被调用。
那么系统如何区分让当前activity退到background时使用是用户的选择?
它是根据促使当前activity退到background的那个新启动的Activity的Intent里是否有FLAG_ACTIVITY_NO_USER_ACTION来确定的。
注意:调用finish()使该activity销毁时不会调用该函数
FLAG_ACTIVITY_NO_HISTORY
意思就是说用这个FLAG启动的Activity,一旦退出,它不会存在于栈中,比方说!原来是A,B,C这个时候再C中以这个FLAG启动D的,D再启动E,这个时候栈中情况为A,B,C,E。
参考资料:
《Android开发艺术探索》 博主链接:http://blog.csdn.net/singwhatiwanna?viewmode=contents
intent flags转载自: http://blog.csdn.net/vipzjyno1/article/details/25463457