This article has been published exclusively by guolin_blog, an official wechat account

1. What is Fragment?

Fragment is the Android View life cycle controller (a custom View with a life cycle). It is part of the View hierarchy on an Activity and can be considered a lightweight Activity. It takes up fewer resources than traditional activities, and offers greater coding flexibility, compatibility on ultra-low versions, and so on.

Using fragments will work well even on clunky platforms (such as older systems with no ART up to API 19) and will allow transition animations to be compatible with lower versions (specified by FragmentTransition).

Early fragments had a number of issues, such as no onBackPressed(), no startup mode, duplicate creation, hotchback stack, puzzle lifecycle, etc., leading many open source authors to develop their own frameworks for Fragment management. One of the most famous is called Fragmentation by YoKeyword.

However, things always develop in twists and turns. After years of teaching by Google, the function of Fragment has been very perfect and can replace the existence of Activity in many occasions. Some of the problems above have been properly solved.

Troll JakeWharton once suggested that an App only needs one Activity.

This refers to the single Activity and multiple fragments mode. There are many benefits to using this pattern:

  • First of all, the first benefit is fluency, you knowActivityBelongs to a system componentAMSManagement and itself is oneGod Object(God object,ActivityIs too powerful to be coupledViewLayer and theModelLayer), its overhead is very large, singleActivityMode can save us a lot of resources, but also can avoid resources when insufficient by the foregroundActivityCovered by theActivityA situation where page data is lost due to being killed (because there is only one)ActivityunlessJAVAHeap memory reaches the point where the system has to kill a program, or else the system is least inclined to kill the foreground running programActivity);
  • The second is that business logic can be broken down into smaller modules and reused in combination, which is especially important in large software systems (new edition)zhihuI used sheetsActivitymoreFragmentThe pattern), because we all knowActivityIs not reusable across multiple pages, and at this pointFragmentThat’s where it comes in. It’s lightweightActivity, basically can act as agentActivityThe work, and it is reusable
  • Furthermore, useFragmentCan bring more flexibility to the program, we all know inActivityTo pass objects, objects need to be serialized becauseActivityAs a system component, is subjected toAMSManaged, whileAMSA system process that is not in the current program running process and startsActivityYou need to temporarily leave the current process toAMSIn the process of, whileAMSThe data you have prepared (i.eIntentEtc.) to startActivity, it is also aFragmentandActivityOne of the differences between,ActivityIs a system component that can run in another process (componentized/multi-process scenario), whileFragmentIt’s just a component that the framework provides to us that must be attached toActivityLive, and can only be used in the current process, but this also means that it can gain more flexibility that we can giveFragmentPass objects without serialization, or even toFragmentpassViewObjects like that, these are allActivityNot easy to do.

2. Things you need to know to use Fragment

First of all, if you want to learn Fragment, you must at least master the Activity. If you don’t know about Activity, I suggest you read some articles related to Activity first and then advanced Fragment. By default, the reader is already familiar with the Activity lifecycle and so on, starting with the following articles.

Fragments can be generated either by hard coding them into an XML file or by adding a Fragment in Java code with a new and then adding a Fragment by opening the FragmentTransaction commit with FragmentManager#beginTransaction (described below). . There are certain differences between the two methods. Fragments hardcoded into XML cannot be removed by FragmentTransition#remove, living or dying with an Activity, so if you use them this way, you don’t have to try. They can’t be removed, but new fragments in code can be removed.

Hardcoded directly into XML:

        <fragment
            android:id="@+id/map_view"
            android:name="org.kexie.android.dng.navi.widget.AMapCompatFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
Copy the code

The second way to add a Fragment is to add it dynamically using the FragmentManager#beginTransaction(code below). You need to create a new Fragment and obtain it from the Fragment#requireFragmentManager below Note that the first parameter of the Add method, you need to give it an ID. This is the ID of the Fragment container, usually a FrameLayout with no child views. It determines where the Fragment should be displayed.

// Write a container in XML that places the Fragment position <FrameLayout Android :id="@+id/fragment_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/> // Add Fragment requireFragmentManager().beginTransaction().add(r.i.d.fragment_container, fragment) .runOnCommit(()->{/*TODO*/}) .addToBackStack(null) .commit();Copy the code

