匯總:記錄線上線下崩潰的問題 (持續記錄)

前言

記錄線上崩潰問題,持續記錄...

  • DigitsKeyListener導致7.x.x以下手機崩潰

設備列表

修復方式:

修復方式
  • Fragment 構造方法私有化導致崩潰

Fragment導致崩潰
修復方式

原因分析

FragmentActivity#onSaveInstanceState
protected void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    markFragmentsCreated();
    mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
    // 進行Fragment狀態保存 拿到Parcelable對象
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
         // 存入
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    // 省略部分保存Key Value
}
FragmentActivity#onCreate(@Nullable Bundle savedInstanceState)
 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
    if (savedInstanceState != null) {
        Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
        // 恢復部分數據
        mFragments.restoreSaveState(p);
    }
    // 省略部分代碼....
}
FragmentManagerImpl#
void restoreSaveState(Parcelable state) {
    for (FragmentState fs : fms.mActive) {
        if (fs != null) {
            // 創建一個新的Fragment對象
            Fragment f = fs.instantiate(mHost.getContext().getClassLoader(),
                    getFragmentFactory());
            f.mFragmentManager = this;
            if (DEBUG) Log.v(TAG, "restoreSaveState: active (" + f.mWho + "): " + f);
            mActive.put(f.mWho, f);
            // Now that the fragment is instantiated (or came from being
            // retained above), clear mInstance in case we end up re-restoring
            // from this FragmentState again.
            fs.mInstance = null;
        }
    }
}
FragmentState#instantiate()
public Fragment instantiate(@NonNull ClassLoader classLoader,
        @NonNull FragmentFactory factory) {
    if (mInstance == null) {
        if (mArguments != null) {
            mArguments.setClassLoader(classLoader);
        }
        // FragmentFactory 所謂的工廠進行創建對象
        mInstance = factory.instantiate(classLoader, mClassName);
        mInstance.setArguments(mArguments);
        if (mSavedFragmentState != null) {
            mSavedFragmentState.setClassLoader(classLoader);
            mInstance.mSavedFragmentState = mSavedFragmentState;
        } else {
            mInstance.mSavedFragmentState = new Bundle();
        }
    }
    return mInstance;
}
FragmentFactory創建對象
@NonNull
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
    try {
        // 反射創建對象
        Class<? extends Fragment> cls = loadFragmentClass(classLoader, className);
        return cls.getConstructor().newInstance();
    } catch (java.lang.InstantiationException e) {
        throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (IllegalAccessException e) {
        throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                + ": make sure class name exists, is public, and has an"
                + " empty constructor that is public", e);
    } catch (NoSuchMethodException e) {
        throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                + ": could not find Fragment constructor", e);
    } catch (InvocationTargetException e) {
        throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                + ": calling Fragment constructor caused an exception", e);
    }
}

因此在Fragment中是不允許存在私有構造方法的否則導致在恢復狀態的時候Fragment創建失敗。

部分機型導致TimeOut異常

java.util.concurrent.TimeoutException: android.content.res.AssetManager$AssetInputStream.finalize() timed out after 10 seconds
 at android.content.res.AssetManager$AssetInputStream.close(AssetManager.java:812)
 at android.content.res.AssetManager$AssetInputStream.finalize(AssetManager.java:845)
 at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:202)
 at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:185)
 at java.lang.Thread.run(Thread.java:833)

解決辦法以及原理分析

private void fixTimeOutException() {
    if (BuildConfig.DEBUG || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        return; 
    }
    try {
        final Class<?> clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
        final Method method = clazz.getSuperclass().getDeclaredMethod("stop");
        method.setAccessible(true);
        final Field field = clazz.getDeclaredField("INSTANCE");
        field.setAccessible(true);
        method.invoke(field.get(null));
        UmengUtils.UmEvent(mApplication.getApplicationContext(), "fix_oppo_success");
    } catch (Exception e1) {
        try {
            UmengUtils.UmEvent(mApplication.getApplicationContext(), "fix_oppo_failed");
        } catch (Exception e2) {
            Logger.e(TAG, e1.getMessage());
            Logger.e(TAG, e2.getMessage());
        }
    }
}
  • Android7.0 - 9.0 啟動Activity時,導致的ActivityRecord not found異常。

