Activity中onResume方法触发的ActivityRecord not found异常分析

问题

最近我在处理线上奔溃日志的时候发现一个由Activity中onResume方法触发的ActivityRecord not found异常,具体信息如下:

java.lang.RuntimeException: Unable to resume activity {com.xxx.xx/com.xxx.xxx.RegisterActivity}: java.lang.IllegalArgumentException
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4025)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4057)
    at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:51)
    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1960)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7097)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
Caused by: java.lang.IllegalArgumentException
    at android.os.Parcel.createException(Parcel.java:1970)
    at android.os.Parcel.readException(Parcel.java:1934)
    at android.os.Parcel.readException(Parcel.java:1884)
    at android.app.IActivityManager$Stub$Proxy.isTopOfTask(IActivityManager.java:7845)
    at android.app.Activity.isTopOfTask(Activity.java:6551)
    at android.app.Activity.onResume(Activity.java:1404)
    at androidx.fragment.app.FragmentActivity.onResume(ProGuard:1)
    at com.xxx.xxx.BaseMVPActivity.onResume(ProGuard:1)
    at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1416)
    at android.app.Activity.performResume(Activity.java:7585)
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4017)
    ... 11 more
Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.server.am.ActivityManagerService.isTopOfTask(ActivityManagerService.java:18293)
    at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2058)
    at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:4174)
    at android.os.Binder.execTransact(Binder.java:739)

可见当发生ActivityRecord not found时,isTopOfTask()方法里抛出来了一个IllegalArgumentException异常。引起ActivityRecord not found的原因有多种,这里不太好针对ActivityRecord not found进行处理,所以我想怎么来屏蔽这个异常,让程序不奔溃。

分析

首先我们来看看Activity的源码(API 24):

...
@CallSuper
protected void onResume() {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
    getApplication().dispatchActivityResumed(this);
    mActivityTransitionState.onResume(this, isTopOfTask());
    mCalled = true;
}
...
private boolean isTopOfTask() {
    if (mToken == null || mWindow == null) {
        return false;
    }
    try {
        return ActivityManagerNative.getDefault().isTopOfTask(getActivityToken());
    } catch (RemoteException e) {
        return false;
    }
}

注意,在7.0到9.0系统上的onResume方法里才会调用这个isTopOfTask方法,所以这个问题只有这几个版本有。

网上有一种做法是直接try-catch onResume方法,然后通过反射将mCalled修改为true。由于这个问题不好复现,我没有去验证这个方法的可行性,但是这种简单粗暴的方法看上去其实是有问题的,因为isTopOfTask()抛出异常后,mActivityTransitionState.onResume()方法里的逻辑就没有执行。

我们分析下isTopOfTask()方法,发现这个方法try-catch了一个RemoteException异常,当发生这个异常的时候将返回false,后面的流程能正常执行,不会影响Activity,于是我们想这里可不可以将IllegalArgumentException异常也try-catch一下呐,但是发现这是一个私有方法,无法重写,所以只能继续往下追踪。
来看看ActivityManagerNative.getDefault()的实现:
在API 24、25上:

public abstract class ActivityManagerNative {
    ...
    static public IActivityManager getDefault() {
        return gDefault.get();
    }
    ...
    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
    ...
}

在API 26+上:

public abstract class ActivityManagerNative {
    ...
    static public IActivityManager getDefault() {
        return ActivityManager.getService();
    }
    ...
}

继续看ActivityManager.getService()

public class ActivityManager {
    ...
    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }
    ...
    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };
    ...
}

我们发现getDefault()方法返回的是一个IActivityManager对象,所以实际上执行的是IActivityManager里的isTopOfTask()方法。而IActivityManager是一个单例,在7.0和7.1上这个单例对象在ActivityManagerNative类里面的,而从8.0开始放到了ActivityManager里。而且通过上面的final IActivityManager am = IActivityManager.Stub.asInterface(b);这句还知道了IActivityManager其实是一个接口(懒得继续翻源码了^_^)。
继续看这个Singleton

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

到这里我们就可以想到使用反射和动态代理就可以对IActivityManager里的方法进行Hook,然后就可以处理这个IllegalArgumentException异常了。

处理方法

第一步:通过反射拿到Singleton对象:

// Singleton是一个隐藏类,无法直接访问,所以这里通过Class.forName来加载,备用
val singletonCls = Class.forName("android.util.Singleton")

在API 24、25上:

