帶你從源碼理解Fragment機制

相信大家平時用到很多Fragment, 手機平板的代碼共用,各種東西的復用,用Fragment也很方便。如今Fragment遍布在我們的APP里面,今天此文將講解Activity是如何Fragment機制聯動的,以及各個生命周期是如何走的,因為,說實話,Fragment的生命周期足夠復雜,我們需要知道它的各個生命周期是如何被調用的。

今天涉及到的類(本文基于Android8.1源碼):
// Activity類, AMS控制的生命周期都是回調給Activity
frameworks/base/core/java/android/app/Activity.java
// Fragment,被實現對象,被回調生命周期對象
frameworks/base/core/java/android/app/Fragment.java
// FragmentController 其實是一個代理類,代理FragmentManager那些方法
frameworks/base/core/java/android/app/FragmentController.java
// FragmentHostCallback 這是一個抽象類,需要主體實現,里面實現一些Fragment機制中所不能實現的功能,比如請求權限,這些都需要主體來實現
frameworks/base/core/java/android/app/FragmentHostCallback.java
// Fragment的管理類,被FragmentHostCallback持有,負責控制Fragment的狀態,真正做操作的類
frameworks/base/core/java/android/app/FragmentManager.java

Fragment體系類介紹

我們從Activity下手, 從getFragmentManager來看,我們是去從FragmentController中獲取的FragmentManager。

public class Activity extends... {

    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

    /**
     * Return the FragmentManager for interacting with fragments associated
     * with this activity.
     */
    public FragmentManager getFragmentManager() {
        return mFragments.getFragmentManager();
    }

    // 在這里實現FragmentHostCallback所無法實現的那些功能,列如activituy跳轉的數據結果等
    class HostCallbacks extends FragmentHostCallback<Activity> {
      ...
    }
}

從FragmentController的源碼可以看到,構造方法的參數為: FragmentHostCallback且,這是唯一的變量。
然后其他方法都是代理, 都調用的是mHost.mFragmentManager中的同名方法。
代碼如下:

public class FragmentController {
    private final FragmentHostCallback<?> mHost;

    /**
     * Returns a {@link FragmentController}.
     */
    public static final FragmentController createController(FragmentHostCallback<?> callbacks) {
        return new FragmentController(callbacks);
    }

    private FragmentController(FragmentHostCallback<?> callbacks) {
        mHost = callbacks;
    }

    // 調用mHost.mFragmentManager的同名方法
    public void dispatchResume() {
        mHost.mFragmentManager.dispatchResume();
    }

    public void dispatchPause() {
        mHost.mFragmentManager.dispatchPause();
    }

     ....
}

我們在前面看Activity初始化FragmentControl的時候,會傳FragmentHostCallback類,

那么mHost.mFragmentManager是什么呢,其是FragmentManagerImpl類,這個是FragmentManger的一個內部類,也繼承于FragmentManger, 并且實現了LayoutInflaterFactory接口。是個做實事的家伙。

public abstract class FragmentHostCallback<E> extends FragmentContainer {
    private final Activity mActivity;
    final Context mContext;
    private final Handler mHandler;
    final int mWindowAnimations;
    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
    /** The loader managers for individual fragments [i.e. Fragment#getLoaderManager()] */
    private SimpleArrayMap<String, LoaderManager> mAllLoaderManagers;
    /** Whether or not fragment loaders should retain their state */
    private boolean mRetainLoaders;
    /** The loader manger for the fragment host [i.e. Activity#getLoaderManager()] */
    private LoaderManagerImpl mLoaderManager;
    private boolean mCheckedForLoaderManager;
    /** Whether or not the fragment host loader manager was started */
    private boolean mLoadersStarted;

    public FragmentHostCallback(Context context, Handler handler, int windowAnimations) {
        this(null /*activity*/, context, handler, windowAnimations);
    }
    ...
  }

FragmentManagerImpl類是最核心的類,Fragment由其管理,各種安全檢查也由其做。代碼如下