java.lang.IllegalArgumentException: reportSizeConfigurations: ActivityRecord not found for: Token{dd2d7e2 ActivityRecord{b2548ad u0 com.ehai/cn.jpush.android.service.JNotifyActivity t-1 f}}
    at android.os.Parcel.createException(Parcel.java:1957)
    at android.os.Parcel.readException(Parcel.java:1921)
    at android.os.Parcel.readException(Parcel.java:1871)
    at android.app.IActivityManager$Stub$Proxy.reportSizeConfigurations(IActivityManager.java:8737)
    at android.app.ActivityThread.reportSizeConfigurations(ActivityThread.java:3670)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3625)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:86)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2199)
    at android.os.Handler.dispatchMessage(Handler.java:112)
    at android.os.Looper.loop(Looper.java:216)
    at android.app.ActivityThread.main(ActivityThread.java:7625)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)
Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.server.am.ActivityManagerService.reportSizeConfigurations(Landroid/os/IBinder;[I[I[I)V(libmapleservices.so:5919109)
    at android.app.IActivityManager$Stub.onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z(libmapleframework.so:4765897)
    at com.android.server.am.ActivityManagerService.onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z(libmapleservices.so:5931469)
    at com.android.server.am.HwActivityManagerService.onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z(libmaplehwServices.so:3387765)
    at android.os.Binder.execTransact(IJJI)Z(libmapleframework.so:6090741)
android.os.RemoteException: Remote stack trace:
    at com.android.server.am.ActivityManagerService.reportSizeConfigurations(Landroid/os/IBinder;[I[I[I)V(libmapleservices.so:5919109)
    at android.app.IActivityManager$Stub.onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z(libmapleframework.so:4765897)
    at com.android.server.am.ActivityManagerService.onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z(libmapleservices.so:5931469)
    at com.android.server.am.HwActivityManagerService.onTransact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z(libmaplehwServices.so:3387765)
    at android.os.Binder.execTransact(IJJI)Z(libmapleframework.so:6090741)

究其原因

先找一下拋出異常的具體位置。我們知道啟動Activity時,會通過IPC binder機制,通知AMS我要啟動Activity了,最終會告知ActivityThread這個類進行回調Activity的各個生命周期的處理。

@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
        PendingTransactionActions pendingActions, Intent customIntent) {
    //... 省略部分代碼
    final Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        // 嗯嗯.... 這里就是問題入口拉
        reportSizeConfigurations(r);
        if (!r.activity.mFinished && pendingActions != null) {
            pendingActions.setOldState(r.state);
            pendingActions.setRestoreInstanceState(true);
            pendingActions.setCallOnPostCreate(true);
        }
    } else {
        // If there was an error, for any reason, tell the activity manager to stop us.
        try {
            ActivityTaskManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
    return a;
}

看下reportSizeConfigurations()這個方法。

private void reportSizeConfigurations(ActivityClientRecord r) {
    // 這里通知了ActivityTaskManagerService去獲取ActivityRecord
    try {
        ActivityTaskManager.getService().reportSizeConfigurations(r.token,
                horizontal.copyKeys(), vertical.copyKeys(), smallest.copyKeys());
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}
ActivityTaskManagerService#reportSizeConfigurations()
@Override
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
        int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Report configuration: " + token + " "
            + horizontalSizeConfiguration + " " + verticalSizeConfigurations);
    synchronized (mGlobalLock) {
        ActivityRecord record = ActivityRecord.isInStackLocked(token);
        // 若ActivityRecord 為 null, 則throw出我們Bugly所記錄的異常。
        if (record == null) {
            throw new IllegalArgumentException("reportSizeConfigurations: ActivityRecord not "
                    + "found for: " + token);
        }
        record.setSizeConfigurations(horizontalSizeConfiguration, verticalSizeConfigurations,
                smallestSizeConfigurations);
    }
}

static ActivityRecord isInStackLocked(IBinder token) {
    // 根據Token來獲取ActivityRecord對象 
    final ActivityRecord r = ActivityRecord.forTokenLocked(token);
    return (r != null) ? r.getActivityStack().isInStackLocked(r) : null;
}

private static @Nullable ActivityRecord tokenToActivityRecordLocked(Token token) {
    if (token == null) {
        return null;
    }
    // 從弱引用中獲取ActivityRecord
    ActivityRecord r = token.weakActivity.get();
    if (r == null || r.getActivityStack() == null) {
        return null;
    }
    return r;
}

// Token 繼承了 Stub  我們知道Stub 是跨進程通信的,并且實現了IBinder接口。
static class Token extends IApplicationToken.Stub {
    // 弱引用 ActivityRecord
    private final WeakReference<ActivityRecord> weakActivity;
    private final String name;
    Token(ActivityRecord activity, Intent intent) {
        weakActivity = new WeakReference<>(activity);
        name = intent.getComponent().flattenToShortString();
    }
    private static @Nullable ActivityRecord tokenToActivityRecordLocked(Token token) {
        if (token == null) {
            return null;
        }
        ActivityRecord r = token.weakActivity.get();
        if (r == null || r.getActivityStack() == null) {
            return null;
        }
        return r;
    }
}

因此產生這個問題的原因就是再執行Activity啟動的時候,根據Token 去獲取ActivityRecord對象,但是這個對象為空,所以會拋出該異常。暫時該問題還不知道源頭怎么解決,所以我的處理方式就是直接將reportSizeConfigurations()這個方法通過動態代理進行異常捕捉。

解決辦法

private void fixReportSizeConfigurationsException() {
    if (Build.VERSION.SDK_INT != Build.VERSION_CODES.P) {
        return;
    }
    try {
        // 反射拿到ActivityManager
        Field activityManager = ActivityManager.class.getDeclaredField("IActivityManagerSingleton");
        activityManager.setAccessible(true);
        Object iActivityManagerSingleton = activityManager.get(null);
        if (iActivityManagerSingleton == null) {
            return;
        }
        Class<?> singletonCls = iActivityManagerSingleton.getClass().getSuperclass();
        if (singletonCls == null){
            return;
        }
        Field instance = singletonCls.getDeclaredField("mInstance");
        instance.setAccessible(true);
        Object iActivityManager = instance.get(iActivityManagerSingleton);
        @SuppressLint("PrivateApi")
        Class<?> iActivityManagerCls = Class.forName("android.app.IActivityManager");
        Class<?>[] classes = {iActivityManagerCls};
        Object iActivityManageProxy = Proxy.newProxyInstance(
                iActivityManagerCls.getClassLoader(),
                classes,
                new IActivityManagerProxy(iActivityManager));
        instance.set(iActivityManagerSingleton, iActivityManageProxy);
    } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}
/**
 * 動態代理處理 try catch  ATMS #ActivityTaskManager#reportSizeConfigurations()方法
 */
private static class IActivityManagerProxy implements InvocationHandler {
    private Object mIActivityManager;
    public IActivityManagerProxy(Object iActivityManager) {
        mIActivityManager = iActivityManager;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("reportSizeConfigurations".equals(method.getName())) {
            try {
                Log.w(TAG, "reportSizeConfigurations invoke execute ");
                return method.invoke(mIActivityManager, args);
            } catch (Exception e) {
                Log.w(TAG, "reportSizeConfigurations exception: " + e.getMessage());
                return null;
            }
        }
        return method.invoke(mIActivityManager, args);
    }
}
  • AbstractMethodError 抽象方法錯誤

AbstractMethodError

這個錯誤問題相對來講遇到的不多,此次制造這個原因的問題解決了,原因是我們公司的項目集成了Mob的ShareSDK,在2020年11月18日15時他們服務端進行了錯誤的配置項導致Android端ShareSDK拋出該問題。雖然問題不是我們的,但為了防范該問題的產生,還是有必要了解一下。

AbstractMethodError

首先AbstractMethodError,顧名思義是應用在調用抽象方法的時候會拋出該異常,并且這個錯誤只會在代碼運行的時候進行觸發的,原因在于某些類在實現父類的抽象方法的時候,在最近一次編譯之后該父類的抽象方法又發生了改變。所以,出現這種情況的原因就是代碼版本不兼容導致的問題。
針對Android開發,最容易產生該錯誤的問題原因則跟混淆有關。在編譯期代碼被混淆過后,有些方法不應該被混淆,在運行時,找不到該方法,所以導致拋出AbstractMethodError。

  • OkHttp - Unexpected TLS version: NONE

公司項目代碼OkHttp版本升級之前是3.6.0,由于提出對請求埋點的需要,需要記錄一次請求各個環節所消耗的時間。OkHttp 對外提供了EventListener接口,不過在3.6.0版本沒有該api,所以升級到3.9.0. 在3.9.0版本,發現有時會崩潰,異常日志。該異常在于成功從連接緩存池中找到一個健康的連接通路后,進行TLS連接時拋出的異常,對于OkHttp的Tls握手連接細節請點擊這里
解決辦法升級到3.14.9。日志請點擊這里

  • OkHttp 網絡請求加密導致得Unexpected Char問題

bug_screen_capture.png

公司得項目最近在更改加解密方式,所以使用Okhttp攔截器對請求參數,請求body進行新一輪加解密方式。
老加密方式為,將body與url中得參數進行aes加密,由于aes是對稱加密,根據key與iv就能進行加密解密,因此相對于RSA非對稱加密來說并不安全。這里簡單說下。我們在使用aes加密得過程中,加密后又對其值進行了base64編碼,這時候問題就來了。

String s =Base64.encodeToString("abc".getBytes(StandardCharsets.UTF_8),Base64.DEFAULT);

Base64.DEFAULT的屬性生成的最終編碼會帶上換行,只不過當字符串長度大于76會加上換行符,這時候比如編碼的是Json字符串,則會改變Json結構。由于我這邊將Json先用aes加密之后base64編碼有換行符,然后放到請求頭中導致后臺拿不到header對應的value值所以爆出的問題。解決辦法:使用NO_WARP屬性。

String s =Base64.encodeToString("abc".getBytes(StandardCharsets.UTF_8),Base64.NO_WRAP);
  • 共用RecyclerPool導致viewHolder views must not be attached when created.錯誤

protected RecyclerView orderListRecycler;
private final RecyclerView.RecycledViewPool mCachePool = new RecycledViewPool();
{
    orderListRecycler.setRecycledViewPool(cachePool);
}
錯誤日志
2021-02-01 15:47:03.691 7636-7636/com.xxx E/EHiError: java.lang.IllegalStateException: ViewHolder views must not be attached when created. Ensure that you are not passing 'true' to the attachToRoot parameter of LayoutInflater.inflate(..., boolean attachToRoot)
        at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7080)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6235)
        at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288)
        at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:345)
        at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361)
        at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:368)
        at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:399)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7560)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
解決辦法BaseQuickAdapter
@Override
protected BaseViewHolder onCreateDefViewHolder(ViewGroup parent, int viewType) {
    if (viewType == ITEM_VIEW_TYPE_LOAD_MORE) {
        // 原因在于這里邊 回調用多次LoadMoreView 但是這個LoadMoreView 實例只有一個,之前添加過了,若再次創建ViewHolder就會報錯
        BaseViewHolder holder = createBaseViewHolder(mLoadMoreView.getLoadMoreView());
        // 禁用回收機制
        holder.setIsRecyclable(false);
        return holder;
    }
    final SelfOrderItemWidget widget = new SelfOrderItemWidget(mContext);
    widget.setOrderItemBtnClick(mOrderItemBtnClick);
    widget.setEntranceType(mEntranceType);
    return createBaseViewHolder(widget);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容