In a Fragment, we can use getId() to return its own ID. This method is usually used to return the ID of the container in which it is located, for use when other fragments are added to or added to the current container (e.g. in the Fragment returning stack scenario).

    /**
     * Return the identifier this fragment is known by.  This is either
     * the android:id value supplied in a layout or the container view ID
     * supplied when adding the fragment.
     */
    final public int getId() {
        return mFragmentId;
    }
Copy the code

Note that FragmentTransaction is not executed immediately, but only after the current code has finished executing, when it returns to the event loop (known as Looper), but it is guaranteed to be executed before the next frame is rendered, if FragmentTransaction is executed To get things done, you need to use runOnCommit. In the above code I used the Java8 lambda expression shorthand for Runnable.

If you still want to use the Fragment to rollback the stack, call addToBackStack, and finally remember to commit, which returns the ID of BackStackEntry

Of course, FragmentTransaction can perform not only add operations, but also remove,show, and hide operations.

A brief digression is inserted to supplement the above knowledge. How do I enable Java8 in Android Studio? In your module’s build.gradle

Android {// omit..... // Add the following script and sync your project compileOptions {sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
Copy the code

OnBackPressed? This is a question I know people who are using Fragments for the first time are dying to ask. The Fragment itself is known to have no onBackPressed. It’s not that Google doesn’t design it, it’s that they really can’t manage it!! If there are three or four fragments on a screen and you press the back button, who knows which Fragment to assign to? So the Fragment itself does not have onBackPressed. However, there is actually a way to add something like onBackPressed to the Fragment, but Google has left it up to the developers to design.

This feature is entirely based on Google’s appcompat package, but you may need a later version of the appcompat package to use this feature, or you may need to migrate your project to AndroidX(see below).

You can use the addOnBackPressedCallback method of FragmentActivity(which AppCompatActivity inherits from FragmentActivity) to block OnBackPressed for your Fragment. (other versions of AndroidX other than AndroidX may also implement this feature.)

    public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
            @NonNull OnBackPressedCallback onBackPressedCallback)
Copy the code

OnBackPressedCallback#handleOnBackPressed needs to return a Boolean value. If you intercept onBackPressed in this callback, return true, indicating that you handled the return button press yourself so your Fragment won’t be bounced off the return stack.

Note that the first argument to this function, a LifecycleOwner, is LifecycleOwner. Both Activity and Fragment are LifecycleOwner, which provides the component’s lifecycle. This argument helps us automatically manage the OnBackPressedCallback callback It needs to be removed manually from the Activity, and when the LifecycleOwner ON_DESTROY event comes in, it is automatically removed from the list. You don’t have to worry about memory leaks, the framework does this for you.

/**
 * Interface for handling {@link ComponentActivity#onBackPressed()} callbacks without
 * strongly coupling that implementation to a subclass of {@link ComponentActivity}.
 *
 * @see ComponentActivity#addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
 * @see ComponentActivity#removeOnBackPressedCallback(OnBackPressedCallback)
 */
public interface OnBackPressedCallback {
    /**
     * Callback for handling the {@link ComponentActivity#onBackPressed()} event.
     *
     * @return True if you handled the {@link ComponentActivity#onBackPressed()} event. No
     * further {@link OnBackPressedCallback} instances will be called if you return true.
     */
    boolean handleOnBackPressed();
}
Copy the code

We can see that the execution of the OnBackPressedCallback managed within the Activity is sequential depending on the addition time. The last to be added gets executed first.

public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
        @NonNull OnBackPressedCallback onBackPressedCallback) {
    Lifecycle lifecycle = owner.getLifecycle();
    if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
        // Already destroyed, nothing to do
        return;
    }
    // Add new callbacks to the front of the list so that
    // the most recently added callbacks get priority
    mOnBackPressedCallbacks.add(0, new LifecycleAwareOnBackPressedCallback(
            lifecycle, onBackPressedCallback));
}
Copy the code

You can see that it was added to the top of the List of mOnBackPressedCallbacks.

Where is the startFragmentForResult method? Sorry, as with OnBackPressed,Google didn’t implement this method for us directly, but that doesn’t mean the Fragment doesn’t have this functionality. Of course, you can get the contents of the Fragment directly by defining getters, but this is not a best practice and it’s best to use public for code purposes A total of the API

