源码分析实战: reportSizeConfigurations 导致应用 Force Stop

简介

这周本来在要发一篇关于 Service 的源码分析文章的,Service 虽然不是最复杂的AMS组件,但是从startService, bindService流程,再到Service的绑定、优先级的变化还有Service的ANR,这些东西一起分析完并画图还是挺耗时的,所以目前为止我只写到了大概一半的样子,这个周末恐怕是写不完了

我发现我的专栏里虽然有很多分析类的文章,但是实战类一篇都没有。刚好这周解决了一个关于Activity的问题,觉着挺有意义的,与大家分享。这个问题最终的成因与应用在主线程执行耗时操作、系统杀进程策略息息相关,中间解决问题涉及的知识点比较常见,算是比较经典的一类问题吧

网上用Google搜索了一圈,无论是github还是stackoverflow,大家都对这个问题一脸懵逼的状态,这个问题的答案只此一家,哈哈~

这个问题的表现是应用先出现卡顿,用户在最近任务中上滑将应用移除,一段时间后应用出现了FC (Force Stop),这个问题在原生系统也会出现,以下是在MIUI的表现:

查看大图

代码视角

reportSizeConfigurations这个方法是在ActivityThread的handleLaunchActivity方法中,执行完Activity.onCreate之后便会被调用到,作用是用来binder call到AMS更新一些资源参数的

ActivityThread.java

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    Activity a = performLaunchActivity(r, customIntent);   // 此处执行Activity.onCreate方法

    if (a != null) {
        reportSizeConfigurations(r);    // 此处将会binder call到AMS调用reportSizeConfigurations方法
        ...
    }
}

随后,APP通过AMP binder call 到AMS

ActivityThread.java

private void reportSizeConfigurations(ActivityClientRecord r) {
    ...
    try {
        ActivityManagerNative.getDefault().reportSizeConfigurations(r.token,
                horizontal.copyKeys(), vertical.copyKeys(), smallest.copyKeys());
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}

在system_server进程,AMS会先从缓存中取出ActivityRecord,如果没能取出就会抛出异常

ActivityManagerService.java

@Override
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
        int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Report configuration: " + token + " "
            + horizontalSizeConfiguration + " " + verticalSizeConfigurations);
    synchronized (this) {
        // 通过token取出ActivityRecord, 这块的细节我们后面再看
        ActivityRecord record = ActivityRecord.isInStackLocked(token);
        if (record == null) {
            // 随后会检查取出的record是否为空,如果为空那么AMS变回抛出异常
            throw new IllegalArgumentException("reportSizeConfigurations: ActivityRecord not "
                    + "found for: " + token);
        }
        ...
    }
}

因为reportSizeConfigrations这个接口是被binder call到system_server的,system_server的接收端,那么我们可以去看Binder类的execTransact,这个方法是从Binder C++层调用过来的,细节这篇文章不多讲

Binder.java

// Entry point from android_util_Binder.cpp's onTransact
private boolean execTransact(int code, long dataObj, long replyObj,
        int flags) {
    ...
    try {
        // 执行reportSizeConfigrations方法
        res = onTransact(this, code, data, reply, flags);
    } catch (RemoteException|RuntimeException e) {
        ...
        if ((flags & FLAG_ONEWAY) != 0) {
            ...
        } else {
            // 如果binder call的接口是非ONE_WAY的,那么onTransact方法的异常会被捕捉到
            // ActivityThread binder call到AMS的方式是非ONE_WAY的,这个需要知晓
            reply.setDataPosition(0);
            reply.writeException(e);
        }
        res = true;
    }
    ...
    return res;
}

异常被捕捉到后,会被转换code写到rely中

Parcel.java

public final void writeException(Exception e) {
    int code = 0;
    ...
    if (e instanceof IllegalArgumentException) {
        // 在此处转换成code
        code = EX_ILLEGAL_ARGUMENT;
    }
    ...
    writeInt(code);
    ...
}

reply返回到客户端之后,会调用readException,读取是否有相关的异常

ActivityManagerNative.java

@Override
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
        int[] verticalSizeConfigurations, int[] smallestSizeConfigurations)
        throws RemoteException {
    ...
    mRemote.transact(REPORT_SIZE_CONFIGURATIONS, data, reply, 0);
    reply.readException();
    ...
}

public final void readException(int code, String msg) {
    switch (code) {
        ...
        case EX_ILLEGAL_ARGUMENT:
            // 最终在此处抛出异常,应用发生FC
            throw new IllegalArgumentException(msg);
        ...
    }
}

梳理一下应用发生FC的步骤:

  1. 应用端调用AMP.reportSizeConfigurations方法,因为是非ONE_WAY的binder call,应用此时阻塞等待AMS回复
  2. AMS通过应用端传输过来的token查询ActivityRecord
  3. 查询出的ActivityRecord为空
  4. AMS抛出异常,但是被捕捉了,异常被转换成code被写到reply中
top Created with Sketch.