四大组件:Fragment: Fragment 你所不知道的细节

Android解惑 - 为什么要用Fragment.setArguments(Bundle bundle)来传递参数_Tibib的专栏-CSDN博客_fragment setarguments为什么要用Fragment.setArguments(Bundle bundle)来传递参数https://blog.csdn.net/tu_bingbing/article/details/24143249​​​​​​Fragment运行机制源码分析(一)_Android学习之旅-CSDN博客_android fragment源码分析
Android-Fragment源码分析 - 知乎Fragment的概念 由于每个页面都要提供一个Activity来展示页面,这样在某些场景下可能太重量级了,比如说频道之间的切换,所以从Android3开始,提供了Fragment。Fragment可以理解成一种更小粒度的Activity,Fragmen…https://zhuanlan.zhihu.com/p/163501082
 

   1 : Fragment构造

    1-1: Fragment的创建过程没有什么特别的,和普通类一样,直接new就可以,构造器中也可以传任意参数,这是因为Fragment和Activity不同,Activity是系统类,在Manifest中注册,由AMS管理,而Fragment被称为碎片,在Activity中创建,那么Fragment的初始化过程应该就是commit了。

所以 :我们可以把 Fragment 看作成 Activity成员变量,这个成员变量可以显示,控制 View

  1-2 :  那么一般通过什么样的方式构造 Fragment

     直接将参数封装到Arguments中,调用者直接调用这个无参的构造函数即可

public class MyFragment extends Fragment {

    /**
     * 静态工厂方法需要一个int型的值来初始化fragment的参数,
     * 然后返回新的fragment到调用者
     */
    public static MyFragment newInstance(int index) {
        MyFragment f = new MyFragment();
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);
        return f;
    }
}

获取到 Fragment之后,我们有两种方式可以将Fragment绑定到Activity中

方式一:

    通过在Activity的布局文件xml中,直接添加 Fragment标签,但是这种方式并不常见,我们知道有这个方式就可以了

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ExampleFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

方式二:就是通过构造函数初始化Fragment 然后通过事务的方式commit到Activity中

比如下面这个页面:基本构造就是:主Activity + RadioButton (嵌入Fragment)

 1: activity_layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/main_tab_fl"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <RadioGroup
        android:background="@color/title_bar_bg_day"
        android:id="@android:id/tabs"
        android:layout_width="fill_parent"
        android:layout_height="@dimen/tab_height"
        android:layout_alignParentBottom="true"
        android:gravity="bottom"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/home_rb"
            style="@style/home_bottom_tab_style"
            android:drawableTop="@drawable/ic_tab_home"
            android:text="首页" />

        <RadioButton
            android:id="@+id/find_rb"
            style="@style/home_bottom_tab_style"
            android:drawableTop="@drawable/ic_tab_discovery"
            android:text="发现" />

        <RadioButton
            android:id="@+id/new_rb"
            style="@style/home_bottom_tab_style"
            android:drawableTop="@drawable/ic_tab_fresh"
            android:text="新鲜" />

        <RadioButton
            android:id="@+id/message_rb"
            style="@style/home_bottom_tab_style"
            android:drawableTop="@drawable/ic_tab_msg"
            android:text="消息" />
    </RadioGroup>
</LinearLayout>

2:  当我们不断点击下面的 tab按钮时,我们需要动态的去替换中间的 Fragment,Google官方文档时这样说的,您可以在在Activity运行期间随时将Fragment片段添加到Activity布局中,您只需要指定放入那个ViewGroup即可。如果你想在您的Activity中执行片段的事务(如添加,替换,删除)那么您必须使用 FragmentTransaction中的 Api.

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
HomeFragment fragment = new HomeFragment();
fragmentTransaction.add(R.id.main_tab_fl, fragment);
fragmentTransaction.commit();

 传递到 add() 的第一个参数是 ViewGroup,即应该放置片段的位置,由资源 ID 指定,第二个参数是要添加的片段。
  一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。添加没有 UI 的片段。如果需要做到切换就需要使用这个replace(int id, Fragment fragment)。

如果需要替换成 其他栏目的 Fragement 我们只需要监听 RadioGroup的 Button即可替换

