Android解惑 - 为什么要用Fragment.setArguments(Bundle bundle)来传递参数_Tibib的专栏-CSDN博客_fragment setarguments为什么要用Fragment.setArguments(Bundle bundle)来传递参数https://blog.csdn.net/tu_bingbing/article/details/24143249Fragment运行机制源码分析(一)_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() 函数源码分析