Fragment#setTargetFragment Sets a target Fragment and a request code for the current Fragment

public void setTargetFragment(@Nullable Fragment fragment, int requestCode)
Copy the code

After the current Fragment has completed its task, we can send the returned value back to our target Fragment using the Intent

        getTargetFragment().onActivityResult(getTargetRequestCode(),
        Activity.RESULT_OK,new Intent());
Copy the code

Note, however, that the target Fragment and the requested Fragment must be managed by the same FragmentManager, otherwise an error will be reported

Well, if the appCompat package you’re using doesn’t have the above SAO operation. So I’m going to take you through the migration to AndroidX.

Some people here might ask what is AndroidX?

AndroidX is simply a platform unbundled library called AppCompat, which means build.gradle doesn’t need to be written as compileSdkVersion.

compile 'com.android.support:appcompat-v7:24.+'
Copy the code

Note: using 24.+ indicates using the latest version of the library starting with 24. Using the + sign directly indicates using the latest version of the library.

It can now be written as:

implementation 'androidx. Appcompat: appcompat: 1.1.0 - alpha02'
Copy the code

(Note: The new dependency method implementation has the same function as compile, but the implementation cannot reference the dependencies of dependencies in the module, but compile can, and the advantage of this is that it can be compiled faster. The new dependency API is exactly the same as Compile, just with a different name)

Migrate to AndroidX Refactor->Migrate to AndroidX Refactor->Migrate to AndroidX Refactor->Migrate to AndroidX Refactor->Migrate to AndroidX Refactor->Migrate to AndroidX However, the migration process can take a lot of time. If the project is large, the migration can take a long time. Do not shut down Android Studio even if the CPU utilization is 0, but if the migration fails, manual migration is required.

Androidx.fragment.app: androidx.fragment.app: androidx.fragment.app: androidx.fragment.app: androidX.fragment.app: androidX.fragment.app: androidX.fragment.app: androidX.fragment.app: androidX.fragment.app: androidX.fragment.app: androidX.fragment.app Android Studio will automatically replace the base class at compile time, but make sure that gradle.properties in your project has the following Settings:

android.useAndroidX=true

android.enableJetifier=true
Copy the code

To get rid of these ugly red lines, you can simply cast the new Fragment to the original Fragment using this method.

TextureSupportMapFragment mapFragment = TextureSupportMapFragment
.class.cast(getChildFragmentManager()
.findFragmentById(R.id.map_view));
Copy the code

In the same way, you can cast an old Fragment to a new Fragment.

Fragment f = Fragment.class.cast(mapFragment);
Copy the code

(note: the above TextureSupportMapFragment is a typical case, he is gold map fragments in the SDK, it itself has inherited the v4 fragments under the package, can be used on the transformation to make his compatible AndroidX)

One final tip: When we use Fragment#getActivity() we return a nullable value if there is no null check on Android A nasty yellow warning will appear in Studio, and you can replace it with requireActivity(), as well as requireFragmentManager().

Fragment life cycle

This is probably one of the most frustrating parts. It highlights one of the scariest parts of the Fragment, its life cycle.

A Fragment has all of the Activity’s lifecycle callback functions and extends some of them due to its own nature, so it can be intuitive if you are not familiar with the Fragment. For example, a Fragment will not be onPause because it is covered by other fragments on the Fragment rollback stack, or because you hide it using the FragmentTransition. OnPause is only available to the Activity that the Fragment is attached to This is clearly written in the Fragment source code.


    /**
     * Called when the Fragment is no longer resumed.  This is generally
     * tied to {@link Activity#onPause() Activity.onPause} of the containing
     * Activity's lifecycle. */ @CallSuper public void onPause() { mCalled = true; }Copy the code

So what do we do when we want to do something with the Fragment not showing? We have the onHiddenChanged callback, which is called when the Fragment’s display state is changed by FragmentTransition (hide and show). The parameter hidden tells you whether the Fragment is now hidden or displayed.


    /**
     * Called when the hidden state (as returned by {@link #isHidden()} of
     * the fragment has changed.  Fragments start out not hidden; this will
     * be called whenever the fragment changes state from that.
     * @param hidden True if the fragment is now hidden, false otherwise.
     */
    public void onHiddenChanged(boolean hidden) {
    }
