前言
记录线上崩溃问题,持续记录...
-
DigitsKeyListener导致7.x.x以下手机崩溃
修复方式:
-
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 抽象方法错误
这个错误问题相对来讲遇到的不多,此次制造这个原因的问题解决了,原因是我们公司的项目集成了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问题
公司得项目最近在更改加解密方式,所以使用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);
}