前言
記錄線上崩潰問題,持續記錄...
-
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);
}