public abstract FragmentTransaction hide(Fragment fragment);

//显示前面隐藏的 fragment,这只适用于之前添加到宿主上的 fragment public abstract FragmentTransaction show(Fragment fragment);

//将指定的 fragment 将布局上解除 //当调用这个方法时,fragment 的布局已经销毁了 public abstract FragmentTransaction detach(Fragment fragment);

//当前面解除一个 fragment 的布局绑定后,调用这个方法可以重新绑定 //这将导致该 fragment 的布局重建,然后添加、展示到界面上 public abstract FragmentTransaction attach(Fragment fragment);

对 fragment 的操作基本就这几步,我们知道,要完成对 fragment 的操作,最后还需要提交一下:

mFragmentManager.beginTransaction() .replace(R.id.fl_child, getChildFragment()) // .commit() .commitAllowingStateLoss();

事务的四种提交方式

事务最终的提交方法有四种:

commit()commitAllowingStateLoss()commitNow()commitNowAllowingStateLoss()

它们之间的特点及区别如下:

public abstract int commit();

commit() 在主线程中异步执行,其实也是 Handler 抛出任务,等待主线程调度执行。

注意: commit() 需要在宿主 Activity 保存状态之前调用,否则会报错。 这是因为如果 Activity 出现异常需要恢复状态,在保存状态之后的 commit() 将会丢失,这和调用的初衷不符,所以会报错。

public abstract int commitAllowingStateLoss();

commitAllowingStateLoss() 也是异步执行,但它的不同之处在于,允许在 Activity 保存状态之后调用,也就是说它遇到状态丢失不会报错。

因此我们一般在界面状态出错是可以接受的情况下使用它。

public abstract void commitNow();

commitNow() 是同步执行的,立即提交任务。

前面提到 FragmentManager.executePendingTransactions() 也可以实现立即提交事务。但我们一般建议使用 commitNow(), 因为另外那位是一下子执行所有待执行的任务,可能会把当前所有的事务都一下子执行了,这有可能有副作用。

此外,这个方法提交的事务可能不会被添加到 FragmentManger 的后退栈,因为你这样直接提交,有可能影响其他异步执行任务在栈中的顺序。

和 commit() 一样,commitNow() 也必须在 Activity 保存状态前调用,否则会抛异常。

public abstract void commitNowAllowingStateLoss();

同步执行的 commitAllowingStateLoss()。

OK,了解了 FragmentTransaction 定义的操作,去看看我们真正关心的、 beginTransaction() 中返回的 BackStackRecord:

@Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); }

事务真正实现/回退栈 BackStackRecord

BackStackRecord 既是对 Fragment 进行操作的事务的真正实现,也是 FragmentManager 中的回退栈的实现:

final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {…}

它的关键成员:

final FragmentManagerImpl mManager;

//Op 可选的状态值 static final int OP_NULL = 0; static final int OP_ADD = 1; static final int OP_REPLACE = 2; static final int OP_REMOVE = 3; static final int OP_HIDE = 4; static final int OP_SHOW = 5; static final int OP_DETACH = 6; static final int OP_ATTACH = 7;

ArrayList mOps = new ArrayList<>(); static final class Op { int cmd; //状态 Fragment fragment; int enterAnim; int exitAnim; int popEnterAnim; int popExitAnim; }

int mIndex = -1; //栈中最后一个元素的索引

可以看到 Op 就是添加了状态和动画信息的 Fragment, mOps 就是栈中所有的 Fragment。

事务定义的方法它是如何实现的呢。

先看添加一个 Fragment 到布局 add() 的实现:

@Override public FragmentTransaction add(int containerViewId, Fragment fragment) { doAddOp(containerViewId, fragment, null, OP_ADD); return this; }

@Override public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { doAddOp(containerViewId, fragment, tag, OP_ADD); return this; } private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { final Class fragmentClass = fragment.getClass(); final int modifiers = fragmentClass.getModifiers(); if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers) || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) { throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()

" must be a public static class to be properly recreated from"" instance state."); }

//1.修改添加的 fragmentManager 为当前栈的 manager fragment.mFragmentManager = mManager;

if (tag != null) { if (fragment.mTag != null && !tag.equals(fragment.mTag)) { throw new IllegalStateException("Can’t change tag of fragment "

fragment + ": was " + fragment.mTag" now " + tag); } fragment.mTag = tag; }