private void homeRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        // 替换成当前页面
        fragmentTransaction.replace(R.id.main_tab_fl, mHomeFragment);
        fragmentTransaction.commit();
    }


    private void findRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        if (mFindFragment == null) {
            mFindFragment = new FindFragment();
        }
        fragmentTransaction.replace(R.id.main_tab_fl, mFindFragment);
        fragmentTransaction.commit();
    }


    private void newRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        if (mNewFragment == null) {
            mNewFragment = new NewFragment();
        }
        fragmentTransaction.replace(R.id.main_tab_fl, mNewFragment);
        fragmentTransaction.commit();
    }

    private void messageRbClick() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        if (mMessageFragment == null) {
            mMessageFragment = new MessageFragment();
        }
        fragmentTransaction.replace(R.id.main_tab_fl, mMessageFragment);
        fragmentTransaction.commit();
    }

2:Activity如何管理 Fragment生命周期

Android中Fragment的解析和使用详解 / 张生荣

1:Activity的生命周期是AMS管理,Fragment的生命周期是被所属Activity管理。

2: Fragment先通过 事务 FragmentTransaction.commit() 使其被FragmentManager管理在队列中

然后当运行宿主Activity生命周期时,同步的回调Frragment生命周期

3:Fragment生命周期的回调,其实是状态的转移改变,那么Fragment状态转移的改变又是怎样进行的了 ? 一方面是宿主Activity生命周期自动修改Fragment状态,从而回调Fragment生命周期函数,另一方面通过手动提交事务,修改Fragment状态,回调生命周期方法。

4:  我们的Activity一般是用AppCompatActivity,而AppCompatActivity继承了FragmentActivity

public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
        TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
复制代码

也就是说Activity之所支持fragment,是因为有FragmentActivity,他内部有一个FragmentController,这个controller持有一个FragmentManager,真正做事的就是这个FragmentManager的实现类FragmentManagerImpl

3:  Fragment 为什么需要通过 setArguments(Bundle bundle)来保留参数

    我们可以知道Activity重新创建时(比如手机横竖屏切换),会重新构建它所管理的Fragment,原先的Fragment的字段值将会全部丢失,但是通过 Fragment.setArguments(Bundle bundle)方法设置的bundle会保留下来。所以尽量使用Fragment.setArguments(Bundle bundle)方式来传递参数

4:Fragment常见的异常

4-1 :java.lang.IllegalStateException: Fragment MyFragment{410f6060} not attached to Activity

表示Fragment还没有绑定到宿主Activity,但是你又引用了宿主Activity相关资源

所以在引用的时候加一个判断即可  : 

/**
 * Return true if the fragment is currently added to its activity.
 */
final public boolean isAdded() {
    return mHost != null && mAdded;
}
4-2:fragment is already add
这种异常仅仅加一个 isAdd() 判断条件是远远不够的,isAdd()这个判断条件有时会出现,Fragment明明已经添加了,但是isAdd() 返回就是false. 即: 事务不是立即执行的,有可能会同时添加两个add的事务,第一个没执行的时候又add了一个。
那么就需要在多添加一个条件 
String tag = this.javaClass.simpleName
Fragment fragment = fragmentManager.findFragmentByTag(tag)
if (fragment != null) {
    return
}
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(this, tag)
fragmentTransaction.commitAllowingStateLoss()
// 事务立即执行
fragmentManager.executePendingTransactions

4-3:java.lang.IllegalStateException: Fragment already active

在 Fragment 没有被添加到 FragmentManager 之前,我们可以通过Fragment.setArguments() 来设置参数,并在 Fragment 中,使用 getArguments()来取得参数。在 Fragment 被添加到 FragmentManager 后,一旦被使用,我们再次调用 setArguments()将会导致 java.lang.IllegalStateException: Fragment already active异常。

解决方法:可以使用setter和getter方法进行数据的存储和获取。

4-4:java.lang.IllegalStateException: Can not perform this actionafter onSaveInstanceState

