赞
踩
本文整体框架转载自http://blog.csdn.net/zhangjg_blog/article/details/10923643
当时看到这篇文章的时候就发现很多东西是自己以前没有注意到的,有一种“原来是这样的感觉”,所以打算对部分测试内容自己亲自测试做个记录,方便自己回过头在回顾,同时对一些文章中一些别人有歧义的地方在做进一步测试,毕竟有时候自己亲自做过的东西,印象才会更深刻,理解更深。
在android应用开发中,打造良好的用户体验是非常重要的。而在用户体验中,界面的引导和跳转是值得深入研究的重要内容。在开发中,与界面跳转联系比较紧密的概念是Task(任务)和Back Stack(回退栈)。activity的启动模式会影响Task和Back Stack的状态,进而影响用户体验。除了启动模式之外,Intent类中定义的一些标志(以FLAG_ACTIVITY_开头)也会影响Task和Back Stack的状态。在这篇文章中主要对四种启动模式进行分析和验证,其中涉及到activity的一个重要属性taskAffinity和Intent中的标志之一FLAG_ACTIVITY_NEW_TASK。关于Intent中其他标志位的具体用法会在另一篇文章中介绍。
Task是一个存在于Framework层的概念,容易与它混淆的有Application(应用)和Process(进程)。在开始介绍Activity的启动模式的使用之前,首先对这些概念做一个简单的说明和区分。
- <?xml version="1.0" encoding="utf-8"?>
- <manifest android:versionCode="1"
- android:versionName="1"
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.android.myapp">
-
- <application android:label="@string/app_name">
- <activity android:name=".MyActivity" android:label="@string/app_nam">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <receiver android:name=".MyReceiver"/>
- <provider android:name=".MyProvider"/>
- <service android:name=".MyService"/>
- </application>
- </manifest>
- <activity android:name=".MyActivity" android:label="@string/app_nam"
- android:process=":remote">
- </activity>
- <activity android:name=".app.InterstitialMessageActivity"
- android:label="@string/interstitial_label"
- android:theme="@style/Theme.Dialog"
- android:launchMode="singleTask"
- </activity>
标准启动模式,也是activity的默认启动模式。在这种模式下启动的activity可以被多次实例化,即在同一个任务中可以存在多个activity的实例,每个实例都会处理一个Intent对象。如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A-->A。
如果一个以singleTop模式启动的activity的实例已经存在于任务桟的桟顶,那么再启动这个Activity时,不会创建新的实例,而是重用位于栈顶的那个实例,并且会调用该实例的onNewIntent()方法将Intent对象传递到这个实例中。举例来说,如果A的启动模式为singleTop,并且A的一个实例已经存在于栈顶中,那么再调用startActivity(new Intent(this,A.class))启动A时,不会再次创建A的实例,而是重用原来的实例,并且调用原来实例的onNewIntent()方法。这是任务桟中还是只有一个A的实例。
如果以singleTop模式启动的activity的一个实例已经存在与任务桟中,但是不在桟顶,那么它的行为和standard模式相同,也会创建多个实例。也就是说在A中启动A,会重用原来的A不会调用新的实例,并且调用A的onNewIntent()
这里自己做2个小测试:一共3个activity,MainActivity(standard)、SecondActivity(singleTop)、thirdActivity(standard)
第一次测试:栈顶有实例
首先程序启动,MainActivity创建,单击按钮,创建SecondActivity,单击按钮再次创建SecondActivity
第一次创建 SecondActivity
adb shell dumpsys activity
在SecondActivity中第二次创建 SecondActivity,单击按钮
adb shell dumpsys activity
对比上面可以看出两个SecondActivity是同一个实例,只是调用了onNewIntent重用了原来的实例
第二次测试:栈顶无实例
首先程序启动,MainActivity创建,单击按钮,创建SecondActivity,单击按钮,创建thirdActivity,单击按钮创建SecondActivity。
第一次创建 SecondActivity
adb shell dumpsys activity
在SecondActivity中创建 thirdActivity,单击按钮
adb shell dumpsys activity
在thirdActivity中创建 SecondActivity,单击按钮
adb shell dumpsys activity
通过两次对比,就可以清楚的发现singleTop模式下,activity只有在栈顶有该实例的时候会重用调用onNewIntent方法,而在即使task中有该实例,但是非栈顶时,还是会创建一个新的实例
谷歌的官方文档上称,如果一个activity的启动模式为singleTask,那么系统总会在一个新任务的最底部(root)启动这个activity,并且被这个activity启动的其他activity会和该activity同时存在于这个新任务中。如果系统中已经存在这样的一个activity则会重用这个实例,并且调用他的onNewIntent()方法。即,这样的一个activity在系统中只会存在一个实例。
其实官方文档中的这种说法并不准确,启动模式为singleTask的activity并不会总是开启一个新的任务。详情请参考 解开Android应用程序组件Activity的"singleTask"之谜,在本文后面也会通过示例来进行验证。
总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他activity会自动运行于另一个任务中。当再次启动该activity的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将Intent实例传递到该实例中。和singleTask相同,同一时刻在系统中只会存在一个这样的Activity实例。
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.q.view">
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.CAMERA"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity
- android:name=".MainActivity"
- android:label="@string/app_name"
- >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <!--android:taskAffinity="com.example.q.view.second"-->
- <activity android:name=".SecondActivity"
- android:exported="true"
-
- android:launchMode="singleTask"
- android:label="secondActivity">
-
- </activity>
- <!--android:theme="@style/translucent"-->
- <activity android:name=".thirdActivity"
- android:label="thirdActivity">
-
- </activity>
- <activity android:name=".WebActivity"
- android:label="webview">
-
- </activity>
- </application>
-
- </manifest>
- public class MainActivity extends AppCompatActivity {
- private static final String TAG="MainActivity";
- Button btn_change;
- int taskid;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- taskid=getTaskId();
- Log.e(TAG, "onCreate: taskid="+taskid);
- btn_change=(Button) findViewById(R.id.btn_change);
- btn_change.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent=new Intent();
- intent.setClass(MainActivity.this,SecondActivity.class);
- startActivity(intent);
- //change
- }
- });
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- taskid=getTaskId();
- Log.e(TAG, "onNewIntent: taskid="+taskid);
- }
}
- public class SecondActivity extends AppCompatActivity {
- private static final String TAG="secondActivity";
- Button btn_change1;
- int taskid;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main1);
- taskid=getTaskId();
- Log.e(TAG, "onCreate: taskid="+taskid);
- btn_change1=(Button) findViewById(R.id.btn_change1);
- btn_change1.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent=new Intent();
- intent.setClass(SecondActivity.this,thirdActivity.class);
- startActivity(intent);
- }
- });
- }
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- taskid=getTaskId();
- Log.e(TAG, "onNewIntent: taskid="+taskid);
- }
}
- public class thirdActivity extends AppCompatActivity {
- private static final String TAG="thirdActivity";
- Button btn_change2;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main2);
- Log.e(TAG, "onCreate: ");
- btn_change2=(Button) findViewById(R.id.btn_change2);
- btn_change2.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent=new Intent();
- intent.setClass(thirdActivity.this,SecondActivity.class);
- startActivity(intent);
-
- }
- });
- }
}
- <activity android:name=".SecondActivity"
- android:exported="true"
- android:taskAffinity="com.example.q.view.second"
- android:launchMode="singleTask"
- android:label="secondActivity">
- </activity>
点击SecondActivity中的按钮,启动ThirdActivity
这就可以解释上面示例中的现象了,由第5条可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity为com.example.q.view,SecondActivity的taskAffinity为com.example.q.view.second,根据上面第4条,taskAffinity可以影响当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。这句话的意思是,当新启动的activity(SecondActivity)是以FLAG_ACTIVITY_NEW_TASK标志启动时(可以认为FLAG_ACTIVITY_NEW_TASK和singleTask作用相同,当启动模式为singleTask时,framework会将它的启动标志设为FLAG_ACTIVITY_NEW_TASK),framework会检索是否已经存在了一个affinity为com.example.q.view.second的任务(即一个TaskRecord对象)
注:这里如果仅仅是设置了FLAG_ACTIVITY_NEW_TASK和taskAffinity而不设置启动模式为singleTask,是不能保证Activity的栈内唯一性的。所以上面“可以认为FLAG_ACTIVITY_NEW_TASK和singleTask作用相同“的表述可能会有点小问题
上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名,那么SecondActivity是不会开启一个新任务的,framework中的判定过程如下:
为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.example.q.view.second时的启动过程
其实framework中对任务和activity的调度是很复杂的,尤其是把启动模式设为singleTask或者以FLAG_ACTIVITY_NEW_TASK标志启动时。所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK时,要仔细测试应用程序。这也是官方文档上的建议。
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.q.androidtasktest">
-
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <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=".OtherActivity"
- android:taskAffinity="com.example.q.view.second"
- android:launchMode="singleTask">
-
- </activity>
- </application>
-
- </manifest>
AndroidTaskTest的log
好吧这里忘记给OtherActivity打印log了,不过从上面的adb shell里也可以看出来
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.q.view">
-
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/AppTheme">
- <activity
- android:name=".MainActivity"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <activity
- android:name=".SecondActivity"
- android:exported="true"
- android:label="secondActivity"
- android:launchMode="singleTask"
- >
-
- </activity>
- <!-- android:theme="@style/translucent" -->
- <activity
- android:name=".thirdActivity"
- android:label="thirdActivity"
- >
-
- </activity>
- <activity android:name=".FourthActivity"
- android:label="fourthActivity"
- >
- </activity>
- </application>
-
- </manifest>
log打印
这里我没有给fourthactivity的除onCreate外的方法打印log,不然应该是fourthactivity也会被stop和destroy,从后面的adb shell 也能看出来 task id=49的任务里在Second之上的活动全部被清除了。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.q.view"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SecondActivity" android:exported="true" android:label="secondActivity" android:launchMode="singleInstance" > <intent-filter> <action android:name="com.example.q.view.ACTION_MY"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <!-- android:theme="@style/translucent" --> <activity android:name=".thirdActivity" android:label="thirdActivity" > </activity> <activity android:name=".FourthActivity" android:label="fourthActivity" > </activity> </application> </manifest>
由上面的清单文件可以知道,该应用包括三个activity,分别为MainActivity,SecondActivity,ThirdActivity,其中SecondActivity启动模式设置为singleInstance。MainActivity可以开启SecondActivity,SecondActivity可以开启ThirdActivity。 并且为了可以在其他应用中开启SecondActivity,为SecondActivity设置了一个IntentFilter,这样就可以在其他应用中使用隐式Intent开启SecondActivity。
为了更好的验证singleInstance的全局唯一性,还需要其他一个应用,对上面的AndroidTaskTest进行一些修改即可。AndroidTaskTest只需要一个MainActivity,在MainActivity中点击按钮会开启AndroidTaskTest应用中的SecondActivity。开启AndroidTaskTest应用中的SecondActivity的代码如下:
执行adb shell dumpsys activity命令,有以下输出:
以上可以说明,singleInstance模式的Activity总是会在新的任务中运行(前提是系统中还不存在这样的一个实例) 。
下面验证它的全局唯一性,执行以下操作:安装另一个应用AndroidTaskTest1,在开启的MainActivity中点击按钮开启AndroidTaskTest应用中的SecondActivity。看到打印出一条新的日志:
执行adb shell dumpsys activity命令,有以下输出:
由此可以得知,开启的SecondActivity就是上次创建的编号为task id=51的SecondActivity,并且Log中没有再次输出关于SecondActivity的信息,说明SecondActivity并没有重新创建。由此可以得出结论:以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
- <activity
- android:name=".thirdActivity"
- android:label="thirdActivity"
- android:taskAffinity="com.example.q.view.second"
- >
- </activity>
由于ThirdActivity是被启动模式为singleInstance类型的Activity(即SecondActivity)启动的,framework会为它它加上FLAG_ACTIVITY_NEW_TASK标志,这时 framework会检索是否已经存在了一个affinity为com.example.q.view.second
(即ThirdActivity的taskAffinity属性)的任务,
如果ThirdActivity不设置taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都为应用的包名,那么ThirdActivity是不会开启一个新任务的,framework中的判定过程如下:
为了作一个清楚的比较,列出ThirdActivity的taskAffinity属性设为com.jg.zhang.androidtasktest.second时的启动过程
我们在操作软件的过程中,一定会涉及界面的跳转。其实在对界面进行跳转时,Android Framework既能在同一个任务中对Activity进行调度,也能以Task为单位进行整体调度。在启动模式为standard或singleTop时,一般是在同一个任务中对Activity进行调度,而在启动模式为singleTask或singleInstance是,一般会对Task进行整体调度。
对Task进行整体调度包括以下操作:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。