Activity启动模式与任务栈(Task)实例验证

你真的了解taskAffinity属性么? 在2019年,有安全研究员向Google官方提交了一个利用taskAffinity的漏洞,后面被命名为StandHogg。最近在2020年5月份又公布了StandHogg 2.0漏洞。StandHogg 2.0 隐藏的更深,一般很难发现,甚至都不用设置taskAffinity就可以实现。目前官方已经发布了Android 10.0系统补丁包,有条件的可以对系统进行升级。有兴趣的同学可以看下这篇文章:https://www.xda-developers.com/strandhogg-2-0-android-vu 阅读详情

一 实例验证singleTask启动模式


上面将activity的四种启动模式就基本介绍完了。为了加深对启动模式的了解,下面会通过一个简单的例子进行验证。由以上的介绍可知,standard和singleTop这两种启动模式行为比较简单,所以在下面的例子中,会对singleTask和singleInstance着重介绍。


验证启动singleTask模式的activity时是否会创建新的任务


以下为验证示例AndroidTaskTest。这个实例中有三个Activity,分别为:MainActivity,SecondActivity和ThirdActivity。以下为这个示例的manifest文件。

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.jg.zhang.androidtasktest"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17" />  
  8.   
  9.     <application android:icon="@drawable/ic_launcher" android:label="@string/app_name">  
  10.         <activity  android:label="@string/app_name"  
  11.             android:name="com.jg.zhang.androidtasktest.MainActivity">  
  12.             <intent-filter>  
  13.                 <action android:name="android.intent.action.MAIN" />  
  14.                 <category android:name="android.intent.category.LAUNCHER" />  
  15.             </intent-filter>  
  16.         </activity>  
  17.           
  18.         <!--android:taskAffinity="com.jg.zhang.androidtasktest.second"   
  19.             android:alwaysRetainTaskState="true"  
  20.             android:allowBackup="true" -->  
  21.               
  22.          <activity android:name="com.jg.zhang.androidtasktest.SecondActivity"  
  23.              android:launchMode="singleTask">  
  24.             <intent-filter >  
  25.                 <action android:name="com.jg.zhang.androidtasktest.SecondActivity"/>  
  26.                 <category android:name="android.intent.category.DEFAULT"/>  
  27.             </intent-filter>  
  28.         </activity>  
  29.           
  30.          <activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"  
  31.             android:label="@string/app_name" >  
  32.         </activity>  
  33.     </application>  
  34.       
  35. </manifest>  

由此可见,MainActivity和ThirdActivity都是标准的启动模式,而SecondActivity的启动模式为singleTask。

以下为这三个Activity的界面,很简单,在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。



以下为这三个activity的主要代码:

MainActivity
[java]  view plain  copy
  1. public class MainActivity extends Activity {  
  2.   
  3.     private static final String ACTIVITY_NAME = "MainActivity";  
  4.     private static final String LOG_TAG = "xxxx";  
  5.   
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.activity_main);  
  10.           
  11.         findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {  
  12.             @Override  
  13.             public void onClick(View v) {  
  14.                 Intent intent = new Intent(MainActivity.this, SecondActivity.class);  
  15.   
  16.                 startActivity(intent);  
  17.             }  
  18.         });  
  19.           
  20.         int taskId = getTaskId();  
  21.         Log.i(LOG_TAG, ACTIVITY_NAME +"所在的任务的id为: " +  taskId);  
  22.     }  



SecondActivity
[java]  view plain  copy
  1. public class SecondActivity extends Activity {  
  2.     private static final String ACTIVITY_NAME = "SecondActivity";  
  3.     private static final String LOG_TAG = "xxxx";  
  4.     @Override  
  5.     protected void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.activity_second);  
  8.           
  9.         findViewById(R.id.button2).setOnClickListener(new OnClickListener() {  
  10.             @Override  
  11.             public void onClick(View v) {  
  12.                     Intent intent = new Intent(SecondActivity.this, ThirdActivity.class);  
  13.                     startActivity(intent);  
  14.             }  
  15.         });  
  16.           
  17.         int taskId = getTaskId();  
  18.         Log.i(LOG_TAG, ACTIVITY_NAME +"所在的任务的id为: " +  taskId);  
  19.           
  20.     }  

ThirdActivity
[java]  view plain  copy
  1. public class ThirdActivity extends Activity {  
  2.       
  3.     private static final String ACTIVITY_NAME = "ThirdActivity";  
  4.     private static final String LOG_TAG = "xxxx";  
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_third);  
  9.         int taskId = getTaskId();  
  10.         Log.i(LOG_TAG, ACTIVITY_NAME +"所在的任务的id为: " +  taskId);  
  11.     }  

以上三个activity只列出了onCreate()方法中的内容,实现的逻辑为在MainActivity中点击按钮启动SecondActivity,在SecondActivity中点击按钮启动ThirdActivity。并且在onCreate方法中会以log的形式打印出当前activity所属的任务(Task)的Id。

现在执行以下操作,运行该示例,并且点击MainActivity界面中的按钮,开启SecondActivity。在该示例中SecondActivity的启动模式为singleTask。按照官方文档的说法,SecondActivity会在一个新的任务中开启。但是查看打印出的log,发现MainActivity和SecondActivity所在的任务的Id相同。


