Fragment的概念
由于每个页面都要提供一个Activity来展示页面,这样在某些场景下可能太重量级了,比如说频道之间的切换,所以从Android3开始,提供了Fragment。Fragment可以理解成一种更小粒度的Activity,Fragment自己管理着一个contentView,拥有着Activity的生命周期,并且还有自己的一些额外的生命周期。在Activity的视图中,可以把一个View上的Fragment切换成另一个以实现页面的切换与回退。Fragment被官方翻译为碎片,可以使用Fragment来拼接成一个完整的页面。例如pad界面的聊天,左边是联系人列表,右边是聊天框,在左边选择联系人时,可以动态地更换右边的聊天框View,而不是整个页面切换。
v4.Fragment和app.Fragment的区别
app.Fragment是Android源码中提供的,但需要系统支持Android3.0以以上,而v4.Fragment是support包提供的(当然也可以AndroidX),是为了向下兼容,最低支持Android1.6。当然,为了使用v4.Fragment,需要Activity继承自FragmentActivity,而且FragmentManager也得使用getSupportFragmentManager 来获取。v4.Fragment和app.Fragment在api层面没有什么大的区别。
Fragment Demo
Activity的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/btnLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="left"/>
<Button
android:id="@+id/right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="right"
android:layout_marginLeft="50dp"
android:layout_toRightOf="@id/left"
/>
</RelativeLayout>
<FrameLayout
android:id="@+id/content_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/btnLayout"/>
</RelativeLayout>
Fragment代码
package com.benson.android.fragment;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class DemoFragment extends Fragment {
public static final String BACKGROUND_COLOR = "background_color";
public static final String TEXT = "text";
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
TextView view = new TextView(getContext());
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
layoutParams.gravity = Gravity.CENTER;
view.setLayoutParams(layoutParams);
view.setBackgroundResource(getArguments().getInt(BACKGROUND_COLOR, android.R.color.holo_red_light));
view.setText(getArguments().getString(TEXT, "demo"));
view.setTextSize(15.F);
view.setGravity(Gravity.CENTER);
return view;
}
}
Activity代码
package com.benson.android.fragment;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
public class DemoFragmentActivity extends AppCompatActivity implements View.OnClickListener {
private DemoFragment fragment1, fragment2, fragment3;
private int current = 0;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dem_fragment_activity);
findViewById(R.id.left).setOnClickListener(this);
findViewById(R.id.right).setOnClickListener(this);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.content_fragment, getFragment1());
transaction.commitAllowingStateLoss();
}
private Fragment getFragment1() {
if (fragment1 == null) {
fragment1 = new DemoFragment();
Bundle args = new Bundle();
args.putInt(DemoFragment.BACKGROUND_COLOR, android.R.color.holo_red_light);
args.putString(DemoFragment.TEXT, "fragment1");
fragment1.setArguments(args);
}
return fragment1;
}
private Fragment getFragment2() {
if (fragment2 == null) {
fragment2 = new DemoFragment();
Bundle args = new Bundle();
args.putInt(DemoFragment.BACKGROUND_COLOR, android.R.color.holo_green_light);
args.putString(DemoFragment.TEXT, "fragment2");
fragment2.setArguments(args);
}
return fragment2;
}
private Fragment getFragment3() {
if (fragment3 == null) {
fragment3 = new DemoFragment();
Bundle args = new Bundle();
args.putInt(DemoFragment.BACKGROUND_COLOR, android.R.color.holo_blue_light);
args.putString(DemoFragment.TEXT, "fragment3");
fragment3.setArguments(args);
}
return fragment3;
}
private Fragment getFragment() {
switch (current) {
case 0:
return getFragment1();
case 1:
return getFragment2();
case 2:
return getFragment3();
default:
throw new IllegalStateException("current is error.");
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.left) {
current = ((--current) + 3) % 3;
} else if (v.getId() == R.id.right) {
current = (++current) % 3;
}
current = Math.abs(current);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.content_fragment, getFragment());
transaction.commitAllowingStateLoss();
}
}
这里用v4.Fragment来演示如果加载一个Fragment,并且如何在多个Fragment之间切换。 用app.Fragment实现的话,只需要把DemoFragment的继承父类换成app.Fragment,把DemoFragmentActivity中的getSupportFragmentManager()换成getFragmentManager()即可。 官方已经把Activity#getFragmentManager设置为Deprecated了,并添加了link到v4.Fragment,这说明Google爸爸不想让我们再用app.Fragment了,而是希望我们用v4.Fragment。
Fragment源码分析
由于Google爸爸都建议我们使用v4.Fragment了,那就只分析一下v4.Fragment的源码吧。
Fragment的创建过程没有什么特别的,和普通类一样,直接new就可以,构造器中也可以传任意参数,这是因为Fragment和Activity不同,Activity是系统类,在Manifest中注册,由AMS管理,而Fragment被称为碎片,在Activity中创建,那么Fragment的初始化过程应该就是commit了。
FragmentActivity对是v4.Fragment框架的入口,有getSupportFragmentManager方法返回一个FragmentManager对象,一路追踪,可知返回的是一个FragmentManagerImpl对象。
&emps; 初始化一个Fragment过程大概如下
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.content_fragment, getFragment());
transaction.commitAllowingStateLoss();
FragmentManagerImpl的beginTransaction方法返回了new出来的BackStackRecord对象,其replace方法在其父类FragmentTransaction中实现,调用了doAddOp方法。在这个方法中将传入的fragment的mContainerId和mFragmentId赋值为传入的桩FragmentLayout的id,最后调用了addOp方法,addOp方法只是将传入的Op对象存入FragmentTransaction的mOps列表中这应该算是一种命令模式,在ReactNative中也有用到过。
最后调用了commitAllowingStateLoss方法,其实所有的commit相关方法最后都是调用了其commitInternal方法。在commintInternal方法中,第一行就判断了是否已经commit过了,如果已经commit过,再commit则会抛一下IllegalStateException。最后一行调用了mManager.enqueueAction(this, allowStateLoss),去看看实现最关键的调用是mPendingActions.add(action)和scheduleCommit();这里相当于启动刚才添加到列表中的命令的调度过程。scheduleCommit中将FragmentManagerImpl中名为mExecCommit的Runnable使用Handler发送到main线程。mExecCommit的run方法调用了FragmentManagerImpl的execPendingActions方法,将pendingActions中的Action都执行一遍其generateOps方法。其实现也比较简单,就是将BackStackRecord添加到FragmentManagerImpl的mTmpRecords并标识当前Fragment有没有被Pop。while得到true后,执行removeRedundantOperationsAndExecture,通过一系列调用,调到moveToState方法。
使用Fragment过程中,线上总喜欢报moveToState这个方法的异常,这应该就是Fragment框架最核心的一个调度方法。在这个moveToState方法中,有个FragmentManagerImpl常用到的字段mCurState,FragmentManagerImpl中有一些dispatchCreate、dispatchState、dispatchResume等方法,这些方法都是在FragmentActivity中Activity的相关生命周期中调用的。这些dispatch的生命周期方法,都调用了dispatchStateChange方法,来更改mCurState的值并执行execPendingActions方法,看来FragmentManagerImpl的mCurState就是用来同步Activity的生命周期的。moveToState的方法中,对Fragment的mState进行switch。
其中Fragment.INITIALIZING是用来初始化Fragment的,将FragmentActivity赋值给Fragment的mHost,Fragment的getActivity就是将Fragment的mHost变量强转成Activity的。调用了Fragment的performAttach方法,这将会调用Fragment的onAttach方法。然后判断Fragment是否已经create,刚创建的Fragment还未create,就调用Fragment的performCreate,这将会调用Fragment的onCreate方法,并且将通过Fragment的Lifecycle将状态转化成ON_CREATE状态。
注意到Fragment.INITIALIZING这个case分支是没有break的,所以会进入Fragment.CREATED这个case分支。这时候的状态还是Fragment.CREATED,所以Fragment.CREATED这个case分支什么都没有执行。
Fragment.CREATED这个分支也没有break,所以也会继续Fragment.ACTIVITY_CREATED这个分支,newState没有大于Fragment.ACTIVITY_CREATED,所以继续执行下一个分支。这样会把switch走完,这里的策略其实是状态当前状态,当前Activity的生命周期走到哪,就需要Fragment的生命周期也走到哪,之所以不加break,是想让Fragment和Activity的生命周期同步,这也是为啥,在任何Activity的生命周期中初始化一个Fragment,这个Fragment的相应生命周期都会调用一遍,直到走到和Activity的生命周期同步的位置。