Activity中onResume方法觸發(fā)的ActivityRecord not found異常分析

問題

最近我在處理線上奔潰日志的時(shí)候發(fā)現(xiàn)一個由Activity中onResume方法觸發(fā)的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)

可見當(dāng)發(fā)生ActivityRecord not found時(shí)夫否,isTopOfTask()方法里拋出來了一個IllegalArgumentException異常局待。引起ActivityRecord not found的原因有多種燎猛,這里不太好針對ActivityRecord not found進(jìn)行處理重绷,所以我想怎么來屏蔽這個異常昭卓,讓程序不奔潰候醒。

分析

首先我們來看看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系統(tǒng)上的onResume方法里才會調(diào)用這個isTopOfTask方法敌土,所以這個問題只有這幾個版本有返干。

網(wǎng)上有一種做法是直接try-catch onResume方法血淌,然后通過反射將mCalled修改為true。由于這個問題不好復(fù)現(xiàn)躺坟,我沒有去驗(yàn)證這個方法的可行性瞳氓,但是這種簡單粗暴的方法看上去其實(shí)是有問題的,因?yàn)?code>isTopOfTask()拋出異常后店诗,mActivityTransitionState.onResume()方法里的邏輯就沒有執(zhí)行捧弃。

我們分析下isTopOfTask()方法擦囊,發(fā)現(xiàn)這個方法try-catch了一個RemoteException異常瞬场,當(dāng)發(fā)生這個異常的時(shí)候?qū)⒎祷豧alse贯被,后面的流程能正常執(zhí)行彤灶,不會影響Activity,于是我們想這里可不可以將IllegalArgumentException異常也try-catch一下吶诵姜,但是發(fā)現(xiàn)這是一個私有方法棚唆,無法重寫瑟俭,所以只能繼續(xù)往下追蹤摆寄。
來看看ActivityManagerNative.getDefault()的實(shí)現(xiàn):
在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();
    }
    ...
}

繼續(xù)看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;
                }
            };
    ...
}

我們發(fā)現(xiàn)getDefault()方法返回的是一個IActivityManager對象矩肩,所以實(shí)際上執(zhí)行的是IActivityManager里的isTopOfTask()方法黍檩。而IActivityManager是一個單例刽酱,在7.0和7.1上這個單例對象在ActivityManagerNative類里面的瞧捌,而從8.0開始放到了ActivityManager里姐呐。而且通過上面的final IActivityManager am = IActivityManager.Stub.asInterface(b);這句還知道了IActivityManager其實(shí)是一個接口(懶得繼續(xù)翻源碼了^_^)头谜。
繼續(xù)看這個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;
        }
    }
}

到這里我們就可以想到使用反射和動態(tài)代理就可以對IActivityManager里的方法進(jìn)行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來進(jìn)行反射儒老,需要這樣處理:
// 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()))
    }
}

第四步:用動態(tài)代理替換原來的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
            }

            // 第三步:使用動態(tài)代理替換原來的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()方法里調(diào)用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住了船侧,這樣當(dāng)再次發(fā)生ActivityRecord not found問題時(shí)就不會再奔潰拋出IllegalArgumentException了吉挣。而且從日志可以看出深滚,以后我們想要處理其他方法時(shí)也可以這樣處理旭从。

后話

當(dāng)然漾肮,我們這里其實(shí)是沒有從本質(zhì)上解決這個ActivityRecord not found問題的路星,這樣處理了即便這里不奔潰吊圾,也沒法保證其他地方不會因?yàn)?code>ActivityRecord not found而不奔潰。這里更多的只是提供一種解決問題的思路蝇裤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芝囤,一起剝皮案震驚了整個濱河市贩毕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖谆棺,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兰珍,死亡現(xiàn)場離奇詭異唠摹,居然都是意外死亡盗温,警方通過查閱死者的電腦和手機(jī)双霍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門丘逸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事唆途∥屡猓” “怎么了痹屹?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵培廓,是天一觀的道長肩钠。 經(jīng)常有香客問我,道長央星,這世上最難降的妖魔是什么霞怀? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任毙石,我火速辦了婚禮滤灯,結(jié)果婚禮上豫尽,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好贯吓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布懈凹。 她就那樣靜靜地躺著,像睡著了一般悄谐。 火紅的嫁衣襯著肌膚如雪介评。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天爬舰,我揣著相機(jī)與錄音们陆,去河邊找鬼。 笑死情屹,一個胖子當(dāng)著我的面吹牛坪仇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播垃你,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼椅文,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惜颇?” 一聲冷哼從身側(cè)響起雾袱,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎官还,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毒坛,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡望伦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煎殷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屯伞。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖豪直,靈堂內(nèi)的尸體忽然破棺而出劣摇,到底是詐尸還是另有隱情,我是刑警寧澤弓乙,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布末融,位于F島的核電站钧惧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勾习。R本人自食惡果不足惜浓瞪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望巧婶。 院中可真熱鬧乾颁,春花似錦、人聲如沸艺栈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湿右。三九已至诅妹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诅需,已是汗流浹背漾唉。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留堰塌,地道東北人赵刑。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像场刑,于是被迫代替她去往敵國和親般此。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345