final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
    static final String TAG = "FragmentManager";

    ArrayList<OpGenerator> mPendingActions;
    boolean mExecutingActions;

    int mNextFragmentIndex = 0;
    SparseArray<Fragment> mActive;
    final ArrayList<Fragment> mAdded = new ArrayList<>();
    ArrayList<BackStackRecord> mBackStack;
    ArrayList<Fragment> mCreatedMenus;

    // Must be accessed while locked.
    ArrayList<BackStackRecord> mBackStackIndices;
    ArrayList<Integer> mAvailBackStackIndices;

    ArrayList<OnBackStackChangedListener> mBackStackChangeListeners;
    final CopyOnWriteArrayList<Pair<FragmentLifecycleCallbacks, Boolean>>
            mLifecycleCallbacks = new CopyOnWriteArrayList<>();

    int mCurState = Fragment.INITIALIZING;
    FragmentHostCallback<?> mHost;
    FragmentContainer mContainer;
    Fragment mParent;
    Fragment mPrimaryNav;

    boolean mNeedMenuInvalidate;
    boolean mStateSaved;
    boolean mDestroyed;
    String mNoTransactionsBecause;
    boolean mHavePendingDeferredStart;

    // Temporary vars for removing redundant operations in BackStackRecords:
    ArrayList<BackStackRecord> mTmpRecords;
    ArrayList<Boolean> mTmpIsPop;
    ArrayList<Fragment> mTmpAddedFragments;

    // Temporary vars for state save and restore.
    Bundle mStateBundle = null;
    SparseArray<Parcelable> mStateArray = null;

    // Postponed transactions.
    ArrayList<StartEnterTransitionListener> mPostponedTransactions;

    // Prior to O, we allowed executing transactions during fragment manager state changes.
    // This is dangerous, but we want to keep from breaking old applications.
    boolean mAllowOldReentrantBehavior;

    // Saved FragmentManagerNonConfig during saveAllState() and cleared in noteStateNotSaved()
    FragmentManagerNonConfig mSavedNonConfig;

我們可以看到,里面有各種狀態位保存,回退棧等,都在這個類里面記錄。

到這里我們應該知道Fragment體系的幾個類是干嘛的了。接下從一個方法來看看Fragment的事務如何處理。

Fragment事務

我們一般使用Fragment是按照下面這樣使用的,開啟一個事務,最后commit

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
  transaction.replace(R.id.main_fragment_container, homeFragment);
   transaction.commit();

我們來看下beginTransaction方法的實現

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

可以見到,其返回了一個BackStackRecord類,那么這個類是繼承于FragmentTransaction的

/**
 * @hide Entry of an operation on the fragment back stack.
 */
final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
    static final String TAG = FragmentManagerImpl.TAG;

    final FragmentManagerImpl mManager;

    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;
    static final int OP_SET_PRIMARY_NAV = 8;
    static final int OP_UNSET_PRIMARY_NAV = 9;

    // 鏈表節點
    static final class Op {
        int cmd;      // 表明動作
        Fragment fragment;  //被操作的Fragment
        int enterAnim;  // 進入動畫
        int exitAnim;   // 退出動畫
        int popEnterAnim;  // 彈入動畫
        int popExitAnim;   // 彈出動畫

        Op() {
        }

        Op(int cmd, Fragment fragment) {
            this.cmd = cmd;
            this.fragment = fragment;
        }
    }

    ArrayList<Op> mOps = new ArrayList<>();  
    int mEnterAnim;
    int mExitAnim;
    int mPopEnterAnim;
    int mPopExitAnim;
    int mTransition;
    int mTransitionStyle;
    boolean mAddToBackStack;
    boolean mAllowAddToBackStack = true;
    String mName;
    boolean mCommitted;
    int mIndex = -1;
    boolean mReorderingAllowed;

    ArrayList<Runnable> mCommitRunnables;

    int mBreadCrumbTitleRes;
    CharSequence mBreadCrumbTitleText;
    int mBreadCrumbShortTitleRes;
    CharSequence mBreadCrumbShortTitleText;

    ArrayList<String> mSharedElementSourceNames;
    ArrayList<String> mSharedElementTargetNames;

我們從其add方法來看

public FragmentTransaction add(Fragment fragment, String tag) {
    doAddOp(0, fragment, tag, OP_ADD);
    return this;
}

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    if (mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1) {
        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.");
        }
    }
    fragment.mFragmentManager = mManager;

    if (tag != null) {
        ....
        fragment.mTag = tag;
    }

    if (containerViewId != 0) {
        ...
        fragment.mContainerId = fragment.mFragmentId = containerViewId;
    }

    addOp(new Op(opcmd, fragment));
}

