Potatotips26

フラグメント移動後に前のフラグメントのデータが次のフラグメントに表示されたので調査したはなし

Tipsというよりコードリーディングの結果の話
あくまで可能性の話
Created with Raphaël 2.1.2FragmentActivityFragmentActivityFragmentControllerFragmentControllerFragmentHostCallbackFragmentHostCallbackcreateControllermHost.getFragmentManagerImplemFragmentManager: FragmentManager
Created with Raphaël 2.1.2FragmentManagerFragmentManagerBackStackRecordBackStackRecordbeginTransactioncommitInternal

ここから長々とFragment(v4.support)とはState付きのViewであるという話をします。

# BackStackRecord.java
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");

mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
} else {
mIndex = -1;
}
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
# FragmentManager.java
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
}
}
}
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions();
}
};
enqueueActionで mPendingActionsに追加して、 Handler で post(mExecCommit) して実行
/**
* Only call from main thread!
*/
public boolean execPendingActions() {
if (mExecutingActions) {
throw new IllegalStateException("Recursive entry to executePendingTransactions");
}

if (Looper.myLooper() != mHost.getHandler().getLooper()) {
throw new IllegalStateException("Must be called from main thread of process");
}

boolean didSomething = false;

while (true) {
int numActions;

synchronized (this) {
if (mPendingActions == null || mPendingActions.size() == 0) {
break;
}

numActions = mPendingActions.size();
if (mTmpActions == null || mTmpActions.length < numActions) {
mTmpActions = new Runnable[numActions];
}
mPendingActions.toArray(mTmpActions);
mPendingActions.clear();
mHost.getHandler().removeCallbacks(mExecCommit);
}

mExecutingActions = true;
for (int i=0; i<numActions; i++) {
mTmpActions[i].run();
mTmpActions[i] = null;
}
mExecutingActions = false;
didSomething = true;
}

if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null && f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
if (!loadersRunning) {
mHavePendingDeferredStart = false;
startPendingDeferredFragments();
}
}
return didSomething;
}
肝心のmPendingActionsのはRunnalbeで、100行くらい有ります
case OP_REPLACE: {
Fragment f = op.fragment;
int containerId = f.mContainerId;
if (mManager.mAdded != null) {
for (int i=0; i<mManager.mAdded.size(); i++) {
Fragment old = mManager.mAdded.get(i);
if (FragmentManagerImpl.DEBUG) Log.v(TAG,
"OP_REPLACE: adding=" + f + " old=" + old);
if (old.mContainerId == containerId) {
if (old == f) {
op.fragment = f = null;
} else {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
}
op.removed.add(old);
old.mNextAnim = exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += 1;
if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+ old + " to " + old.mBackStackNesting);
}
mManager.removeFragment(old, transition, transitionStyle);
}
}
}
}
if (f != null) {
f.mNextAnim = enterAnim;
mManager.addFragment(f, false);
}
} break;
FragmentはmContainer:ViewGroup で全体のViewを管理しています。ViewをFragmentと同じ動作にみせかけます
// When a fragment is being dynamically added to the view hierarchy, this
// is the identifier of the parent container it is being added to.
int mContainerId;
static final class Op {
Op next;
Op prev;
int cmd;
Fragment fragment;
int enterAnim;
int exitAnim;
int popEnterAnim;
int popExitAnim;
ArrayList<Fragment> removed;
}
case OP_ATTACH: {
Fragment f = op.fragment;
f.mNextAnim = enterAnim;
mManager.attachFragment(f, transition, transitionStyle);
} break;
public void attachFragment(Fragment fragment, int transition, int transitionStyle) {
if (fragment.mDetached) {
fragment.mDetached = false;
if (!fragment.mAdded) {
if (mAdded == null) {
mAdded = new ArrayList<Fragment>();
}
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
mAdded.add(fragment);
fragment.mAdded = true;
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
moveToState(fragment, mCurState, transition, transitionStyle, false);
}
}
}
moveToStateではstateという値をゴニョゴニョしてる
static final int INITIALIZING = 0;     // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3; // Fully created, not started.
static final int STARTED = 4; // Created and started, not resumed.
static final int RESUMED = 5; // Created started and resumed.

int mState = INITIALIZING;
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
f.mResumed = true;
f.performResume();
f.mSavedFragmentState = null;
f.mSavedViewState = null;
今のFragmentがSTARTED stateで、次のリクエストされたstateが STARTED以上ならperfomeResumeを呼ぶ

続いてListView and Adapter

Created with Raphaël 2.1.2ListViewListViewAbsListViewAbsListViewAdapterViewAdapterView
Created with Raphaël 2.1.2RenderNodeRenderNodeDisplayListCanvasDisplayListCanvas
# ListView#setAdapter
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
# AdapterView.java
class AdapterDataSetObserver extends DataSetObserver {

private Parcelable mInstanceState = null;

@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();

// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}

@Override
public void onInvalidated() {
mDataChanged = true;

if (AdapterView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = AdapterView.this.onSaveInstanceState();
}

// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;

checkFocus();
requestLayout();
}

public void clearSavedState() {
mInstanceState = null;
}
}
ではdrawはどういうコールのされ方でしょう
protected void dispatchDraw(Canvas canvas) {
final DisplayListCanvas canvas = renderNode.start(width, height);
mRenderNode = RenderNode.create(getClass().getName(), this);
public static RenderNode create(String name, @Nullable View owningView) {
return new RenderNode(name, owningView);
}
public DisplayListCanvas start(int width, int height) {
DisplayListCanvas canvas = DisplayListCanvas.obtain(this);
canvas.setViewport(width, height);
// The dirty rect should always be null for a display list
canvas.onPreDraw(null);
return canvas;
}
# View.java
if (drawingWithRenderNode) {
// Delay getting the display list until animation-driven alpha values are
// set up and possibly passed on to the view
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
// Uncommon, but possible. If a view is removed from the hierarchy during the call
// to getDisplayList(), the display list will be marked invalid and we should not
// try to use it again.
renderNode = null;
drawingWithRenderNode = false;
}
}

結論: updateDisplayListIfDirty() を呼び出してcanvasにレンダリングしている間に ビューがツリーから削除されたけど、renderNodedestroyDisplayListData() が呼ばれる前だった(という予想)