Android-Fragment源码分析

1,071 阅读5分钟

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的生命周期同步的位置。