项目中有VOIP的需求,类似微信语音聊天的界面。这种类型的界面通常会要求可以最小化并保持运行状态。最小化自然想到悬浮窗,悬浮窗的实现以及注意事项不在本文讨论范围,本文讨论的是怎样让Activity不可见但又不finish呢?
通过简单的搜索可以比较轻松的看到网上提供的实现方式:将Activity设置成singleTask,搭配taskAffinity属性使用,目的是让此Activity独立运行在一个task中。
说一下Task,我们知道Android中每个Activity运行在一个task中,由ActivityManager中的RunningTaskInfo统一管理。通常情况下,一个Application中的所有Activity都默认是同一个task,除非在AndroidManifest.xml文件中对某Activity设置了taskAffinity,可以使此Activity运行在指定名称的task中。
由于接手项目时已经是这种实现方式,大部分设备上也没有问题,达到了我们想要的效果,具体实现细节如下:
1、在AndroidManifest.xml中将ActivityVoip设置成singleTask,设置android:taskAffinity为"com.xx.xx.xx"。
2、加入属性android:excludeFromRecents,让该task不在最近任务列表中显示。此处强调一下,如果刚启动ActivityVoip,马上呼出最近任务界面还是能看到两个task,一个是ActivityVoip界面所属的task,一个是APP所在的默认task,这是没问题的,官方有说明。
3、点击ActivityVoip中的最小化按钮,生成悬浮窗的同时moveTaskToBack(true)将当前task退到后台。悬浮窗的权限注意控制。
<activity
android:name="ui.ActivityVoip"
android:exported="false"
android:launchMode="singleTask"
android:excludeFromRecents="true"
android:taskAffinity="com.xx.xx.xx"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
以上,似乎完美实现了这个功能,可突然测试说华为手机最小化通话页面或者直接讲应用退到后台后,再冷启动任意一个其他app都会导致通话断开,悬浮窗消失!
此处吐槽一下国产系统特别是华为的UI,乱改什么Android,留下那么多坑。经过测试,华为在launcher冷启动一个app时会将设置为android:excludeFromRecents="true"并且设置了taskAffinity的task干掉。。。
最佳的方案当然是Activity和Voip状态分离,Activity只负责展示,即使被无情kill掉,再启动时还是可以正确展示相关信息。
可如今想要最简单的方式解决这个问题要怎么办呢?经过几个小时的研究和尝试,将singleTask改为singleInstance,去掉taskAffinity和excludeFromRecents属性,也可以实现Activity独立运行的目的,但怎么可能没有其他问题呢?T_T 。当启动ActivityVoip后,退到后台,再从最近任务回到通话界面,按返回键页面finish()后并没有返回上个页面,而是退到了系统Launcher上,给人的感觉像是app崩掉了,然而事实是并没有。原因也比较简单,ActivityManager最顶端的task并不是主APP的task,解决方案自然想到finish()通话界面前召唤主app的task回来。代码如下:
private void moveAppToFront() {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> recentList = am.getRunningTasks(30);
recentList.remove(0); //去掉当前 Activity
for (ActivityManager.RunningTaskInfo info : recentList) {
if (info.topActivity.getPackageName().equals(getPackageName())) {
am.moveTaskToFront(info.id, 0);
return;
}
}
}
还有没有坑有待观察。。。。
更新:
经过测试SingleInstance也不能解决问题。原因在于singleInstance启动模式在不同机型表现不同,有的机型会在最近任务中显示两个task,有的则显示一个。显示一个自然没问题,显示两个又要考虑在清单文件中添加android:excludeFromRecents="true"。然而虽然添加这个属性后,华为手机冷启动其他应用不会destroy这个页面,但是最近任务中整个app的task都不见了,这也是singleInstance和singleTask的区别之一。
接下来还是研究一下微信的实现吧,首先通过 adb命令打印出微信的Activity堆栈信息,打印之前最好清楚最近任务,免得不好找。
上面两张图是打印出来的最小化微信语音界面前后的堆栈信息,发现微信VideoActivity也就是通话界面的taskId和聊天页面的是一样的,也就是说微信并没有采用SingleTask和SingleInstance中任何一种启动方式!!!而且最小化实际上就是finish了VideoActivity,所以。。也跟我想到的第一种解决方案一样,最保险的做法就是持久化voip的状态,Activity只是用来显示。唉,前人挖坑后人跳~