void addOp(Op op) {
    mOps.add(op);
    op.enterAnim = mEnterAnim;
    op.exitAnim = mExitAnim;
    op.popEnterAnim = mPopEnterAnim;
    op.popExitAnim = mPopExitAnim;
}

可以看到,其將整個操作封裝成了一個Op并將其添加到mOps列表里面去。這個列表是我們在上面定義的,一個鏈表。

這個流程到這其實就結束了,接下來我們會調用 commit方法。
commit里面調用FragmentManager的enqueueAction方法

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

int commitInternal(boolean allowStateLoss) {
    if (mCommitted) {
        throw new IllegalStateException("commit already called");
    }
    if (FragmentManagerImpl.DEBUG) {
        Log.v(TAG, "Commit: " + this);
        LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
        PrintWriter pw = new FastPrintWriter(logw, false, 1024);
        dump("  ", null, pw, null);
        pw.flush();
    }
    mCommitted = true;
    if (mAddToBackStack) {
        mIndex = mManager.allocBackStackIndex(this);
    } else {
        mIndex = -1;
    }
    mManager.enqueueAction(this, allowStateLoss);  //調用FragmentManager的enqueueAction方法
    return mIndex;
}

FragmentManager中將操作添加進pendingAction中

/**
 * Adds an action to the queue of pending actions.
 *
 * @param action the action to add
 * @param allowStateLoss whether to allow loss of state information
 * @throws IllegalStateException if the activity has been destroyed
 */
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
    if (!allowStateLoss) {
        checkStateLoss();  // 這邊會檢查狀態,若activity已經走了onSaveInstanceStatus,這邊會拋出異常
    }
    synchronized (this) {
        if (mDestroyed || mHost == null) {
            if (allowStateLoss) {
                // This FragmentManager isn't attached, so drop the entire transaction.
                return;
            }
            throw new IllegalStateException("Activity has been destroyed");
        }
        if (mPendingActions == null) {
            mPendingActions = new ArrayList<>();
        }
         // 將commit處理操作放置到mPendingActions列表中
        mPendingActions.add(action);
        scheduleCommit();
    }
}

接下來開啟執行的runnable,進行調度動作

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

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);
        }
    }
}

這樣會調用到Runnable中的execPendingActions()方法

/**
 * Only call from main thread!
 */
public boolean execPendingActions() {
    ensureExecReady(true);

    boolean didSomething = false;
    // 進入死循環, generateOpsForPendingActions會判斷當前是否還有任務
    while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
        mExecutingActions = true;
        try {
            // 去掉多余任務,并執行任務
            // 執行任務的時候,會去挨個調用BackStackRecord中的expandOps方法。里面會執行之前的Ops
            removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
        } finally {
            // 清理多余任務
            cleanupExec();
        }
        didSomething = true;
    }

    doPendingDeferredStart();
    burpActive();

    return didSomething;
}

