什么?按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方法中被销毁的,日志如下:

24-2-4

按照如上的思路,逐步类推,添加日志,查看调用栈,我们最终追溯到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跟进的,大家可以测试看看。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值