private fun getIActivityManagerSingletonInstanceN(singletonCls: Class<*>): Any? {
    // 找到ActivityManagerNative里的gDefault这个常量
    // 这里通过变量的类型对比找出gDefault这个常量,
    // 没有通过名字来查找,防止名字有变法
    // 这里也可以直接使用activityManagerNativeCls.getDeclaredField("gDefault")来获取
    val activityManagerNativeCls = Class.forName("android.app.ActivityManagerNative")
    var iActivityManagerSingleton: Any? = null
    for (field in activityManagerNativeCls.declaredFields) {
        if (field.type == singletonCls) {
            field.isAccessible = true
            iActivityManagerSingleton = field.get(null)
            break
        }
    }
    if (iActivityManagerSingleton == null) {
        Logger.w(TAG, "Not found IActivityManager singleton field in class ActivityManagerNative.")
    }

    return iActivityManagerSingleton
}

在API 26+上:

private fun getIActivityManagerSingletonInstance(singletonCls: Class<*>): Any? {
    // 找到ActivityManager里的IActivityManagerSingleton常量
    // 这里通过变量的类型对比找出IActivityManagerSingleton这个常量,
    // 没有通过名字来查找,防止名字有变法
    // 这里也可以直接使用ActivityManager::class.java.getDeclaredField("IActivityManagerSingleton")来获取
    var iActivityManagerSingleton: Any? = null
    for (field in ActivityManager::class.java.declaredFields) {
        if (field.type == singletonCls) {
            field.isAccessible = true
            iActivityManagerSingleton = field.get(null)
            break
        }
    }
    if (iActivityManagerSingleton == null) {
        Logger.w(TAG, "Not found IActivityManager singleton field in class ActivityManager.")
    }

    return iActivityManagerSingleton
}

第二步:拿到Singleton里的mInstance字段:

// 注意这里的iActivityManagerSingleton是Singleton的一个匿名子类
// 如果要用iActivityManagerSingleton来进行反射,需要这样处理:
// iActivityManagerSingleton::class.java.superclass.getDeclaredField("mInstance")
val instanceField = singletonCls.getDeclaredField("mInstance")
instanceField.isAccessible = true
val iActivityManager = instanceField.get(iActivityManagerSingleton)

第三步:把代理类写出来
(本方法的处理核心就在这里)

private class IActivityManagerProxy(private val instance: Any): InvocationHandler {
    override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any? {
        Logger.i(TAG, "invoke: ${method.name}()")
        if (method.name == "isTopOfTask") {
            return try {
                val result = method.invoke(instance, *(args ?: emptyArray())) as Boolean
                Logger.i(TAG, "isTopOfTask() invoke success")
                result
            } catch (e: Exception) {
                Logger.w(TAG, "isTopOfTask() invoke exception: $e")
                false
            }
        }

        return method.invoke(instance, *(args ?: emptyArray()))
    }
}

第四步:用动态代理替换原来的IActivityManager对象

val proxy = IActivityManagerProxy(iActivityManager)
val iActivityManagerCls = Class.forName("android.app.IActivityManager")
val iActivityManageProxy = Proxy.newProxyInstance(iActivityManagerCls.classLoader, arrayOf(iActivityManagerCls), proxy)
instanceField.set(iActivityManagerSingleton, iActivityManageProxy)

完整代码如下:

object IActivityManagerHook {
   private const val TAG = "IActivityManagerHook"

    @SuppressLint("PrivateApi")
    fun iActivityManagerHook() {
        if (Build.VERSION.SDK_INT < 24 || Build.VERSION.SDK_INT > 28) {
            return
        }

        Logger.i(TAG, "IActivityManager hook ...")
        try {
            val singletonCls = Class.forName("android.util.Singleton")

            // 第一步:通过反射拿到Singleton对象
            val iActivityManagerSingleton = if (Build.VERSION.SDK_INT <= 25) {
                getIActivityManagerSingletonInstanceN(singletonCls)
            } else {
                getIActivityManagerSingletonInstance(singletonCls)
            } ?: return

            // 第二步:找出Singleton里的mInstance变量
            val instanceField = singletonCls.getDeclaredField("mInstance")
            instanceField.isAccessible = true
            val iActivityManager = instanceField.get(iActivityManagerSingleton)
            if (iActivityManager == null) {
                Logger.w(TAG, "Not found IActivityManager instance.")
                return
            }

            // 第三步:使用动态代理替换原来的IActivityManager对象
            val proxy = IActivityManagerProxy(iActivityManager)
            val iActivityManagerCls = Class.forName("android.app.IActivityManager")
            val iActivityManageProxy = Proxy.newProxyInstance(iActivityManagerCls.classLoader, arrayOf(iActivityManagerCls), proxy)
            instanceField.set(iActivityManagerSingleton, iActivityManageProxy)
            Logger.i(TAG, "IActivityManager hook success.")
        } catch (e: Throwable) {
            Logger.w(TAG, "IActivityManager hook fail: $e")
        }
    }