Copy the code

Originally, I wanted to use ProcessOn to draw a flow chart of the Fragment life cycle, and finally…… Really sweet, because this graph is really too complicated, really want to draw it time a little hard, so I have to take doctrine.

The chart below shows the chronological order of the callbacks (see here):

Just to clarify, if you’re a little annoyed by the picture above, look at the summary below. Common callbacks include these:

  • OnInflate (Context, AttributeSet, Bundle) only hardcoded into the XML Fragment (i.e., use fragments tags) to callback the method, is similar to this with a custom View, This method is called when the XML layout is instantiated, prior to onAttach.

  • When onAttach(Context) executes this method, the Fragment and Activity are already bound. When a Fragment is added to the FragmentManager, the method is always the first to be called back unless the Fragment tag is defined directly in the XML. This method passes in a Context object, which is essentially the Activity to which the Fragment is attached. When overriding this method, remember to call super.onAttach. When the onAttach call returns, the getActivity call will not return NULL. But Activity#onCreate may not have finished executing (this will happen if it is defined in XML, because the callback will happen at the same time that you setContentView in Activity#onCreate until Fragment#on Activity#onCreate will not continue until ViewCreated returns).

  • OnCreate (Bundle) is used to initialize the Fragment. It always calls back after onAttach completes, and you can get the previously saved value with the savedInstanceState argument. Be sure to call super.onCreate from the parent class.

  • OnCreateView (LayoutInflater, ViewGroup, Bundle) needs to return a View used to initialize the layout of the fragments, it is always in the onCreate execution after the callback. By default, null is returned. Note that returning null Fragment#onViewCreated will be skipped. If you define a fragment tag in XML and specify a fragment by name, this method will not be allowed to return NULL or an error will be reported. This method may be called multiple times when using ViewPager+Fragment (paired with Fragment#onDestroyView).

  • When onActivityCreated(Bundle) executes this method, the onCreate method of the Activity bound to the Fragment has been executed and returned. If there is no guarantee that the Activity will interact with the Fragment before this method, nullpointer exceptions will be raised for references to uninitialized resources.

  • When onStart() executes this method, the Activity in which the Fragment is located changes from invisible to visible

  • OnResume () executes this method while the Fragment Activity is active and the user can interact with it.

  • OnPause () executes this method when the Fragment Activity is paused, but still visible, and the user cannot interact with it, such as the Dialog covering the Activity

  • When onStop() executes this method, the Activity in which the Fragment is located is completely invisible

  • OnSaveInstanceState (Bundle) Saves the current Fragment state. This method automatically saves the state of the Fragment, such as the text typed in EditText, even if the Fragment is recycled and recreated, It also allows you to restore the text you typed before EditText, which I don’t really like, and it’s a silly design to save in the Bundle, but there’s an alternative,Google’s Android Jetpack MVVM framework, which I’ll cover in a later article.

  • OnDestroyView () destroys views associated with a Fragment, but does not unbind the Fragment from the Activity. It normally unreferences the Fragment from the view in this callback. This method is usually overwritten in ViewPager+Fragment mode, and can be multiple times as well as Fragment#onCreateView.

  • OnDestroy () Destroys the Fragment. This method is usually called when the Back key is pressed to exit or the Fragment is removed from the FragmentManager, at which point all data managed in the Fragment should be cleaned up, and it will be called Back before onDetach.

  • OnDetach () unbinds the Activity. Called after the onDestroy method. At the end of the Fragment life cycle, if you get getActivity() after super.ondetach returns, you’ll get a null.

4. Alternatives to Fragments

After reading so much about fragments, if you still despise them and want to reduce the granularity of your business logic, I can only offer you an alternative to fragments.

View Frame Flow, an alternative to Fragments developed by an engineer at Square (the same company that created Retrofit and OkHTTP), and a blog post about it. A good short book writer in The country translated the article why I Don’t Advocate Using Fragments, original author I think you might like this article, which lambasted the various shortcomings of Fragments.

5. Conclusion

Well, that’s about all I have to say about moving from Activity to Fragment. I’ll write whatever COMES to mind, so the structure of the article might be a bit confusing (escape……). If there are other knowledge points in the future, I will slowly add up.

Give me a “like” if you like my article, please, it really means a lot to me.