按Home键时SingleInstance Activity销毁了??

前段时间,突然有朋友询问,自己写的SingleInstance Activity在按home键的时候被销毁了,刚听到这个问题的时候,我直觉怀疑是Activity在onPause或者onStop中发生了Crash导致闪退了,但是安装apk查看现象,没有发现异常日志,这究竟是怎么回事呢?编写测试Demo来详细探索下

Demo代码说明

Demo日志很简单,包含MainActivity和SingleInstanceActivity两个页面,在MainActivity中的TextView点击事件中启动SingleInstanceActivity,在SingleInstanceActivity中的TextView点击事件中调用moveTaskToBack(true)切回后台,随后在MainActivity界面按Home键返回桌面,就可以看到SingleInstanceActivity被销毁了,示例代码如下所示:

 // MainActivity.kt
 class MainActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContent {
             MyApplicationTheme {
                 // A surface container using the 'background' color from the theme
                 Surface(
                     modifier = Modifier
                         .fillMaxSize()
                         .clickable { onBtnClick() },
                     color = MaterialTheme.colorScheme.background
                 ) {
                     Greeting("Android")
                 }
             }
         }
     }
     fun onBtnClick() {
          startActivity(Intent(this,                                  SingleInstanceActivity::class.java))
     }
 }
 ​
 @Composable
 fun Greeting(name: String, modifier: Modifier = Modifier) {
     Text(
         text = "Hello $name!",
         modifier = modifier
     )
 }
 ​
 @Preview(showBackground = true)
 @Composable
 fun GreetingPreview() {
     MyApplicationTheme {
         Greeting("Android")
     }
 }

 // SingleInstanceActivity.java
 public class SingleInstanceActivity extends ComponentActivity {
     private static final String TAG = "SingleInstanceActivity";
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     Log.d(TAG,"SingleInstanceActivity onCreate method called",new Exception());
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_single_instance);
     findViewById(R.id.move_back).setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             moveTaskToBack(true);
         }
     });
   }
 ​
   @Override
   protected void onDestroy() {
     Log.d(TAG,"SingleInstanceActivity onDestroy method called",new Exception());
     super.onDestroy();
   }
 }

 <!-- AndroidManifest.xml文件中application节点的内容-->
 <application
     android:allowBackup="true"
     android:dataExtractionRules="@xml/data_extraction_rules"
     android:fullBackupContent="@xml/backup_rules"
     android:icon="@mipmap/ic_launcher"
     android:label="@string/app_name"
     android:roundIcon="@mipmap/ic_launcher_round"
     android:supportsRtl="true"
     android:theme="@style/Theme.MyApplication"
     tools:targetApi="31">
     <activity
         android:name=".SingleInstanceActivity"
         android:launchMode="singleInstance"
         android:exported="true" />
     <activity
         android:name=".MainActivity"
         android:exported="true"
         android:label="@string/app_name"
         android:theme="@style/Theme.MyApplication">
         <intent-filter>
             <action android:name="android.intent.action.MAIN" />
 ​
             <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
     </activity>
 </application>

调用栈回溯

即然SingleInstanceActivity被销毁了,那么我们只需要在Activity生命周期中添加日志,来看下onDestroyed函数是怎么驱动调用的即可,从Activity生命周期可知,在Framework中框架通过ClientLifecycleManager类来管理Activity的生命周期变化,在该类的scheduleTransaction函数中,Activity的每一种生命周期类型均被包装成一个ClientTransaction来处理,在该函数中添加日志,打印调用栈,即可确定是那个地方销毁了SingleInstanceActivity,添加日志的代码如下:

24-2-3

随后编译framework.jar并push到设备上,查看日志,可以看到SingleInstanceActivity是在Task类的removeActivities方法中被销毁的,日志如下:

在这里插入图片描述

按照如上的思路,逐步类推,添加日志,查看调用栈,我们最终追溯到ActivityThread的handleResumeActivity,在该函数的最后,添加的IdlerHandler里面会执行RecentTasks的onActivityIdle方法,在该函数的调用流程里,会判断当前resume的Activity是不是桌面,是的话在HiddenTask不为空的情况下,就会执行removeUnreachableHiddenTasks的逻辑,销毁SingleInstanceActivity(这里的代码分支为android-13.0.0_r31)。

完整的正向调用流程如下图所示:

SingleInstance Task release process 1

remove-hidden-task机制

前文中我们已经跟踪到Activity销毁的调用流程,那么为什么要销毁SingleInstanceActivity呢?我们继续看前文中的日志,可以看出Activity销毁的原因是:remove-hidden-task。

24-2-6

那么这个remove-hidden-task到底是用来干嘛的呢?我们来看下代码提交信息:

24-2-1

从代码提交说明不难看出,这里的意思是:当我们向最近任务列表中添加一个任务时,会移除已不可达/未激活的Task,这里我们的SingleInstanceActivity所在的Task被判定为不可达/未激活状态,所以被这套机制移除了。

不可达/未激活的Task

那么为什么SingleInstanceActivity被认为是不可达的呢?我们进一步追踪代码,可以看到RencentTasks.removeUnreachableHiddenTasks移除的是mHiddenTasks中的任务,代码如下:

24-2-7

这样我们就只需要搞清楚什么样的Task会被加入mHiddenTasks中即可,mHiddenTasks.add的调用代码如下所示:

24-2-8

24-2-9

从上述代码可知,在removeForAddTask中通过findRemoveIndexForAddTask来查找当给定Task添加到最近任务列表时,需要被移除的Task,在findRemoveIndexForAddTask中最典型的一种场景就是当两个Task的TaskAffinity相同时,当后来的Task被添加到最近任务列表时,前一个Task会被销毁,这也就意味着在SingleInstanceActivity按Home键,MainActivity也会被销毁,经过实践,确实是这样。

解决方案

前文中已探讨了remove-hidden-task的运行机制,那么解决方案也就很简单了,给SingleInstanceActivity添加独立的TaskAffinity即可(注意:此时SingleInstanceActivity会显示在最近任务中,如果不想显示,请指定android:excludeFromRecents=“true”)。

影响范围

经排查,Google Pixel记性从Android 12开始支持该特性,针对国内定制厂商而言,大多数应该是在Android 13跟进的,大家可以测试看看。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值