if (containerViewId != 0) { if (containerViewId == View.NO_ID) { throw new IllegalArgumentException("Can’t add fragment "

fragment + " with tag " + tag + " to container view with no id"); } if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { throw new IllegalStateException("Can’t change container ID of fragment "fragment + ": was " + fragment.mFragmentId" now " + containerViewId); } //2.设置宿主 ID 为布局 ID fragment.mContainerId = fragment.mFragmentId = containerViewId; }

//3.构造 Op Op op = new Op(); op.cmd = opcmd; //状态 op.fragment = fragment; //4.添加到数组列表中 addOp(op); } void addOp(Op op) { mOps.add(op); op.enterAnim = mEnterAnim; op.exitAnim = mExitAnim; op.popEnterAnim = mPopEnterAnim; op.popExitAnim = mPopExitAnim; }

可以看到添加一个 Fragment 到布局很简单,概况一下就是: 修改 fragmentManager 和 ID,构造成 Op,设置状态信息,然后添加到列表里。

添加完了看看替换 replace 的实现:

@Override public FragmentTransaction replace(int containerViewId, Fragment fragment) { return replace(containerViewId, fragment, null); }

@Override public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { if (containerViewId == 0) { throw new IllegalArgumentException(“Must use non-zero containerViewId”); }

doAddOp(containerViewId, fragment, tag, OP_REPLACE); return this; }

太可怕了,也是调用上面刚提到的 doAddOp(),不同之处在于第四个参数为 OP_REPLACE,看来之前小看了这个状态值!

再看其他方法的实现就很简单了,无非就是构造一个 Op,设置对应的状态值。

@Override public FragmentTransaction remove(Fragment fragment) { Op op = new Op(); op.cmd = OP_REMOVE; op.fragment = fragment; addOp(op);

return this; }

@Override public FragmentTransaction hide(Fragment fragment) { Op op = new Op(); op.cmd = OP_HIDE; op.fragment = fragment; addOp(op);

return this; }

@Override public FragmentTransaction show(Fragment fragment) { Op op = new Op(); op.cmd = OP_SHOW; op.fragment = fragment; addOp(op);

return this; }

那这些状态值的不同是什么时候起作用的呢?

别忘了我们操作 Fragment 还有最后一步,提交。

看看这两个是怎么实现的:

@Override public int commit() { return commitInternal(false); }

@Override public int commitAllowingStateLoss() { return commitInternal(true); } int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException(“commit already called”); //… mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); //更新 index 信息 } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); //异步任务入队 return mIndex; } public void enqueueAction(OpGenerator 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<>(); } mPendingActions.add(action); scheduleCommit(); //发送任务 } } private void scheduleCommit() { synchronized (this) { boolean postponeReady = mPostponedTransactions != null && !mPostponedTransactions.isEmpty(); boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1; if (postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); } } }

前面已经介绍过了,FragmentManager.enqueueAction() 最终是使用 Handler 实现的异步执行。

现在的问题是执行的任务是啥?

答案就是 Handler 发送的任务 mExecCommit:

Runnable mExecCommit = new Runnable() { @Override public void run() { execPendingActions(); } }; /**

Only call from main thread!更新 UI 嘛,肯定得在主线程 */ public boolean execPendingActions() { ensureExecReady(true);

boolean didSomething = false; while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) { mExecutingActions = true; try { optimizeAndExecuteOps(mTmpRecords, mTmpIsPop); //这里是入口 } finally { cleanupExec(); } didSomething = true; }

doPendingDeferredStart();

return didSomething; } private void optimizeAndExecuteOps(ArrayList records, ArrayList isRecordPop) { if (records == null || records.isEmpty()) { return; }

if (isRecordPop == null || records.size() != isRecordPop.size()) { throw new IllegalStateException(“Internal error with the back stack records”); }

// Force start of any postponed transactions that interact with scheduled transactions: executePostponedTransaction(records, isRecordPop);

final int numRecords = records.size(); int startIndex = 0; for (int recordNum = 0; recordNum < numRecords; recordNum++) { final boolean canOptimize = records.get(recordNum).mAllowOptimization; if (!canOptimize) { // execute all previous transactions if (startIndex != recordNum) { //这里将 Ops 过滤一遍 executeOpsTogether(records, isRecordPop, startIndex, recordNum); } // execute all unoptimized pop operations together or one add operation //… } if (startIndex != numRecords) { executeOpsTogether(records, isRecordPop, startIndex, numRecords); } } private void executeOpsTogether(ArrayList records, ArrayList isRecordPop, int startIndex, int endIndex) { final boolean allowOptimization = records.get(startIndex).mAllowOptimization; boolean addToBackStack = false; if (mTmpAddedFragments == null) { mTmpAddedFragments = new ArrayList<>(); } else { mTmpAddedFragments.clear(); } if (mAdded != null) { mTmpAddedFragments.addAll(mAdded); } for (int recordNum = startIndex; recordNum < endIndex; recordNum++) { final BackStackRecord record = records.get(recordNum); final boolean isPop = isRecordPop.get(recordNum); if (!isPop) { record.expandReplaceOps(mTmpAddedFragments); //修改状态 } else { record.trackAddedFragmentsInPop(mTmpAddedFragments); } addToBackStack = addToBackStack || record.mAddToBackStack; } mTmpAddedFragments.clear();

if (!allowOptimization) { FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex, false); } //真正处理的入口 executeOps(records, isRecordPop, startIndex, endIndex);

int postponeIndex = endIndex; if (allowOptimization) { ArraySet addedFragments = new ArraySet<>(); addAddedFragments(addedFragments); postponeIndex = postponePostponableTransactions(records, isRecordPop, startIndex, endIndex, addedFragments); makeRemovedFragmentsInvisible(addedFragments); //名字就能看出来作用 }

if (postponeIndex != startIndex && allowOptimization) { // need to run something now FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, postponeIndex, true); moveToState(mCurState, true); } //… } //修改 Ops 状态,这一步还没有真正处理状态 expandReplaceOps(ArrayList added) { for (int opNum = 0; opNum < mOps.size(); opNum++) { final Op op = mOps.get(opNum); switch (op.cmd) { case OP_ADD: case OP_ATTACH: added.add(op.fragment); break; case OP_REMOVE: case OP_DETACH: added.remove(op.fragment); break; case OP_REPLACE: { Fragment f = op.fragment; int containerId = f.mContainerId; boolean alreadyAdded = false; for (int i = added.size() - 1; i >= 0; i–) { Fragment old = added.get(i); if (old.mContainerId == containerId) { if (old == f) { alreadyAdded = true; } else { Op removeOp = new Op(); removeOp.cmd = OP_REMOVE; //可以看到,替换也是通过删除实现的 removeOp.fragment = old; removeOp.enterAnim = op.enterAnim; removeOp.popEnterAnim = op.popEnterAnim; removeOp.exitAnim = op.exitAnim; removeOp.popExitAnim = op.popExitAnim; mOps.add(opNum, removeOp); added.remove(old); opNum++; } } } if (alreadyAdded) { mOps.remove(opNum); opNum–; } else { op.cmd = OP_ADD; added.add(f); } } break; } } } //设置将要被移除的 Fragment 为不可见的最终实现 private void makeRemovedFragmentsInvisible(ArraySet fragments) { final int numAdded = fragments.size(); for (int i = 0; i < numAdded; i++) { final Fragment fragment = fragments.valueAt(i); if (!fragment.mAdded) { final View view = fragment.getView(); //获取 Fragment 的布局,设置状态 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { fragment.getView().setVisibility(View.INVISIBLE); } else { //高版本设置透明度 fragment.mPostponedAlpha = view.getAlpha(); view.setAlpha(0f); } } } }

代码多了一点,但我们终于找到了最终的实现:Handler 异步发到主线,调度执行后,聚合、修改 Ops 的状态,然后遍历、修改 Fragment 栈中的 View 的状态。

真正处理的部分

前面主要是对 Fragment 的包装类 Ops 进行一些状态修改,真正根据 Ops 状态进行操作在这个部分:

/**

Executes the operations contained within this transaction. The Fragment states will onlybe modified if optimizations are not allowed. */ void executeOps() { final int numOps = mOps.size(); for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); final Fragment f = op.fragment; f.setNextTransition(mTransition, mTransitionStyle); switch (op.cmd) { case OP_ADD: f.setNextAnim(op.enterAnim); mManager.addFragment(f, false); break; case OP_REMOVE: f.setNextAnim(op.exitAnim); mManager.removeFragment(f); break; case OP_HIDE: f.setNextAnim(op.exitAnim); mManager.hideFragment(f); break; case OP_SHOW: f.setNextAnim(op.enterAnim); mManager.showFragment(f); break; case OP_DETACH: f.setNextAnim(op.exitAnim); mManager.detachFragment(f); break; case OP_ATTACH: f.setNextAnim(op.enterAnim); mManager.attachFragment(f); break; default: throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } if (!mAllowOptimization && op.cmd != OP_ADD) { mManager.moveFragmentToExpectedState(f); } } if (!mAllowOptimization) { // Added fragments are added at the end to comply with prior behavior. mManager.moveToState(mManager.mCurState, true); } }

FragmentManager 对这些方法的实现也很简单,修改 Fragment 的状态值,比如 remove(Fragment):

public void removeFragment(Fragment fragment) { if (DEBUG) Log.v(TAG, “remove: " + fragment + " nesting=” + fragment.mBackStackNesting); final boolean inactive = !fragment.isInBackStack(); if (!fragment.mDetached || inactive) { if (mAdded != null) { mAdded.remove(fragment); } if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.mAdded = false; //设置属性值 fragment.mRemoving = true; } }

最终会调用 moveToState(),我们直接来看它的实现:

void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { //还没有添加的 Fragment 处于 onCreate() 状态 if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) { newState = Fragment.CREATED; } if (f.mRemoving && newState > f.mState) { // While removing a fragment, we can’t change it to a higher state. newState = f.mState; } //推迟启动的设置为 stop if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) { newState = Fragment.STOPPED; } if (f.mState < newState) { // For fragments that are created from a layout, when restoring from // state we don’t want to allow them to be created until they are // being reloaded from the layout. if (f.mFromLayout && !f.mInLayout) { return; } if (f.getAnimatingAway() != null) { // The fragment is currently being animated… but! Now we // want to move our state back up. Give up on waiting for the // animation, move to whatever the final state should be once // the animation is done, and then we can proceed from there. f.setAnimatingAway(null); //如果当前 Fragment 正有动画,直接修改为最终状态 moveToState(f, f.getStateAfterAnimating(), 0, 0, true); } switch (f.mState) { case Fragment.INITIALIZING: if (DEBUG) Log.v(TAG, "moveto CREATED: " + f); if (f.mSavedFragmentState != null) { f.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader()); f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); f.mTarget = getFragment(f.mSavedFragmentState, FragmentManagerImpl.TARGET_STATE_TAG); if (f.mTarget != null) { f.mTargetRequestCode = f.mSavedFragmentState.getInt( FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0); } f.mUserVisibleHint = f.mSavedFragmentState.getBoolean( FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true); if (!f.mUserVisibleHint) { f.mDeferStart = true; if (newState > Fragment.STOPPED) { newState = Fragment.STOPPED; } } } f.mHost = mHost; f.mParentFragment = mParent; f.mFragmentManager = mParent != null ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); dispatchOnFragmentPreAttached(f, mHost.getContext(), false); f.mCalled = false; f.onAttach(mHost.getContext()); //调用 Fragment 生命周期方法 if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f

" did not call through to super.onAttach()"); } if (f.mParentFragment == null) { mHost.onAttachFragment(f); } else { f.mParentFragment.onAttachFragment(f); } dispatchOnFragmentAttached(f, mHost.getContext(), false);

if (!f.mRetaining) { f.performCreate(f.mSavedFragmentState); //调用 Fragment 生命周期方法

dispatchOnFragmentCreated(f, f.mSavedFragmentState, false); } else { f.restoreChildFragmentState(f.mSavedFragmentState); f.mState = Fragment.CREATED; } f.mRetaining = false; if (f.mFromLayout) { //从布局解析来的 // For fragments that are part of the content view // layout, we need to instantiate the view immediately // and the inflater will take care of adding it. f.mView = f.performCreateView(f.getLayoutInflater( //调用 Fragment 生命周期方法 f.mSavedFragmentState), null, f.mSavedFragmentState); if (f.mView != null) { f.mInnerView = f.mView; if (Build.VERSION.SDK_INT >= 11) { ViewCompat.setSaveFromParentEnabled(f.mView, false); } else { f.mView = NoSaveStateFrameLayout.wrap(f.mView); } if (f.mHidden) f.mView.setVisibility(View.GONE); f.onViewCreated(f.mView, f.mSavedFragmentState); //调用 Fragment 生命周期方法 dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false); } else { f.mInnerView = null; } } case Fragment.CREATED: if (newState > Fragment.CREATED) { if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { if (f.mContainerId == View.NO_ID) { throwException(new IllegalArgumentException( "Cannot create fragment "

f" for a container view with no id")); } container = (ViewGroup) mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { String resName; try { resName = f.getResources().getResourceName(f.mContainerId); } catch (NotFoundException e) { resName = “unknown”; } throwException(new IllegalArgumentException( “No view found for id 0x”Integer.toHexString(f.mContainerId) + " ("resName") for fragment " + f)); } } f.mContainer = container; f.mView = f.performCreateView(f.getLayoutInflater( //调用 Fragment 生命周期方法 f.mSavedFragmentState), container, f.mSavedFragmentState); if (f.mView != null) { f.mInnerView = f.mView; if (Build.VERSION.SDK_INT >= 11) { ViewCompat.setSaveFromParentEnabled(f.mView, false); } else { f.mView = NoSaveStateFrameLayout.wrap(f.mView); } if (container != null) { container.addView(f.mView); //将 Fragment 的布局添加到父布局中 } if (f.mHidden) { f.mView.setVisibility(View.GONE); } f.onViewCreated(f.mView, f.mSavedFragmentState);//调用 Fragment 生命周期方法 dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false); // Only animate the view if it is visible. This is done after // dispatchOnFragmentViewCreated in case visibility is changed f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE) && f.mContainer != null; } else { f.mInnerView = null; } }

f.performActivityCreated(f.mSavedFragmentState); //调用 Fragment 生命周期方法

dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false); if (f.mView != null) { f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; } case Fragment.ACTIVITY_CREATED: if (newState > Fragment.ACTIVITY_CREATED) { f.mState = Fragment.STOPPED; } case Fragment.STOPPED: if (newState > Fragment.STOPPED) { if (DEBUG) Log.v(TAG, "moveto STARTED: " + f); f.performStart(); dispatchOnFragmentStarted(f, false); } case Fragment.STARTED: if (newState > Fragment.STARTED) { if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f); f.performResume(); dispatchOnFragmentResumed(f, false); f.mSavedFragmentState = null; f.mSavedViewState = null; } } } else if (f.mState > newState) { switch (f.mState) { case Fragment.RESUMED: if (newState < Fragment.RESUMED) { if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f); f.performPause(); dispatchOnFragmentPaused(f, false); } case Fragment.STARTED: if (newState < Fragment.STARTED) { if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f); f.performStop(); dispatchOnFragmentStopped(f, false); } case Fragment.STOPPED: if (newState < Fragment.STOPPED) { if (DEBUG) Log.v(TAG, "movefrom STOPPED: " + f); f.performReallyStop(); } case Fragment.ACTIVITY_CREATED: if (newState < Fragment.ACTIVITY_CREATED) { if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f); if (f.mView != null) { // Need to save the current view state if not // done already. if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) { saveFragmentViewState(f); } } f.performDestroyView(); dispatchOnFragmentViewDestroyed(f, false); if (f.mView != null && f.mContainer != null) { Animation anim = null; if (mCurState > Fragment.INITIALIZING && !mDestroyed && f.mView.getVisibility() == View.VISIBLE && f.mPostponedAlpha >= 0) { anim = loadAnimation(f, transit, false, transitionStyle); } f.mPostponedAlpha = 0; if (anim != null) { final Fragment fragment = f; f.setAnimatingAway(f.mView); f.setStateAfterAnimating(newState); final View viewToAnimate = f.mView; anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener( viewToAnimate, anim) { @Override public void onAnimationEnd(Animation animation) { super.onAnimationEnd(animation); if (fragment.getAnimatingAway() != null) { fragment.setAnimatingAway(null); moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false); } } }); f.mView.startAnimation(anim); } f.mContainer.removeView(f.mView); } f.mContainer = null; f.mView = null; f.mInnerView = null; } case Fragment.CREATED: if (newState < Fragment.CREATED) { if (mDestroyed) { if (f.getAnimatingAway() != null) { // The fragment’s containing activity is // being destroyed, but this fragment is // currently animating away. Stop the // animation right now – it is not needed, // and we can’t wait any more on destroying // the fragment. View v = f.getAnimatingAway(); f.setAnimatingAway(null); v.clearAnimation(); } } if (f.getAnimatingAway() != null) { // We are waiting for the fragment’s view to finish // animating away. Just make a note of the state // the fragment now should move to once the animation // is done. f.setStateAfterAnimating(newState); newState = Fragment.CREATED; } else { if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f); if (!f.mRetaining) { f.performDestroy(); dispatchOnFragmentDestroyed(f, false); } else { f.mState = Fragment.INITIALIZING; }

f.performDetach(); dispatchOnFragmentDetached(f, false); if (!keepActive) { if (!f.mRetaining) { makeInactive(f); } else { f.mHost = null; f.mParentFragment = null; f.mFragmentManager = null; }

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)

写在最后

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android) [外链图片转存中…(img-Mh0Urr6i-1712099151145)]

写在最后

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: