解开Android应用程序组件Activity的"singleTask"之谜(3)

简介:

   回到前面的startActivityUncheckedLocked函数中,这里的变量top就为null了,于是执行下面的else语句:

 
 
  1.    if (top != null) {   
  2. ......   
  3.    } else {   
  4. // A special case: we need to   
  5. // start the activity because it is not currently   
  6. // running, and the caller has asked to clear the   
  7. // current task to have this activity at the top.   
  8. addingToTask = true;   
  9. // Now pretend like this activity is being started   
  10. // by the top of its task, so it is put in the   
  11. // right place.   
  12. sourceRecord = taskTop;   
  13.    }   

于是,变量addingToTask值就为true了,同时将变量sourceRecord的值设置为taskTop,即前面调用findTaskLocked函数的返回值,这里,它就是表示MainActivity了。

        继续往下看,下面这个if语句:

 
 
  1.    if (r.packageName != null) {   
  2. // If the activity being launched is the same as the one currently   
  3.        // at the top, then we need to check if it should only be launched   
  4. // once.   
  5. ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);   
  6. if (top != null && r.resultTo == null) {   
  7.     if (top.realActivity.equals(r.realActivity)) {   
  8.             if (top.app != null && top.app.thread != null) {   
  9.             ......   
  10.         }   
  11.     }   
  12. }   
  13.    
  14.    } else {   
  15. ......   
  16.    }   

       它是例行性地检查当前任务顶端的Activity,是否是即将启动的Activity的实例,如果是否的话,在某些情况下,它什么也不做,就结束这个函数调用了。这里,当前任务顶端的Activity为MainActivity,它不是SubActivity实例,于是继续往下执行:

 
 
  1.    boolean newTask = false;   
  2.    
  3.    // Should this be considered a new task?   
  4.    if (r.resultTo == null && !addingToTask   
  5. && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {   
  6. ......   
  7.    
  8.    } else if (sourceRecord != null) {   
  9. if (!addingToTask &&   
  10.     (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {   
  11.     ......   
  12. else if (!addingToTask &&   
  13.         (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {   
  14.     ......   
  15. }   
  16. // An existing activity is starting this new activity, so we want   
  17. // to keep the new one in the same task as the one that is starting   
  18. // it.   
  19. r.task = sourceRecord.task;   
  20.           
  21. ......   
  22.    
  23.    } else {   
  24.        ......   
  25.    }   

        这里首先将newTask变量初始化为false,表示不要在新的任务中启动这个SubActivity。由于前面的已经把addingToTask设置为true,因此,这里会执行中间的else if语句,即这里会把r.task设置为sourceRecord.task,即把SubActivity放在MainActivity所在的任务中启动。

        最后,就是调用startActivityLocked函数继续进行启动Activity的操作了。后面的操作这里就不跟下去了,有兴趣的读者可以参考两篇文章Android应用程序启动过程源代码分析Android应用程序内部启动Activity过程(startActivity)的源代码分析

        到这里,思路就理清了,虽然SubActivity的launchMode被设置为"singleTask"模式,但是它并不像官方文档描述的一样:The system creates a new task and instantiates the activity at the root of the new task,而是在跟它有相同taskAffinity的任务中启动,并且位于这个任务的堆栈顶端,于是,前面那个图中,就会出现一个带着"singleTask"标签的箭头指向一个任务堆栈顶端的Activity Y了。
        那么,我们有没有办法让一个"singleTask"的Activity在新的任务中启动呢?答案是肯定的。从上面的代码分析中,只要我们能够进入函数startActivityUncheckedLocked的这个if语句中:

 
 
  1.  if (r.resultTo == null && !addingToTask   
  2.        && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {   
  3. // todo: should do better management of integers.   
  4.        mService.mCurTask++;   
  5.        if (mService.mCurTask <= 0) {   
  6.             mService.mCurTask = 1;   
  7.        }   
  8.        r.task = new TaskRecord(mService.mCurTask, r.info, intent,   
  9.                   (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);   
  10.        if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r   
  11.                   + " in new task " + r.task);   
  12.         newTask = true;   
  13.         if (mMainStack) {   
  14.               mService.addRecentTaskLocked(r.task);   
  15.         }   
  16.  }   

 

       那么,这个即将要启动的Activity就会在新的任务中启动了。进入这个if语句需要满足三个条件,r.resultTo为null,launchFlags的Intent.FLAG_ACTIVITY_NEW_TASK位为1,并且addingToTask值为false。从上面的分析中可以看到,当即将要启动的Activity的launchMode为"singleTask",并且调用startActivity时不要求返回要启动的Activity的执行结果时,前面两个条件可以满足,要满足第三个条件,只要当前系统不存在affinity属性值等于即将要启动的Activity的taskAffinity属性值的任务就可以了。

        我们可以稍微修改一下上面的AndroidManifest.xml配置文件来做一下这个实验:

 
 
  1. <?xml version="1.0" encoding="utf-8"?>     
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"     
  3.     package="shy.luo.task"     
  4.     android:versionCode="1"     
  5.     android:versionName="1.0">     
  6.     <application android:icon="@drawable/icon" android:label="@string/app_name">     
  7.         <activity android:name=".MainActivity"     
  8.                   android:label="@string/app_name"   
  9.                   android:taskAffinity="shy.luo.task.main.activity">     
  10.             <intent-filter>     
  11.                 <action android:name="android.intent.action.MAIN" />     
  12.                 <category android:name="android.intent.category.LAUNCHER" />     
  13.             </intent-filter>     
  14.         </activity>     
  15.         <activity android:name=".SubActivity"     
  16.                   android:label="@string/sub_activity"   
  17.                   android:launchMode="singleTask"   
  18.                   android:taskAffinity="shy.luo.task.sub.activity">     
  19.             <intent-filter>     
  20.                 <action android:name="shy.luo.task.subactivity"/>     
  21.                 <category android:name="android.intent.category.DEFAULT"/>     
  22.             </intent-filter>     
  23.         </activity>     
  24.     </application>     
  25. </manifest>     

 

        注意,这里我们设置MainActivity的taskAffinity属性值为"shy.luo.task.main.activity",设置SubActivity的taskAffinity属性值为"shy.luo.task.sub.activity"。重新编译一下程序,在模拟器上把这个应用程序再次跑起来,用“adb shell dumpsys activity”命令再来查看一下系统运行的的任务,就会看到:

 
 
  1. Running activities (most recent first):   
  2.     TaskRecord{4069c020 #4 A shy.luo.task.sub.activity}   
  3.       Run #2: HistoryRecord{40725040 shy.luo.task/.SubActivity}   
  4.     TaskRecord{40695220 #3 A shy.luo.task.main.activity}   
  5.       Run #1: HistoryRecord{406b26b8 shy.luo.task/.MainActivity}   
  6.     TaskRecord{40599c90 #2 A com.android.launcher}   
  7.       Run #0: HistoryRecord{40646628 com.android.launcher/com.android.launcher2.Launcher}   

        这里就可以看到,SubActivity和MainActivity就分别运行在不同的任务中了。

        至此,我们总结一下,设置了"singleTask"启动模式的Activity的特点:

        1. 设置了"singleTask"启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了"singleTask"启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。

        2. 如果设置了"singleTask"启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。

        看来,要解开Activity的"singleTask"之谜,还是要自力更生啊,不过,如果我们仔细阅读官方文档,在http://developer.android.com/guide/topics/manifest/activity-element.html中,有这样的描述:

        As shown in the table above, standard is the default mode and is appropriate for most types of activities. SingleTop is also a common and useful launch mode for many types of activities. The other modes — singleTask and singleInstance —are not appropriate for most applications, since they result in an interaction model that is likely to be unfamiliar to users and is very different from most other applications.
        Regardless of the launch mode that you choose, make sure to test the usability of the activity during launch and when navigating back to it from other activities and tasks using the BACK key.

       这样看,官方文档也没有坑我们呢,它告诫我们:make sure to test the usability of the activity during launch







本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966140,如需转载请自行联系原作者
目录
相关文章
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
1658 4
|
存储 Android开发
如何查看Flutter应用在Android设备上已被撤销的权限?
如何查看Flutter应用在Android设备上已被撤销的权限?
783 64
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
存储 Android开发 数据安全/隐私保护
如何在Android设备上撤销Flutter应用程序的所有权限?
如何在Android设备上撤销Flutter应用程序的所有权限?
1049 64
|
缓存 Android开发 开发者
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
2795 62
|
开发工具 Android开发 开发者
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
1116 61
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
1148 156
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
1037 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
564 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
449 14

热门文章

最新文章