在命令行中执行以下命令 adb shell dumpsys activity , 有以下输出:
TaskRecord{412ded08 #8 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{412c91e8 com.jg.zhang.androidtasktest/.SecondActivity}
Run #1: ActivityRecord{412c08a0 com.jg.zhang.androidtasktest/.MainActivity}

所以,和官方文档表述的不同,MainActivity和SecondActivity是启动在同一个任务中的。 其实,把启动模式设置为singleTask,framework在启动该activity时只会把它标示为可在一个新任务中启动,至于是否在一个新任务中启动,还要受其他条件的限制。现在在SecondActivity增加一个taskAffinity属性,如下所示:

[html]  view plain  copy
  1. <activity android:name="com.jg.zhang.androidtasktest.SecondActivity"  
  2.     android:launchMode="singleTask"  
  3.     android:taskAffinity="com.jg.zhang.androidtasktest.second">  
  4.    <intent-filter >  
  5.        <action android:name="com.jg.zhang.androidtasktest.SecondActivity"/>  
  6.        <category android:name="android.intent.category.DEFAULT"/>  
  7.    </intent-filter>  
  8. lt;/activity>  

重新运行该示例,执行相同的操作,即:点击MainActivity界面中的按钮,开启SecondActivity,并且点击SecondActivity中的按钮,启动ThirdActivity,log中输出的内容为:


在命令行中执行adb shell dumpsys activity命令,有以下输出:
 
TaskRecord{411e6a88 #6 A com.jg.zhang.androidtasktest.second}
Run #3: ActivityRecord{411c8ea0 com.jg.zhang.androidtasktest/.ThirdActivity}
Run #2: ActivityRecord{412bc870 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{412ece18 #5 A com.jg.zhang.androidtasktest}
 Run #1: ActivityRecord{412924c0 com.jg.zhang.androidtasktest/.MainActivity}

由此可见,MainActivity和SecondActivity运行在不同的任务中了,并且被SecondActivity启动的ThirdActivity和SecondActivity运行在同一个任务中。这种现象的具体解释可以参考 解开Android应用程序组件Activity的"singleTask"之谜。

在这里便引出了manifest文件中<activity>的一个重要属性,taskAffinity。在官方文档中可以得到关于taskAffinity的以下信息
  1. taskAffinity表示当前activity具有亲和力的一个任务(翻译不是很准确,原句为The task that the activity has an affinity for.),大致可以这样理解,这个 taskAffinity表示一个任务,这个任务就是当前activity所在的任务。
  2. 在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务。
  3.  一个任务的affinity决定于这个任务的根activity(root activity)的taskAffinity。
  4.  这个属性决定两件事:当activity被re-parent时,它可以被re-paren哪个任务中;当activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被启动到哪个任务中。(这个比较    难以理解,请结合<activity>中的属性allowTaskReparenting和Intent中的标志       FLAG_ACTIVITY_NEW_TASK加以理解)
  5.  默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的       应用中的activity的taskAffinity设置成相同的值。
  6.  为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。

这就可以解释上面示例中的现象了,由第5条可知,MainActivity和SecondActivity具有不同的taskAffinity,MainActivity的taskAffinity为com.jg.zhang.androidtasktest,SecondActivity的taskAffinity为com.jg.zhang.androidtasktest.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.jg.zhang.androidtasktest.second的任务(即一个TaskRecord对象)

  • 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个SecondActivity的实例,
    • 如果已经存在一个SecondActivity的实例,则会重用这个任务和任务中的SecondActivity实例,将这个任务调到前台,清除位于SecondActivity上面的所有Activity,显示SecondActivity,并调用SecondActivity的onNewIntent();
    • 如果不存在一个SecondActivity的实例,会在这个任务中创建SecondActivity的实例,并调用onCreate()方法
  • 如果不存在这样的一个任务,会创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将SecondActivity启动到这个新的任务中


上面讨论的是设置taskAffinity属性的情况,如果SecondActivity只设置启动模式为singleTask,而不设置taskAffinity,即三个Activity的taskAffinity相同,都为应用的包名,那么SecondActivity是不会开启一个新任务的,framework中的判定过程如下:

  1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK
  2.  然后获得SecondActivity的taskAffinity,即为包名com.jg.zhang.androidtasktest
  3. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的
  4.  既然已经存在这个任务,就检索在这个任务中是否存在一个SecondActivity的实例,发现不存在
  5.  在这个已有的任务中启动一个SecondActivity的实例

为了作一个清楚的比较,列出SecondActivity启动模式设为singleTask,并且taskAffinity设为com.jg.zhang.androidtasktest.second时的启动过程

  1. 在MainActivity启动SecondActivity时,发现启动模式为singleTask,那么设定他的启动标志为FLAG_ACTIVITY_NEW_TASK
  2. 然后获得SecondActivity的taskAffinity,即com.jg.zhang.androidtasktest.second
  3. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest.second的任务,这个任务是不存在的
  4. 创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将SecondActivity启动到这个新的任务中

其实framework中对任务和activity‘的调度是很复杂的,尤其是把启动模式设为singleTask或者以FLAG_ACTIVITY_NEW_TASK标志启动时。所以,在使用singleTask和FLAG_ACTIVITY_NEW_TASK时,要仔细测试应用程序。这也是官方文档上的建议。



实例验证将两个不同app中的不同的singleTask模式的Activity的taskAffinity设成相同


官方文档中提到,可以把不同的 应用中的activity的taskAffinity设置成相同的值,这样的话这两个activity虽然不在同一应用中,却会在运行时分配到同一任务中,下面对此进行验证,在这里,会使用上面的示例AndroidTaskTest,并创建一个新的示例AndroidTaskTest1。AndroidTaskTest1由两个activity组成,分别为MianActivity和OtherActivity,在MianActivity中点击按钮会启动OtherActivity,该程序的界面和上一个类似,代码也类似,再此仅列出清单文件。

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.jg.zhang.androidtasktest1"  
  4.     android:versionCode="1"  android:versionName="1.0" >  
  5.   
  6.     <uses-sdk android:minSdkVersion="9"  android:targetSdkVersion="17" />  
  7.   
  8.     <application  
  9.         android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name"  
  10.         android:theme="@style/AppTheme" >  
  11.         <activity  
  12.             android:name="com.jg.zhang.androidtasktest1.MainActivity"  
  13.             android:label="com.jg.zhang.androidtasktest1.MainActivity" >  
  14.             <intent-filter>  
  15.                 <action android:name="android.intent.action.MAIN" />  
  16.                 <category android:name="android.intent.category.LAUNCHER" />  
  17.             </intent-filter>  
  18.         </activity>  
  19.           
  20.         <activity  
  21.             android:name="com.jg.zhang.androidtasktest1.OtherActivity"  
  22.             android:label="com.jg.zhang.androidtasktest1.OtherActivity"  
  23.             android:taskAffinity="com.jg.zhang.androidtasktest.second"  
  24.             android:launchMode="singleTask">  
  25.         </activity>  
  26.     </application>  
  27.   
  28. </manifest>  

可以看到OtherActivity的启动模式被设置为singleTask,并且taskAffinity属性被设置为com.jg.zhang.androidtasktest.second,这和AndroidTaskTest应用中的SecondActivity相同。现在将这两个应用安装在设备上。执行以下操作:

启动AndroidTaskTest应用,在它的MianActivity中点击按钮开启SecondActivity,由上面的介绍可知secondActivity是运行在一个新任务中的,这个任务就是com.jg.zhang.androidtasktest.second。

然后按Home键回到Launcher,启动AndroidTaskTest1,在启动AndroidTaskTest1的入口Activity(MianActivity)时,会自动启动新的任务,那么现在一共有三个任务,AndroidTaskTest的MianActivity和SecondActivity分别占用一个任务,AndroidTaskTest1的MianActivity也占用一个任务。

在AndroidTaskTest1的MianActivity中点击按钮启动OtherActivity,那么这个OtherActivity是在哪个任务中呢?

下面执行adb shell dumpsys activity命令,发现有以下输出:

TaskRecord{412370c0 #4 A  com.jg.zhang.androidtasktest.second} 
Intent { cmp=com.jg.zhang.androidtasktest/.SecondActivity }
Hist #4: ActivityRecord{412f5ba0  com.jg.zhang.androidtasktest1/.OtherActivity}
Intent { flg=0x400000 cmp=com.jg.zhang.androidtasktest1/.OtherActivity }
ProcessRecord{412adb28 479:com.jg.zhang.androidtasktest1/ 10044}
Hist #3: ActivityRecord{4125c880  com.jg.zhang.androidtasktest/.SecondActivity}
Intent { cmp=com.jg.zhang.androidtasktest/.SecondActivity }
ProcessRecord{41218e48 463:com.jg.zhang.androidtasktest/ 10043}

TaskRecord{412f0f60 #5 A  com.jg.zhang.androidtasktest1} 
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.jg.zhang.androidtasktest1/.MainActivity }
Hist #2: ActivityRecord{413045a8  com.jg.zhang.androidtasktest1/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.jg.zhang.androidtasktest1/.MainActivity }
ProcessRecord{412adb28 479:com.jg.zhang.androidtasktest1/ 10044}
   
TaskRecord{412c5928 #3 A  com.jg.zhang.androidtasktest}
 Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.jg.zhang.androidtasktest/.MainActivity }
Hist #0: ActivityRecord{41250850  com.jg.zhang.androidtasktest/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.jg.zhang.androidtasktest/.MainActivity }
ProcessRecord{41218e48 463:com.jg.zhang.androidtasktest/ 10043}

在执行上述操作时,打印出的Log为:


所以由此可见,AndroidTaskTest的SecondActivity和AndroidTaskTest1的OtherActivity是在同一任务中的。由上面adb shell dumpsys activity命令的输出结果(蓝色字体)还可以看出,AndroidTaskTest和AndroidTaskTest1这两个应用程序会开启两个进程,他们的所有组件分别运行在独立的进程中,其中AndroidTaskTest所在进程的进程号为10043,AndroidTaskTest1所在进程的进程号为10044。com.jg.zhang.androidtasktest.second任务中的两个activity属于不同的应用,并且运行在不同的进程中,这也说明了一个问题: 任务(Task)不仅可以跨应用(Application),还可以跨进程(Process)。


实例验证singleTask的另一意义:在同一个任务中具有唯一性


谷歌官方文档中提到,singleTask模式的activity总会在一个新的任务中开启。上面已经验证了这种说法不确切,singleTask模式只意味着“可以在一个新的任务中开启”,至于是不是真的会在新任务中开启,在framework中还有其他条件的限制。由上面的介绍可知,这个条件为:是否已经存在了一个由他的taskAffinity属性指定的任务。这一点具有迷惑性,我们在看到singleTask这个单词的时候,会直观的想到它的本意:single in task。即,在同一个任务中,只会有一个该activity的实例。现在让我们进行验证:

为了验证这种情况,需要修改一下上面用到的AndroidTaskTest示例。增加一个FourthActivity,并且MianActivity,SecondActivity,ThirdActivity和FourthActivity这四个activity都不设置taskAffinity属性,并且将SecondActivity启动模式设为singleTask,这样这四个activity会在同一个任务中开启。他们的开启流程是这样的:MianActivity开启SecondActivity,SecondActivity开启ThirdActivity,ThirdActivity开启FourthActivity,FourthActivity开启SecondActivity。代码和软件界面就不列出了,只列出清单文件

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.jg.zhang.androidtasktest"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17" />  
  8.   
  9.     <application android:allowBackup="true"  
  10.         android:icon="@drawable/ic_launcher" android:label="androidtasktest">  
  11.           
  12.         <activity  android:name="com.jg.zhang.androidtasktest.MainActivity">  
  13.             <intent-filter>  
  14.                 <action android:name="android.intent.action.MAIN" />  
  15.                 <category android:name="android.intent.category.LAUNCHER" />  
  16.             </intent-filter>  
  17.         </activity>  
  18.               
  19.          <activity android:name="com.jg.zhang.androidtasktest.SecondActivity"  
  20.              android:launchMode="singleTask"/>  
  21.           
  22.          <activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"/>  
  23.            
  24.          <activity android:name="com.jg.zhang.androidtasktest.FourthActivity"/>  
  25.            
  26.     </application>  
  27.       
  28. </manifest>  

现在从MianActivity一直启动到FourthActivity,打印出的系统Log为:


由此可见这四个activity都是在同一个任务中的。再次执行adb shell dumpsys activity命令加以验证:

TaskRecord{412e9458 #6 A com.jg.zhang.androidtasktest}
Run #4: ActivityRecord{412e12e8 com.jg.zhang.androidtasktest/.FourthActivity}
Run #3: ActivityRecord{412a9e30 com.jg.zhang.androidtasktest/.ThirdActivity}
Run #2: ActivityRecord{412a4dd8 com.jg.zhang.androidtasktest/.SecondActivity}
Run #1: ActivityRecord{4122fae0 com.jg.zhang.androidtasktest/.MainActivity}

同样可以说明目前这四个activity都运行在affinity为com.jg.zhang.androidtasktest的任务中,即栈中的状态为MainActivity -->  SecondActivity --> ThirdActivity --> FourthActivity。

下面执行在FourthActivity中点击按钮启动SecondActivity的操作,注意,SecondActivity的启动模式为singleTask,那么现在栈中的情况如何呢?再次执行adb shell dumpsys activity命令,有以下输出:

TaskRecord{412e9458 #6 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{412a4dd8 com.jg.zhang.androidtasktest/.SecondActivity}
Run #1: ActivityRecord{4122fae0 com.jg.zhang.androidtasktest/.MainActivity}

这时栈中的状态为MainActivity -->  SecondActivity。确实确保了在任务中是唯一的,并且清除了同一任务中它上面的所有Activity。 那么这个SecondActivity的实例是重用的上次已有的实例还是重新启动了一个实例呢?可以观察系统Log, 发现系统Log没有改变,还是上面的四条Log。打印Log的语句是在各个Activity中的onCreate方法中执行的,没有打印出新的Log,说明SecondActivity的onCreate的方法没有重新执行,也就是说是重用的上次已经启动的实例,而不是销毁重建。

经过上面的验证,可以得出如下的结论: 在启动一个singleTask的Activity实例时,如果系统中已经存在这样一个实例,就会将这个实例调度到任务栈的栈顶,并清除它当前所在任务中位于它上面的所有的activity。



二 实例验证singleInstance的行为


根据上面的讲解,并且参考谷歌官方文档,singleInstance的特点可以归结为以下三条:
  1. 以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例
  2. 以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中(官方文档上的描述为,singleInstance模式的Activity不允许其他Activity和它共存在一个任务中)
  3. 被singleInstance模式的Activity开启的其他activity,能够开启一个新任务,但不一定开启新的任务,也可能在已有的一个任务中开启
下面对这三个特点分别验证,所使用的示例同样为AndroidTaskTest,只不过会进行一些修改,下面列出它的清单文件:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.jg.zhang.androidtasktest"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17" />  
  8.   
  9.     <application android:allowBackup="true"  
  10.         android:icon="@drawable/ic_launcher" android:label="androidtasktest">  
  11.           
  12.         <activity  android:name="com.jg.zhang.androidtasktest.MainActivity">  
  13.             <intent-filter>  
  14.                 <action android:name="android.intent.action.MAIN" />  
  15.                 <category android:name="android.intent.category.LAUNCHER" />  
  16.             </intent-filter>  
  17.         </activity>  
  18.           
  19.          <activity android:name="com.jg.zhang.androidtasktest.SecondActivity"  
  20.              android:launchMode="singleInstance">  
  21.              <intent-filter>  
  22.                  <action android:name="com.jg.zhang.androidtasktest.ACTION_MY"/>  
  23.                  <category android:name="android.intent.category.DEFAULT"/>  
  24.              </intent-filter>  
  25.          </activity>  
  26.           
  27.          <activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"/>  
  28.            
  29.     </application>  
  30.       
  31. </manifest>  

由上面的清单文件可以知道,该应用包括三个activity,分别为MianActivity,SecondActivity,ThirdActivity,其中SecondActivity启动模式设置为singleInstance。MianActivity可以开启SecondActivity,SecondActivity可以开启ThirdActivity。 并且为了可以在其他应用中开启SecondActivity,为SecondActivity设置了一个IntentFilter,这样就可以在其他应用中使用隐式Intent开启SecondActivity。


为了更好的验证singleInstance的全局唯一性,还需要其他一个应用,对上面的AndroidTaskTest1进行一些修改即可。AndroidTaskTest1只需要一个MianActivity,在MainActivity中点击按钮会开启AndroidTaskTest应用中的SecondActivity。开启AndroidTaskTest应用中的SecondActivity的代码如下:

[java]  view plain  copy
  1. /** 
  2.  * 该方法在布局中按钮的android:onClick属性中指定 
  3.  * android:onClick="launchOtherActivity" 
  4.  * @param v 
  5.  */  
  6. public void launchOtherActivity(View v){  
  7.     Intent intent = new Intent();  
  8.       
  9.     //以下Action为"com.jg.zhang.androidtasktest.ACTION_MY"  
  10.     //即AndroidTaskTest应用中SecondActivity的action  
  11.     intent.setAction("com.jg.zhang.androidtasktest.ACTION_MY");  
  12.       
  13.     startActivity(intent);  
  14. }  


下面开始验证第一个特点:以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只会存在一个这样的实例


执行如下操作:安装AndroidTaskTest应用,点击MainActivity中的按钮,开启SecondActivity,可以看到如下log输出:

执行adb shell dumpsys activity命令,有以下输出:

TaskRecord{411189e0 #9 A com.jg.zhang.androidtasktest}

Run #2: ActivityRecord{4129af80 com.jg.zhang.androidtasktest/.SecondActivity}

TaskRecord{41305528 #8 A com.jg.zhang.androidtasktest}

Run #1: ActivityRecord{41296e60 com.jg.zhang.androidtasktest/.MainActivity}

以上可以说明,singleInstance模式的Activity总是会在新的任务中运行(前提是系统中还不存在这样的一个实例) 。


下面验证它的全局唯一性,执行以下操作:安装另一个应用AndroidTaskTest1,在开启的MainActivity中点击按钮开启AndroidTaskTest应用中的SecondActivity。看到打印出一条新的日志:


执行adb shell dumpsys activity命令,有以下输出:

TaskRecord{411189e0 #9 A com.jg.zhang.androidtasktest}

Run #3: ActivityRecord{4129af80 com.jg.zhang.androidtasktest/.SecondActivity}

TaskRecord{412dc788 #12 A com.jg.zhang.androidtasktest1}

Run #2: ActivityRecord{4121c628 com.jg.zhang.androidtasktest1/.MainActivity}

TaskRecord{41305528 #8 A com.jg.zhang.androidtasktest}

Run #1: ActivityRecord{41296e60 com.jg.zhang.androidtasktest/.MainActivity}


由红色字体可以得知,开启的SecondActivity就是上次创建的编号为4129af80的SecondActivity,并且Log中没有再次输出关于SecondActivity的信息,说明SecondActivity并没有重新创建。由此可以得出结论:以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。



下面开始验证第二个特点:以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中


重新安装AndroidTaskTest应用,点击MainActivity中的按钮,开启SecondActivity,在SecondActivity中点击按钮,开启ThirdActivity。可以看到有如下Log输出:


执行adb shell dumpsys activity命令,有以下输出:
  
TaskRecord{412a95b8 #15 A com.jg.zhang.androidtasktest}
 Run #3: ActivityRecord{411f9318 com.jg.zhang.androidtasktest/.ThirdActivity}
TaskRecord{41353a68 #16 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{413537c8 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{412a95b8 #15 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{4123a0c8 com.jg.zhang.androidtasktest/.MainActivity}


SecondActivity所在的任务为16,被SecondActivity启动的ThirdActivity所在的任务为15,这就说明 以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被他开启的任何activity都会运行在其他任务中



下面开始验证第三个特点:被singleInstance模式的Activity开启的其他activity,能够在新的任务中启动,但不一定开启新的任务,也可能在已有的一个任务中开启


有上面对第二个特点的验证可以看到,被SecondActivity启动的ThirdActivity并没有运行在一个新开启的任务中,而是和MainActivity运行在了同一个已有的任务中,那么在什么情况下ThirdActivity才会启动一个新的任务呢?

现在对程序的清单文件做以下修改,为ThirdActivity增加一个属性taskAffinity:

[html]  view plain  copy
  1. <activity android:name="com.jg.zhang.androidtasktest.ThirdActivity"  
  2.     android:taskAffinity="com.jg.zhang.androidtasktest.second"/>  

重新安装AndroidTaskTest应用,执行和上一步中同样的操作:点击MainActivity中的按钮,开启SecondActivity,在SecondActivity中点击按钮,开启ThirdActivity。可以看到有如下输出:



执行adb shell dumpsys activity命令,有以下输出:

TaskRecord{413551b0  #20 A com.jg.zhang.androidtasktest.second}
 Run #3: ActivityRecord{412de9c0 com.jg.zhang.androidtasktest/.ThirdActivity}
TaskRecord{4134b268  #19 A com.jg.zhang.androidtasktest}
Run #2: ActivityRecord{412a36a0 com.jg.zhang.androidtasktest/.SecondActivity}
TaskRecord{413131e8  #18 A com.jg.zhang.androidtasktest}
Run #1: ActivityRecord{41271e10 com.jg.zhang.androidtasktest/.MainActivity}

可见,被SecondActivity启动的ThirdActivity启动在了一个新的任务中,即在启动ThirdActivity时创建了一个新任务。这就说明 被singleInstance模式的Activity A在开启另一activity B时,能够开启一个新任务,但是是不是真的开启新任务,还要受其他条件的限制,这个条件是:当前系统中是不是已经有了一个activity B的taskAffinity属性指定的任务。

其实这种行为和singleTask启动时的情况相同。在Activity的启动模式设置为singleTask时,启动时系统会为它加上FLAG_ACTIVITY_NEW_TASK标志,而被singleInstance模式的Activity开启的activity,启动时系统也会为它加上FLAG_ACTIVITY_NEW_TASK标志,所以他们启动时的情况是相同的,上面再验证singleTask时已经阐述过,现在重新说明一下:

由于ThirdActivity是被启动模式为singleInstance类型的Activity(即SecondActivity)启动的,framework会为它它加上FLAG_ACTIVITY_NEW_TASK标志,这时  framework会检索是否已经存在了一个affinity为com.jg.zhang.androidtasktest.second(即ThirdActivity的taskAffinity属性)的任务,

  • 如果存在这样的一个任务,则检查在这个任务中是否已经有了一个ThirdActivity的实例,
  1. 如果已经存在一个ThirdActivity的实例,则会重用这个任务和任务中的ThirdActivity实例,将这个任务调到前台,清除位于ThirdActivity上面的所有Activity,显示ThirdActivity,并调用ThirdActivity的onNewIntent()。
  2. 如果不存在一个ThirdActivity的实例,会在这个任务中创建ThirdActivity的实例,并调用onCreate()方法
  • 如果不存在这样的一个任务,会创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将ThirdActivity启动到这个新的任务中

如果ThirdActivity不设置taskAffinity,即ThirdActivity和MainActivity的taskAffinity相同,都为应用的包名,那么ThirdActivity是不会开启一个新任务的,framework中的判定过程如下:

  1. 在SecondActivity启动ThirdActivity时,因为SecondActivity是singleInstance的,所以设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK
  2. 然后获得ThirdActivity的taskAffinity,即为包名com.jg.zhang.androidtasktest
  3. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest的任务,这个任务是存在的,就是MainActivity所在的任务,这个任务是在启动MainActivity时开启的
  4.  既然已经存在这个任务,就检索在这个任务中是否存在一个ThirdActivity的实例,发现不存在
  5.  在这个已有的任务中启动一个SecondActivity的实例

为了作一个清楚的比较,列出ThirdActivity的taskAffinity属性设为com.jg.zhang.androidtasktest.second时的启动过程

  1. 在SecondActivity启动ThirdActivity时,因为SecondActivity是singleInstance的,那么设定ThirdActivity的启动标志为FLAG_ACTIVITY_NEW_TASK
  2. 然后获得ThirdActivity的taskAffinity,即为com.jg.zhang.androidtasktest.second
  3. 检查是否已经存在一个affinity为com.jg.zhang.androidtasktest.second的任务,这个任务是不存在的
  4.  创建一个新的affinity为com.jg.zhang.androidtasktest.second的任务,并且将ThirdActivity启动到这个新的任务
到此singleInstance也介绍完了。

对 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP 的实践 编写DEMO对Intent Flag实践:FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP 阅读详情

相关推荐

Andorid之taskAffinity 和 FLAG_ACTIVITY_NEW_TASK

taskAffinity 和 FLAG_ACTIVITY_NEW_TASK都是和activity启动时是否需要新建一个task有关,我们分四种情况看一下这两个标志对启动activity的影响:(前提:从MainActivity中启动ActivityA) 1)、两个标志都不设置 2)、有FLAG_ACTIVITY_NEW_TASK   3)、无FLAG_ACTIVITY_NEW_TASK有ta

码莎拉蒂 3176

浅谈android:taskAffinity属性及问题解决

浅谈android:taskAffinity属性及问题解决

seevc的专栏 7270

Android Studio中的Activity:启动模式与任务栈

本文聚焦Android开发中Activity的**启动模式(Launch Mode)和四种启动模式(standard、singleTop、singleTask、singleInstance)的底层逻辑任务栈如何管理Activity实例(LIFO原则)不同启动模式的典型应用场景通过代码实战验证启动效果(含adb命令查看任务栈)本文从“奶茶店排队”的生活案例切入,逐步拆解启动模式与任务栈的关系,结合代码实战演示效果,最后总结应用场景和常见问题。任务栈(Task Stack)

移动开发前沿的博客 1336

Android中Activity四种启动模式和taskAffinity属性详解

启动主Activity A,接着A开启B,B开启C,这时栈中有三个Activity,并且这三个Activity默认在同一个任务(task)中,当用户按返回时,弹出C,栈中只剩A和B,再按返回键,弹出B,栈中只剩A,再继续按返回键,弹出A,任务被移除。

w1142203475的博客 3289

Android中Activity四种启动模式和taskAffinity属性详解-Rong

# Android中Activity四种启动模式和taskAffinity属性详解 #

linux操作系统使用以太网连接ubuntu,设置固定ipv4

首先,您需要确定要配置的网络接口。可以使用ifconfig -a或ip addr命令来列出当前系统上的所有网络接口及其相关信息。找到您要配置的网络接口名称,例如eth0、ens33或enp0s3;保存并关闭文件后,您需要重启网络服务以使更改生效。在linux操作系统中,要使用交换机连接以太网可以直接使用交互界面;如果你的操作系统没有交互界面,你可以使用命令方式配置;点击ipv4,方式为手动,可以设置ip和子网了;开头,后接网络接口名称。第三步:点击有线最右边圆形控件;第二步:点击有线设置;

2301_79311842的博客 2404

Activity 的启动模式

关于 Activity 的启动模式的讲解到这里就结束了,最后再强调一遍,launchMode 和 Intent flag 的各种组合效果还是有点过于难理解了,使得我很难全面地进行描述,再加上似乎还存在版本兼容性问题,使用起来就更加麻烦了,所以我觉得开发者只需要有个大致的印象即可,当真正要使用的时候再来亲自测试验证效果就好,不必强行记忆。

2501_90498209的博客 2008

从零到一:基于Blazor和Elsa Workflows的工作流系统开发实战

本文详细介绍了如何利用Blazor和Elsa Workflows构建高效的工作流系统,从技术选型到生产环境部署,涵盖动态工作流建模、Blazor前端集成及性能优化等核心内容。通过实战案例,展示如何实现订单处理系统的自动化与灵活性,提升企业流程效率。

weixin_26875109的博客 428

android 让dialog保持在最前_Android 面试进阶指南 —— 唠唠任务栈,返回栈和启动模式...

Android 面试进阶指南目录唠唠任务栈,返回栈和启动模式唠唠 Activity 的生命周期扒一扒 Context为什么不能使用 Application Context 显示 Dialog?OOM 可以被 try catch 吗?Activity.finish() 之后十秒才回调 onDestroy ? 本文永久更新地址: https://blog.youkuaiyun.com/sunluyao_/art...

weixin_33756596的博客 870

nvidia-container-toolkit 国内镜像源安装

nvidia-container-toolkit 国内镜像源安装

湫然的博客 1万+

复现HumanMAC(HumanMAC: Masked Motion Completion for Human Motion Prediction)

Human3.6M和HumanEva-I都放到data目录下(记得将data_multi_modal-20250320T022408Z-001.zip和humaneva_multi_modal-20250320T022412Z-001.zip解压缩并且分别重命名为data_multi_modal和humaneva_multi_modal)。生成的gif存放在inference目录下zero_shot_<INDEX>下的out目录下。新建一个data目录,将

qq_52643100的博客 831

明修“栈“道——越过Android启动栈陷阱

本文从一例业务中遇到的问题出发,以FLAG_ACTIVITY_NEW_TASK这一flag作为切入点,带大家探究Activity启动前的一项重要的工作——栈校验。文中列举一系列业务中可能遇到的异常状况,详细描述了使用FLAG_ACTIVITY_NEW_TASK时可能遇到的“坑”,并从源码中探究其根源。只有合理使用flag、launchMode,才能避免因为栈机制的特殊性,导致一系列与预期不符的启动问题。

vivo互联网技术 1065

Coze智能体开发:搭建一个解析 PDF 文件的工作流

大模型本身并不具备文件读取和解析的能力,无法直接处理文件字节流。对于支持工具调用(Function calling)的模型,扣子提供插件功能来帮助大模型调用外部工具 API,拓展大模型的能力边界。扣子插件商店提供海量官方插件和第三方插件,例如链接读取插件可以将链接对应的文件内容解析为纯文本传递给大模型。

王国平的博客 2813

Android 验证Activity启动模式

launchMode standard singleTop singleTask singleInstance Intent标记 FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TASK 和 FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TOP FLAG_ACTIVITY_SINGLE_TOP

xiangxiongfly 1390

五步通关!宝塔Linux部署ASP.NET Core的魔法秘籍大公开!

摘要: 本文提供ASP.NET Core应用五步部署攻略,涵盖从环境配置到线上运行的完整流程: 1️⃣ 环境准备:安装宝塔面板、ASP.NET Core运行时及开放端口; 2️⃣ 应用发布:通过dotnet publish生成独立部署包并上传服务器; 3️⃣ Nginx配置:设置反向代理将域名请求转发至应用端口; 4️⃣ 进程守护:使用Supervisor确保应用持续运行; 5️⃣ HTTPS加密:通过宝塔快速申请SSL证书并解决验证路径冲突。 关键点: 强调权限设置(755)、端口开放及反向代理细节; 提

java专栏 969

激光雷达点云异常现象详解:鬼影、高反膨胀与拖点

现象核心外观主要成因核心硬件对策核心算法对策鬼影位置错误,出现"复制"的物体镜面反射/光学元件间杂散光物理光阑、抗反射涂层多帧时序一致性检验、反射率逻辑判断膨胀尺寸变大,轮廓呈毛刺状信号饱和、像素/通道串扰可变增益控制、随机编码序列强度阈值滤波、几何轮廓拟合(剪裁)拖点边缘粘连,出现"阶梯"或"连线"光斑跨立边缘、双回波融合小光斑光学设计、多回波支持邻域深度突变检测、波形脉宽分析这些异常现象是当前激光雷达感知中的核心难点,通常需要硬件物理特性优化与上层感知算法。

Fun_LiDAR的博客 303

Activity启动模式与任务栈(Task)、TaskAffinity应用场景【转】

通过上一篇文件的分析,我们对Activity的启动模式有了比较清晰的了解后,本篇我们将继续对Activity启动模式的相关参数和任务栈分析,接下来我们就继续上一篇的问题,如何通过taskAffinity属性在同一个应用中创建多个任务栈进行探究。

iblue007的博客 1172

osg点云加载与渲染

osg点云加载与渲染

cangqiongxiaoye的博客 821

Activity启动模式一

众所周知,Activity有4种启动模式,分别是:Standard、SingleTop、SingleTask和SingleInstance,它们控制了被启动Activity的启动行为。本文将通过具体案例,详细分析这几种模式的差异和使用场景,方便日后查阅。 在展开具体分析之前,我们首先要了解下两个基础知识:Activity任务栈和android:taskAffinity属性。 基础知识 Activi...

tli600605的专栏 2366

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Activity启动模式与任务栈(Task)全面深入记录(下)

转载请注明出处(谢谢): http://blog.youkuaiyun.com/javazejian/article/details/52072131  通过上一篇文件的分析,我们对Activity的启动模式有了比较清晰的了解后,本篇我们将继续对Activity启动模式的相关参数和任务栈分析,接下来我们就继续上一篇的问题,如何通过taskAffinity属性在同一个应用中创建多个任务栈进行探究。任务栈之ta

zejian的博客 1万+

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

带你深入理解Activity启动模式(LaunchMode)

我们知道默认情况下,当我们多次启动同一个activity时,系统会创建多个实例并把他们一个个放入任务栈,当我们按back键,这些activity又会一个个退出。在讲activity的launchmode之前,我们有必要了解下“任务栈(Task Stack)”这个概念。在Android中是使用任务(Task)来管理Activity的,任务就是存放在栈里面的Activity的集合,这个栈就是称为任务栈。

sd_zhuzhipeng的专栏 1万+

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Activity四种启动模式和task、process、Application之间的区别

在android应用开发中,打造良好的用户体验是非常重要的。而在用户体验中,界面的引导和跳转是值得深入研究的重要内容。在开发中,与界面跳转联系比较紧密的概念是Task(任务)和Back Stack(回退栈)。activity的启动模式会影响Task和Back Stack的状态,进而影响用户体验。除了启动模式之外,Intent类中定义的一些标志(以FLAG_ACTIVITY_开头)也会影响Task和

yyyysjhappy的专栏 5118

Activity基础 - 任务栈

什么是任务栈? 前面的文章我们多次提到过一个叫任务栈的东西,那什么是任务栈呢?首先我们先搞清什么是任务,任务的定义: 任务是在执行特定任务的时候与用户交互的一些activity 那什么是任务栈呢? android任务栈简单了解 android任务栈又称为Task,它是一个栈结构,具有后进先出的特性,用于存放我们的Activity组件。 我们每次打开一个新的Activity或者退出当前Acti...

白小飞的博客 1728
上一篇: Activity启动模式与任务栈(Task)全面深入记录(下)
下一篇: Java多线程学习
LP_stone
博客等级 码龄12年 0粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值