杀不掉的知乎 - 聊一聊 Android 的多任务
不知道大家平时使用 APP 的时候,有没有碰到过下面这种情况:
这是我在最近使用知乎的时候出现的,可以看到在任务列表里面看不到知乎,但很明显它还在运行中。你现在打开知乎看大概率是正常的,原因后面会提到。
通常要杀掉一个 APP 的进程,最直接的方法的就是在任务列表里把对应的任务划掉。于是,保活黑科技又增加了一种新思路:如果在任务列表里把应用隐藏掉
,那用户不就杀不掉了?
事先说明一下,本文并不是教大家怎么做保活,仅探讨这是怎么做的,并借此聊一聊 Android 的多任务。而且对于这样的体验,我是真的被恶心到了。
怎么杀掉它?
任务列表并不是杀掉 APP 的唯一途径,我们先看看要怎样才能杀掉这种应用。
adb
我们可以通过 adb shell ps 查看系统当前运行的所有进程,和预期的一样,在里面找到了还在运行中的知乎:
然后可以使用 adb shell am force-stop com.zhihu.android 强制杀掉知乎进程。
系统设置
对于普通用户,也还是有办法的。进入系统设置,在应用设置里找到知乎,点击强行停止:
但无论是哪种方法,都是比较麻烦的,真心希望大家不要这样搞。
excludeFromRecents
其实,Android 是允许我们在任务列表里隐藏的,而且很简单,只要在清单中声明了 android:excludeFromRecents="true" 就好了。
我们新建一个项目试一下,把 MainActivity 加上这个配置:
<activity
android:name=".MainActivity"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
这样就能达到不在任务列表显示的效果。
但仅仅如此还达不到知乎的效果,因为首次打开它是有在任务列表显示的。下面引入多任务的另一个概念。
taskAffinity
之所以叫任务列表,是因为这里显示的是当前在执行的任务,而不是当前运行的应用。只不过在默认情况下,一个应用就对应一个任务。
每个任务会有一个 TaskAffinity,可以把它理解为任务名,默认情况下 TaskAffinity 是应用的包名。我们可以用 taskAffinity 属性给 Activity 配置不同的任务名,让一个 APP 拥有多个任务。
无论是 excludeFromRecents 还是 taskAffinity,它们只对栈内的根 Activity 生效,其实它们作用的是任务栈 Task,而不是 Activity。
举个例子,我们增加一个 SecondActivity,清单配置如下:
<activity
android:name=".MainActivity"
android:label="Task 1"
android:taskAffinity="com.nanbox.task1">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:label="Task 2"
android:launchMode="singleTask"
android:taskAffinity="com.nanbox.task2" />
当两个 Activity 都开启后,任务列表就会出现两个任务。
taskAffinity 经常会和 singleTask 搭配使用,当启动一个 singleTask 的 Activity 时,系统会先比对当前的和新的 taskAffinity,如果不一致就会在一个新的 Task 里启动 Activity。
另外,不仅一个应用可以有多个任务,不同应用也可以属于同一个任务,任务是可以跨进程的。这种使用场景应该比较少,这里就不展开讲了。
骚操作
基于上面的多任务,假如我们一个应用有两个任务,一个可见一个不可见,用户只能在任务列表里杀掉可见的任务,不可见的任务还可以继续跑,那岂不是可以一定程度上保活?
我们来试一下,还是上面的代码,不过这次让 Task 2 在任务列表中不可见:
<activity
android:name=".MainActivity"
android:label="Task 1"
android:taskAffinity="com.nanbox.task1">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:excludeFromRecents="true"
android:label="Task 2"
android:launchMode="singleTask"
android:taskAffinity="com.nanbox.task2" />
我们把启动过的 SecondActivity 存起来,在 MainActivity 中判断,如果 SecondActivity 已经存在就直接启动它,以便恢复到应用上一次的状态:
Activity activity = ActivityProvider.getActivity();
if (activity != null) {
Intent intent = new Intent(this, activity.getClass());
startActivity(intent);
finishAndRemoveTask();
}
于是就有了类似前面知乎的效果:
GIF 重复播放不太好体现效果,感兴趣的可以自己跑一下看看。
首次启动可以在任务列表中看到 Task1,当启动 Task2 并结束 Task1 之后,任务列表就变成空了,但点击桌面应用可以恢复到正在运行的 Task2。
这也是为什么知乎首次启动是正常的,用着用着才可能出现这种情况。