    @SuppressLint("PrivateApi")
    private fun getIActivityManagerSingletonInstanceN(singletonCls: Class<*>): Any? {
        // 找到ActivityManagerNative里的gDefault这个常量
        // 这里通过变量的类型对比找出gDefault这个常量,
        // 没有通过名字来查找,防止名字有变法
        // 这里也可以直接使用activityManagerNativeCls.getDeclaredField("gDefault")来获取
        val activityManagerNativeCls = Class.forName("android.app.ActivityManagerNative")
        var iActivityManagerSingleton: Any? = null
        for (field in activityManagerNativeCls.declaredFields) {
            if (field.type == singletonCls) {
                field.isAccessible = true
                iActivityManagerSingleton = field.get(null)
                break
            }
        }
        if (iActivityManagerSingleton == null) {
            Logger.w(TAG, "Not found IActivityManager singleton field in class ActivityManagerNative.")
        }

        return iActivityManagerSingleton
    }

    private fun getIActivityManagerSingletonInstance(singletonCls: Class<*>): Any? {
        // 找到ActivityManager里的IActivityManagerSingleton常量
        // 这里通过变量的类型对比找出IActivityManagerSingleton这个常量,
        // 没有通过名字来查找,防止名字有变法
        // 这里也可以直接使用ActivityManager::class.java.getDeclaredField("IActivityManagerSingleton")来获取
        var iActivityManagerSingleton: Any? = null
        for (field in ActivityManager::class.java.declaredFields) {
            if (field.type == singletonCls) {
                field.isAccessible = true
                iActivityManagerSingleton = field.get(null)
                break
            }
        }
        if (iActivityManagerSingleton == null) {
            Logger.w(TAG, "Not found IActivityManager singleton field in class ActivityManager.")
        }

        return iActivityManagerSingleton
    }

    private class IActivityManagerProxy(private val instance: Any): InvocationHandler {
        override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any? {
            Logger.i(TAG, "invoke: ${method.name}()")
            if (method.name == "isTopOfTask") {
                return try {
                    val result = method.invoke(instance, *(args ?: emptyArray())) as Boolean
                    Logger.i(TAG, "isTopOfTask() invoke success")
                    result 
                } catch (e: Exception) {
                    Logger.w(TAG, "isTopOfTask() invoke exception: $e")
                    false
                }
            }

            return method.invoke(instance, *(args ?: emptyArray()))
        }
    }
}

最后在Application的onCreate()方法里调用IActivityManagerHook.iActivityManagerHook()即可完成全局的IActivityManagerHook了。
启动下看看:

1588489555.734 I/[2:iActivityManagerHook(IActivityManagerHook.kt:55):IActivityManagerHook]: IActivityManager hook ...
1588489555.757 I/[2:iActivityManagerHook(IActivityManagerHook.kt:90):IActivityManagerHook]: IActivityManager hook success.
1588489555.784 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: registerReceiver()
1588489555.796 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: getActivityDisplayId()
1588489555.876 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: getContentProvider()
1588489555.881 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: setTaskDescription()
1588489555.901 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: checkPermission()
1588489555.902 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: checkPermission()
1588489555.978 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: getActivityStackId()
1588489556.023 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: getActivityOptions()
1588489556.024 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: getActivityOptions()
1588489556.047 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: reportSizeConfigurations()
1588489556.049 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: isTopOfTask()
1588489556.049 I/[2:invoke(IActivityManagerHook.kt:102):IActivityManagerHook]: isTopOfTask() invoke success
1588489556.061 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: setRenderThread()
1588489556.068 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: activityResumed()
1588489556.255 I/[2:invoke(IActivityManagerHook.kt:98):IActivityManagerHook]: invoke: activityIdle()

完美,isTopOfTask成功Hook住了,这样当再次发生ActivityRecord not found问题时就不会再奔溃抛出IllegalArgumentException了。而且从日志可以看出,以后我们想要处理其他方法时也可以这样处理。

后话

当然,我们这里其实是没有从本质上解决这个ActivityRecord not found问题的,这样处理了即便这里不奔溃,也没法保证其他地方不会因为ActivityRecord not found而不奔溃。这里更多的只是提供一种解决问题的思路。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266