杀不掉的知乎 - 聊一聊 Android 的多任务

7,983 阅读4分钟

不知道大家平时使用 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。

这也是为什么知乎首次启动是正常的,用着用着才可能出现这种情况。

参考资料

Recents Screen

Activity 清单配置