在使用Fragment保存参数的时候,可能是因为需要保存的参数比较大或者比较多,这种情况下页会引起异常。比如代码:Bundle b = new Bundle();
b.putParcelable("bitmap", bitmap2);
imageRecognitionFragment.setArguments(b);
设置好参数,并且添加hide(),add(),方法之后,需要commit(),来实现两个Fragment跳转的时候,这种情形下参数需要进行系统保存,但是这个时候你已经实现了跳转,系统参数却没有保存。此时就会报
java.lang.IllegalStateException: Can not perform this action afteronSaveInstanceState
异常。

分析原因:你并不需要系统保存的参数,只要你自己设置的参数能够传递过去,在另外一个Fragment里能够顺利接受就行了,现在android里提供了另外一种形式的提交方式commitAllowingStateLoss(),从名字上就能看出,这种提交是允许状态值丢失的。到此问题得到完美解决,值的传递是你自己控制的。
这里也说一下另外一个问题,bitmap 也可以通过Bundle传递的,使用putParacelable就可以了。

5-5: Fragment 添加,删除,替换,隐藏, 其实就是通过 BackStackRecord,将对应的 Fragment  相关状态变量改变和 操作保存Fragment的链表

归纳理解:

(1) :Fragment生命周期怎么理解,其生命周期的回调函数是如何调用的。

1:Fragment生命周期函数(比如:onAttach, onCreateView onViewCreated onResume)这些函数的调用,其实是取决与 FragmentManagerImpl.moveToState()函数状态决定的,在这个函数中,会根据当前Fragment的 newState状态,直接回调Fragment生命周期函数

比如:回调 Fragment的  onAttach函数

 2:那 FragmentManagerImpl.moveToState()是如何触发的了 ?

分两种情况:第一种情况是:通过提交事务,什么意思,就是通过 FragmentTranaction.commit()来实现FragmentManagerImpl回调 moveToState(),具体回调流程就是:看如下代码:

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transition = fragmentManager.beginTransaction();
        FragmentOne fragmentOne = new FragmentOne();
        transition.add(R.id.container_layout,fragmentOne);
        transition.commit();

Add a fragment to the activity state. This fragment may optionally also have its view (if Fragment.onCreateView returns non-null) inserted into a container view of the activity.
add是把一个fragment添加到activity中的容器container view中

源码分析:-----------------------------------------------------------

FragmentManager是个抽象类,当我们执行getSupportFragmentManager方法时,得到的是它的实现类FragmentManagerImpl。FragmentTransaction同样是抽象类,它的实现类是BackStackRecord类。从BackStackRecord中,我们可以看到add,replace方法。
add方法:
 

    @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.");
        }

        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);
            }
            fragment.mContainerId = fragment.mFragmentId = containerViewId;
        }

        Op op = new Op();
        op.cmd = opcmd;
        op.fragment = fragment;
        addOp(op);
    }

 方法里面,首先判断传入的fragment方法是不是合法的。
接着,如果传入的参数TAG不为空的话,将它赋值到fragment的mTag中,如:fragment.mTag = tag;
然后判断containerViewId的合法后,执行赋值:fragment.mContainerId = fragment.mFragmentId = containerViewId;
最后将fragment加入到队中,调用addOp方法。

void addOp(Op op) {
        if (mHead == null) {
            mHead = mTail = op;
        } else {
            op.prev = mTail;
            mTail.next = op;
            mTail = op;
        }
        op.enterAnim = mEnterAnim;
        op.exitAnim = mExitAnim;
        op.popEnterAnim = mPopEnterAnim;
        op.popExitAnim = mPopExitAnim;
        mNumOp++;
    }

以上完成了准备阶段,我们继续看BackStackRecord类,知道原来它继承了Runable,run方法才是真正执行的方法。在while循环中的switch case包含了OP_ADD和OP_REPLACE的分支,这不正是刚才方法doAddOp中传入的不同参数吗。所以我们只需要关注这两个case的分支执行的代码就行了。
其中OP_ADD很简单,仅仅就是将fragment加入队列中

case OP_ADD: {
                    Fragment f = op.fragment;
                    f.mNextAnim = enterAnim;
                    mManager.addFragment(f, false);
                } break;

