汇总:记录线上线下崩溃的问题 (持续记录)

前言

记录线上崩溃问题,持续记录...

  • 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阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容