07-11 22:36:10.067 8431 8431 E AndroidRuntime: FATAL EXCEPTION: main
07-11 22:36:10.067 8431 8431 E AndroidRuntime: Process: com.xiaomi.micolauncher, PID: 8431
07-11 22:36:10.067 8431 8431 E AndroidRuntime: java.util.ConcurrentModificationException
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.util.ArrayList.writeObject(ArrayList.java:766)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1037)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1552)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1488)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1234)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1604)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1565)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1488)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1234)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:354)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeSerializable(Parcel.java:1709)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeValue(Parcel.java:1662)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeArrayMapInternal(Parcel.java:875)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1579)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Bundle.writeToParcel(Bundle.java:1233)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeBundle(Parcel.java:915)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.support.v4.app.d.writeToParcel(FragmentState.java:124)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeTypedObject(Parcel.java:1516)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeTypedArray(Parcel.java:1497)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.support.v4.app.c.writeToParcel(FragmentManager.java:639)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeParcelable(Parcel.java:1683)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeValue(Parcel.java:1589)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeArrayMapInternal(Parcel.java:875)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1579)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Bundle.writeToParcel(Bundle.java:1233)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.app.IActivityManager$Stub$Proxy.activityStopped(IActivityManager.java:3985)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:144)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:873)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Looper.loop(Looper.java:209)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6702)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
java.util.ConcurrentModificationException
java.util.ArrayList.writeObject(ArrayList.java:766)
ArrayList在读的同时集合改变了,具体可以看ArrayList源码
日志没有太多,只有如上一段最关键的日志。首先既然是并发处理了ArrayList,那么一定有一个地方writeObject,另一个地方put或者remove了。仔细分析日志,大概筛选出writeObject的地方:
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Bundle.writeToParcel(Bundle.java:1233)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.app.IActivityManager S t u b Stub StubProxy.activityStopped(IActivityManager.java:3985)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:144)
日志显示最早问题开始于PendingTransactionActions,然后到了IActivityManager,它的实现类是ActivityManagerService.java,看下它的activityStopped方法:
@Override
public final void activityStopped(IBinder token, Bundle icicle,
PersistableBundle persistentState, CharSequence description) {
if (DEBUG_ALL) Slog.v(TAG, "Activity stopped: token=" + token);
// Refuse possible leaked file descriptors
if (icicle != null && icicle.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Bundle");
}
final long origId = Binder.clearCallingIdentity();
synchronized (this) {
final ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r != null) {
r.activityStoppedLocked(icicle, persistentState, description);
}
}
trimApplications();
Binder.restoreCallingIdentity(origId);
}
如果是activityStopped内部某个方法出错,那么如上的runtime日志应该具体把哪个方法记录下来,但是没有。说明
IActivityManager代理中出了问题,这就看不到代码了。
我们再顺着日志网上看,有这么一个关键日志:
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeBundle(Parcel.java:915)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.support.v4.app.d.writeToParcel(FragmentState.java:124)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeTypedObject(Parcel.java:1516)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.os.Parcel.writeTypedArray(Parcel.java:1497)
07-11 22:36:10.067 8431 8431 E AndroidRuntime: at android.support.v4.app.c.writeToParcel(FragmentManager.java:639)
我们看下FragmentState.writeToParcel
Bundle mSavedFragmentState;
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mClassName);
dest.writeInt(mIndex);
dest.writeInt(mFromLayout ? 1 : 0);
dest.writeInt(mFragmentId);
dest.writeInt(mContainerId);
dest.writeString(mTag);
dest.writeInt(mRetainInstance ? 1 : 0);
dest.writeInt(mDetached ? 1 : 0);
dest.writeBundle(mArguments);
dest.writeInt(mHidden ? 1 : 0);
dest.writeBundle(mSavedFragmentState);
}
writeBundle报错的,根据日志的代码行数是mArguments这个值变化了。
到这里日志给出的信息基本没啥再多的东西了。其中有一个线程操作了PendingTransactionActions,这个方法最终会对FragmentState.mSavedFragmentState做Parcelable。同时,有其他线程对FragmentState.mArguments进行了增加或者删除。
一、首先分析哪里调用了PendingTransactionActions,先看下PendingTransactionActions.java
public StopInfo getStopInfo() {
return mStopInfo;
}
public void setStopInfo(StopInfo stopInfo) {
mStopInfo = stopInfo;
}
public static class StopInfo implements Runnable {
@Override
public void run() {
// Tell activity manager we have been stopped.
try {
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + mActivity);
// TODO(lifecycler): Use interface callback instead of AMS.
ActivityManager.getService().activityStopped(
mActivity.token, mState, mPersistentState, mDescription);
......
}
}
StopInfo是个runnable,调用它只能通过get和set方法,看下哪里set和get的。经过一番查找,发现在ActivityThread.java
@Override
public void handleStopActivity(IBinder token, boolean show, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
......
final StopInfo stopInfo = new StopInfo();
stopInfo.setActivity(r);
stopInfo.setState(r.state);
stopInfo.setPersistentState(r.persistentState);
pendingActions.setStopInfo(stopInfo);
mSomeActivitiesChanged = true;
}
/**
* Schedule the call to tell the activity manager we have stopped. We don't do this
* immediately, because we want to have a chance for any other pending work (in particular
* memory trim requests) to complete before you tell the activity manager to proceed and allow
* us to go fully into the background.
*/
@Override
public void reportStop(PendingTransactionActions pendingActions) {
mH.post(pendingActions.getStopInfo());
}
ActivityTread.handleStopActivity时会创建StopInfo,随后会有类调用reportStop通过Handler执行。handleStopActivity这里就不深挖了,肯定是给activity分发stop状态。看下哪里调用了reportStop,代码在StopActivityItem.java
public class StopActivityItem extends ActivityLifecycleItem {
private static final String TAG = "StopActivityItem";
private boolean mShowWindow;
private int mConfigChanges;
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions,
true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@Override
public void postExecute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
client.reportStop(pendingActions);
}
@Override
public int getTargetState() {
return ON_STOP;
}
// ObjectPoolItem implementation
private StopActivityItem() {}
/** Obtain an instance initialized with provided params. */
public static StopActivityItem obtain(boolean showWindow, int configChanges) {
StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
if (instance == null) {
instance = new StopActivityItem();
}
instance.mShowWindow = showWindow;
instance.mConfigChanges = configChanges;
return instance;
}
这里有两个重要方法,execute和postExecute,ClientTransactionHandler是什么?ActivityThread extends ClientTransactionHandler,很明显,它可以是ActivityThread。
调用StopActivityItem的地方在TransactionExecutorHelper.java
/** Get the lifecycle state request to match the current state in the end of a transaction. */
public static ActivityLifecycleItem getLifecycleRequestForCurrentState(ActivityClientRecord r) {
final int prevState = r.getLifecycleState();
final ActivityLifecycleItem lifecycleItem;
switch (prevState) {
// TODO(lifecycler): Extend to support all possible states.
case ON_PAUSE:
lifecycleItem = PauseActivityItem.obtain();
break;
case ON_STOP:
lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(),
0 /* configChanges */);
break;
default:
lifecycleItem = ResumeActivityItem.obtain(false /* isForward */);
break;
}
return lifecycleItem;
}
调用TransactionExecutorHelper.getLifecycleRequestForCurrentState的地方在ActivityThread.java
/** Performs the activity relaunch locally vs. requesting from system-server. */
private void handleRelaunchActivityLocally(IBinder token) {
......
// Make sure to match the existing lifecycle state in the end of the transaction.
final ActivityLifecycleItem lifecycleRequest =
TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
// Schedule the transaction.
final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
transaction.addCallback(activityRelaunchItem);
transaction.setLifecycleStateRequest(lifecycleRequest);
executeTransaction(transaction);
}
调用handleRelaunchActivityLocally的地方在ActivityThread.handleRelaunchActivityLocally
/**
* Post a message to relaunch the activity. We do this instead of launching it immediately,
* because this will destroy the activity from which it was called and interfere with the
* lifecycle changes it was going through before. We need to make sure that we have finished
* handling current transaction item before relaunching the activity.
*/
void scheduleRelaunchActivity(IBinder token) {
sendMessage(H.RELAUNCH_ACTIVITY, token);
}
到这里就不往上看了,很明显了,当Activity被重新launcher的时,如果ActivityClientRecord.getLifecycleState是Stop状态会最终调用到日志中的IActivityManager.activityStopped,从而会触发Activity内的FragmentManger,然后触发FragmentState.writeToParcel。这样出问题的ArrayList的遍历读的地方逻辑梳理完了,当Activity处于Stop状态变更的时候,会在stop执行完调用IActivityManager.activityStopped,打印一些日志什么的吧。
二、我们看下异步会触发FragmentState.mArguments的地方
调用FragmentState.mArguments变动的地方,在FragmentState.java
public Fragment instantiate(FragmentHostCallback host, FragmentContainer container,
Fragment parent, FragmentManagerNonConfig childNonConfig) {
if (mInstance == null) {
final Context context = host.getContext();
if (mArguments != null) {
mArguments.setClassLoader(context.getClassLoader());
}
if (container != null) {
mInstance = container.instantiate(context, mClassName, mArguments);
} else {
mInstance = Fragment.instantiate(context, mClassName, mArguments);
}
......
}
}
调用instantiate地方在FragmentManager.FragmentManagerImpl.java
SparseArray<Fragment> mActive;
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
// Build the full list of active fragments, instantiating them from
// their saved state.
mActive = new SparseArray<>(fms.mActive.length);
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
FragmentManagerNonConfig childNonConfig = null;
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.put(f.mIndex, 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;
}
}
}
调用的地方restoreAllState地方是Fragment.java
void restoreChildFragmentState(@Nullable Bundle savedInstanceState, boolean provideNonConfig) {
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG);
if (p != null) {
if (mChildFragmentManager == null) {
instantiateChildFragmentManager();
}
mChildFragmentManager.restoreAllState(p, provideNonConfig ? mChildNonConfig : null);
mChildNonConfig = null;
mChildFragmentManager.dispatchCreate();
}
}
}
void performCreate(Bundle savedInstanceState) {
......
if (version < Build.VERSION_CODES.N) {
restoreChildFragmentState(savedInstanceState, false);
}
}
public void onCreate(@Nullable Bundle savedInstanceState) {
mCalled = true;
final Context context = getContext();
final int version = context != null ? context.getApplicationInfo().targetSdkVersion : 0;
if (version >= Build.VERSION_CODES.N) {
restoreChildFragmentState(savedInstanceState, true);
if (mChildFragmentManager != null
&& !mChildFragmentManager.isStateAtLeast(Fragment.CREATED)) {
mChildFragmentManager.dispatchCreate();
}
}
}
导致mArguments变动的地方有两个,一个是Fragment.onCreate,另一个是Fragment.performCreate。
结论:基本逻辑分析完了。AndroidRuntime的日志也没有更多了。触发此问题的时机:
1、Activity被重新launcher,同时Activity状态是Stop
2、Fragment到了onCreate状态,会恢复所有被FragmentManager管理的Fragment。也就是说在Activity处于Stop状态时,对Fragment栈进行了变动。
剩下的就是看AndroidRuntime上边的日志,确定同时发生场景1和2的地方。
经过仔细查找。最终发现是代码中往Fragment.setArgument中put了List。而这个List是put的引用对象,这个List后台会变动。也就低概率导致了这个问题。所以使用Bundler切记不要传递可变对象的引用。