在BackStackRecord中有FragmentManagerImpl引用,然后在构造器的参数列表中进行赋值初始化。在FragmentManagerImpl中的addFragment方法,用来保存每次add的Fragment,并将它放在mAdded的容器中。

    public void addFragment(Fragment fragment, boolean moveToStateNow) {
        if (mAdded == null) {
            mAdded = new ArrayList<Fragment>();
        }
        if (DEBUG) Log.v(TAG, "add: " + fragment);
        makeActive(fragment);
        if (!fragment.mDetached) {
            if (mAdded.contains(fragment)) {
                throw new IllegalStateException("Fragment already added: " + fragment);
            }
            mAdded.add(fragment);
            fragment.mAdded = true;
            fragment.mRemoving = false;
            if (fragment.mHasMenu && fragment.mMenuVisible) {
                mNeedMenuInvalidate = true;
            }
            if (moveToStateNow) {
                moveToState(fragment);
            }
        }
    }

现在终于到了 庐山真面目moveToState()函数,这个函数就可以根据刚刚一顿操作赋值,然后在moveToState()函数中判断状态,最后回调Fragment相应的生命周期函数

void moveToState(final Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {
        
        if (f.mState < newState) {
            if (f.mFromLayout && !f.mInLayout) {
                return;
            }

            if (f.mAnimatingAway != null) {
                f.mAnimatingAway = null;
                this.moveToState(f, f.mStateAfterAnimating, 0, 0, true);
            }

            switch(f.mState) {
            case 0:

                f.mHost = this.mHost;
                f.mParentFragment = this.mParent;
                f.mFragmentManager = this.mParent != null ? this.mParent.mChildFragmentManager : this.mHost.getFragmentManagerImpl();
                f.mCalled = false;
                f.onAttach(this.mHost.getContext());
}

那么Fragment生命周期函数回调的第二种情况就是:通过宿主Activity生命状态改变来回调FragmentManagerImpl.moveToState函数,进而回调Fragment生命周期函数

其实就是在 FragmentActivity的每个生命周期函数里面调用 Fragment生命周期函数,比如onResume()

protected void onResume() {
        super.onResume();
        this.mHandler.sendEmptyMessage(2);
        this.mResumed = true;
        this.mFragments.execPendingActions();
}


public boolean execPendingActions() {
        return this.mHost.mFragmentManager.execPendingActions();
}
public boolean execPendingActions() {
        if (this.mExecutingActions) {
            throw new IllegalStateException("FragmentManager is already executing transactions");
        } else if (Looper.myLooper() != this.mHost.getHandler().getLooper()) {
            throw new IllegalStateException("Must be called from main thread of fragment host");
        } else {
            boolean didSomething = false;

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

                    numActions = this.mPendingActions.size();
                    if (this.mTmpActions == null || this.mTmpActions.length < numActions) {
                        this.mTmpActions = new Runnable[numActions];
                    }

                    this.mPendingActions.toArray(this.mTmpActions);
                    this.mPendingActions.clear();
                    this.mHost.getHandler().removeCallbacks(this.mExecCommit);
                }

                this.mExecutingActions = true;

                for(int i = 0; i < numActions; ++i) {
                    this.mTmpActions[i].run();
                    this.mTmpActions[i] = null;
                }

                this.mExecutingActions = false;
                didSomething = true;
            }

            this.doPendingDeferredStart();
            return didSomething;
        }

然后回调到 moveToState函数,然后一顿判断回调 performResume() (fragment.performResume)

void moveToState(final Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {

        if (f.mState < newState) {
            if (f.mFromLayout && !f.mInLayout) {
                return;
            }

            if (f.mAnimatingAway != null) {
                f.mAnimatingAway = null;
                this.moveToState(f, f.mStateAfterAnimating, 0, 0, true);
            }

            switch(f.mState) {
            case 4:
                if (newState > 4) {
                    if (DEBUG) {
                        Log.v("FragmentManager", "moveto RESUMED: " + f);
                    }

                    f.performResume();
                    f.mSavedFragmentState = null;
                    f.mSavedViewState = null;
                }
            }
        }

    }

6: Fragment中  add()  replace(), remove(), hide() 函数源码分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值