我們看一下 removeRedundantOperationsAndExecute里面調用BackStackRecord中的expandOps方法的邏輯,在這里就負責取出各個事務進行操作。

Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
     //遍歷鏈表挨個取出動作進行執行
     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);
                 if (op.fragment == oldPrimaryNav) {
                     mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment));
                     opNum++;
                     oldPrimaryNav = null;
                 }
             }
             break;
             case OP_REPLACE: {
                 final Fragment f = op.fragment;
                 final int containerId = f.mContainerId;
                 boolean alreadyAdded = false;
                 for (int i = added.size() - 1; i >= 0; i--) {
                     final Fragment old = added.get(i);
                     if (old.mContainerId == containerId) {
                         if (old == f) {
                             alreadyAdded = true;
                         } else {
                             // This is duplicated from above since we only make
                             // a single pass for expanding ops. Unset any outgoing primary nav.
                             if (old == oldPrimaryNav) {
                                 mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
                                 opNum++;
                                 oldPrimaryNav = null;
                             }
                             final Op removeOp = new Op(OP_REMOVE, 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;
             case OP_SET_PRIMARY_NAV: {
                 // It's ok if this is null, that means we will restore to no active
                 // primary navigation fragment on a pop.
                 mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
                 opNum++;
                 // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
                 oldPrimaryNav = op.fragment;
             }
             break;
         }
     }
     return oldPrimaryNav;
 }

Fragment的生命周期回調

對于Fragment的生命周期,很多人都理解不懂,畢竟生命周期過多,從源碼看,應該會好很多。我們看下Activity是如何回調Fragment的生命周期的。

Activity源碼中:

protected void onCreate(@Nullable Bundle savedInstanceState) {
 ...
   //調用FragmnentController的 dispatchCreate方法
    mFragments.dispatchCreate();
}

public void dispatchCreate() {
    mHost.mFragmentManager.dispatchCreate();
}

FragmentManager中

public void dispatchCreate() {
    mStateSaved = false;
    dispatchMoveToState(Fragment.CREATED);
}

private void dispatchMoveToState(int state) {
    if (mAllowOldReentrantBehavior) {
        moveToState(state, false);
    } else {
        try {
            mExecutingActions = true;
            moveToState(state, false);
        } finally {
            mExecutingActions = false;
        }
    }
    execPendingActions();
}

最后會調到:
這個方法特別長,

static final int INVALID_STATE = -1;   // Invalid state used as a null value.
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.

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
        boolean keepActive) {
    switch (f.mState) {
         case Fragment.INITIALIZING:   //里面會回調onAttach
              ...
              f.onAttach(mHost.getContext());
              break;
         case Fragment.CREATED:
              f.mView = f.performCreateView(f.performGetLayoutInflater(  // 回調onCreateView
                               f.mSavedFragmentState), container, f.mSavedFragmentState);
              f.onViewCreated(f.mView, f.mSavedFragmentState);      // 回調onViewCreated
              f.performActivityCreated(f.mSavedFragmentState);      // 回調 onActivityCreated(savedInstanceState);
               dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);  
              if (f.mView != null) {
                  f.restoreViewState(f.mSavedFragmentState);     // 回調 onViewStateRestored(savedInstanceState);
              }
              case Fragment.ACTIVITY_CREATED:
                  if (newState > Fragment.ACTIVITY_CREATED) {
                      f.mState = Fragment.STOPPED;
                  }
                  // fall through
              case Fragment.STOPPED:
                  if (newState > Fragment.STOPPED) {
                      if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
                      f.performStart();                          // 回調 onStart
                      dispatchOnFragmentStarted(f, false);
                  }
                  // fall through
              case Fragment.STARTED:
                  if (newState > Fragment.STARTED) {
                      if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
                      f.performResume();                        // 回調 onResume
                      dispatchOnFragmentResumed(f, false);
                      // Get rid of this in case we saved it and never needed it.
                      f.mSavedFragmentState = null;
                      f.mSavedViewState = null;
                  }
              ....

}

其他的生命周期也是類似的,大體都一樣

final void performPause() {
    mDoReportFullyDrawn = false;
    mFragments.dispatchPause();     // 通知Fragment onPause
    mCalled = false;
    onPause();                      //Activity自身onPause
    mResumed = false;
    if (!mCalled && getApplicationInfo().targetSdkVersion
            >= android.os.Build.VERSION_CODES.GINGERBREAD) {
        throw new SuperNotCalledException(
                "Activity " + mComponent.toShortString() +
                " did not call through to super.onPause()");
    }
    mResumed = false;
}


final void performResume() {
    performRestart();

    mFragments.execPendingActions();

    mLastNonConfigurationInstances = null;

    mCalled = false;
    // mResumed is set by the instrumentation
    mInstrumentation.callActivityOnResume(this);      // 調用Activity的onResume

    // Now really resume, and install the current status bar and menu.
    mCalled = false;

    mFragments.dispatchResume();     // 調用Fragment的onResume
    mFragments.execPendingActions();

    onPostResume();
    if (!mCalled) {
        throw new SuperNotCalledException(
            "Activity " + mComponent.toShortString() +
            " did not call through to super.onPostResume()");
    }
}

通過閱讀這些源碼,我們可以總結出Fragment的生命周期如下


fragment-life.png

結尾

520這天,寫了這篇博客。。。嗯 希望大家多多進步


本文作者:Anderson/Jerey_Jobs
博客地址 : http://jerey.cn/
簡書地址 : Anderson大碼渣
github地址 : https://github.com/Jerey-Jobs

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容