In the beginning, the Activity was officially provided as a carrier for the UI, so we had no choice but to use it. After Android 3.0, Fragment also appeared. At first, it was used to adapt tablet. Take the adaptation of mailing list and details as an example. For this adaptation, the list page and the detail page must be in the same Activity, and this is where I know fragments are born. However, with the continuous improvement of the Fragment framework, the architecture of single Activity and multiple fragments has been proposed. Even now, the official Navigation library is in this mode, which shows how much the official favor of fragments.

Of course, now we have more than just these two options for writing UI. Reative Native, Flutter, and future compose provide us with many more options for writing UI, and we can learn more and more things. Today we’re only going to talk about activities and fragments. For the others, I’m just looking forward to the coming of the Compose era.

Activity

One of the most common mistakes you can make when it comes to an Activity is forgetting to register it in the AndroidManifest, and then forgetting to register it and compile it again until it fails to run. For the use of activities, we need to pay attention to the life cycle, start mode, setContentView, we also need to master a key knowledge is SaveState.

SaveState does not exist on iOS or Web, so it is almost unique to Android. Because in the early days of Android, the View was very underrated. In the eyes of Android, View is cheap, data and state are precious, View can be destroyed at any time, and then restore according to the state. For example, in the scenario of vertical/horizontal rotation or Dark Mode switch, Android will destroy the current Activity and then create it again. During the re-creation, it will read the resources under the current configuration, such as font size and color, from the Resource. These resources can be configured to different values in different files according to different states, so we do not need to write various judgments. But the problem is, when the UI is displayed, most of the time there is a data rendering or state change, so how do we recover our data after the destruction and reconstruction? This is the SaveState mechanism: when we destroy, we save the data, and when we rebuild, we recover the data.

Of course, you can also choose not to let the system rebuild your Activity and handle configuration changes yourself. To do this, add the configuration you want to handle yourself to the Activity’s configChanges property in the AndroidManifest file. Such as:

android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode"
Copy the code

For details about configuration items, see the official documents. For example, orientation is orientation, and uiMode can be used to receive Dark Mode switches. When these are configured. We can accept configuration changes in the Activity by overriding onConfigurationChanged.

Let’s go back to SaveState. When an Activity is killed by the system due to configuration changes or memory problems, onSaveInstanceState is called to save the UI state. By default, the Activity iterates through the View and collects the state using the View ID as the key. Of course, each View can override onSaveInstanceState to change the default behavior. For example, RecyclerView/ListView prevents the child View state from being saved. So there’s a problem here. What if two Views with the same ID appear in the same Activity? State overwriting can occur and state recovery can be chaotic. Don’t think it won’t happen. Let me list the possible scenarios and see if you have one.

  1. Now the interface is getting more and more complicated. SameActivityMay be used inViewPagerViewPagerThe phenomenon, and many people from the ID is also very random, not business, are calledviewPager“, and then you see, I originally selected the second TAB, but as soon as the screen rotates, it becomes the third TAB. That’s whyViewPagerWill save the current selected TAB, multiple with the same IDViewPagerThe latter overwrites the former in state saving, and is always the last state selected by the ViewPager after recovery.
  2. There are many people switchingFragment“, use add instead of replace. As a result, there may be many at the same timeFragmentCoexistence, every one of themFragmentThe interface representation is oneViewIn addition to intangibly increasing the system layout and rendering time, it is also possible for the state to be saved in recovery errors, such as eachFragmentThere is aRecyclerViewAnd then its ID isrecyclerView, so that there are many of the same ID on the interfaceRecyclerViewAnd theRecyclerViewThe scroll position is saved when the state is saved, so when the screen is rotated, all of yourRecyclerViewThe state was restored to be the last one savedRecyclerViewIt will scroll to the beginning or some weird position.
  3. inRecyclerViewBefore we mature, we do horizontal page scrolling, basically usedViewPagerDo it (again) if weViewPagerThe same XML is used for every Pager on the web, and this can happen again. Imagine that you have a progress bar for each Pager, and when the screen rotates, the progress gets all messed up. So now you know why.RecyclerViewThere is no such problem because it interceptsSaveStateThe UI rendering is entirely determined by the Adapter data.

In a complex UI, all kinds of weird state problems are easy to occur, but the essence of the problem is the same, so people familiar with the UI will quickly recognize the problem in such a scenario and fix it. It would be nice if you could write your UI with this in mind and choose an arbitrary ID.

This also reveals whether we are considering any of these issues when making custom views. Frankly, QMUI has not done a good job in this area. We put more emphasis on adaptation and recommend configChange to get around the problems in StateSave.

Finally, if we have a large amount of data, this StateSave is certainly does not meet the demand, this time, the Activity provides a way to save the state onRetainNonConfigurationInstance irrelevant data, If you’re using a ViewModel, you don’t need to worry about this at all. You’ll use the same ViewModel after your Activity destroys and rebuilds, so use the ViewModel early. There’s more to it than meets the eye.

Fragment

Fragment is lighter than Activity and has many features, but it is more complex to use and has more pit points.

The first point is that it has two life cycles, one for the Fragment and the other for the fragment-managed View. Why are there two? As mentioned earlier, Android is UI light, so when you switch from FragmentA to FragmentB, you release the View managed by FragmentA, and when you return from FragmentB to FragmentA, you rebuild the View. So fragment.onCreateView (three arguments) and fragment.ondestoryView () will be called multiple times, which is probably a bit confusing for many people.

The destruction of the View will happen when we switch fragments, so we also need to make StateSave, therefore, Fragment onSaveInstanceState and onViewStateRestored are basically tied to the View lifecycle, not just when the screen rotates or Dark Mode switches. Of course, in general, when an Activity is destroyed and rebuilt, the Fragment will also be destroyed and rebuilt. But if you call the fragments. SetRetainInstance (true), it will go onRetainNonConfigurationInstance Activity, and won’t be the destruction of reconstruction, But the View’s life cycle is still going to be destroyed and rebuilt.

Another aspect of the Fragment life cycle is that you can’t manipulate the UI as you would in an Activity. If you do something to the UI after onDestoryView, then when the View is rebuilt, all of that is wasted. This is where viewModels and LiveData become particularly important, allowing you to render data to the UI in the correct life cycle. The Fragment has two Lifecircleowners. If it is related to UI, We should use the viewLifeCircleOwner and call the LiveData observe method in onActivityCreated.

Another problem with fragments is the transition animation. If data rendering occurs during the transition animation, animation freezes because data rendering causes a UI relayout, while the Activity is a Window animation and is not affected by this. By contrast, perhaps the Activity’s animation is smoother. There is no way to solve the Fragment problem except to avoid rendering data during animation, so QMUI provides a runAfterAnimation method.

Fragments are managed using the FragmentManager, and FragmentTransaction is used to add, remove, and switch fragments. Of course fragments provide Child FragmentManager, which allows you to add Child Fragments layer by layer. When an Activity switches to a different life-cycle state, it is sent to all fragments along the FragmentManager. This is useful, for example, when doing a Pager for a ViewPager, we can use either a custom View or a Fragment. However, when Pager needs to do something with onResume, onPause, etc., we need to write a lot of code to control it with a custom View, and we can easily use this feature with fragments. Therefore, FragmentPagerAdapter is also provided. If we have some common business UI components, we might as well encapsulate them to facilitate lifecycle management.

QMUI adds functionality to activities and fragments

QMUI arch library adds some new functionality to activities and fragments.

The first is gesture return, which comes with iOS but requires developers to implement on Android. One of the main reasons is that Android insists that views can be created and destroyed at any time. Take FragmentA as an example. When we switch from FragmentA to FragmentB, the FragmentA View has been destroyed, and the gesture will return with no access to the original View. So by default, you can’t implement gesture returns like iOS. So we have to do it. In the case of fragments, QMUI is simply storing the View created by the Fragment as a member variable on the QMUI layer and passing it directly to the cached View the next time it is created. This directly breaks the original official logic and introduces new concepts. For example, we may add sub-views to TopBar in onActivityCreated, but since the View is cached, there will be a problem of repeated addition. So QMUI also provides onViewCreated(a parameter) to circumvent this problem. Fragment gestures make extensive use of emission to change the data recorded in the FragmentManager, based on insertion points found after reading the FragmentManager source code. In general, this implementation has version-compatibility issues: If the implementation is officially updated, my reflection will fail, but currently the Fragment framework is stable and will not easily change the core function, so the impact is actually ok.

Gesture return compared to Fragment. The gesture return implementation of an Activity is much simpler, but there are some problems that can’t be solved. At present, there are three mainstream implementation methods:

  1. Gesture return willActivityMake it transparent so you can see behind itActivity. But calling transparentActivityThe method is reflection, and more time-consuming, and behindActivityYou can’t move. You can’t do parallax.
  2. The gesture will return when the previous oneActivityRemove the View and add it to the currentActivityView of the lower layer. Put the View back after the gesture returns. This scheme existsViewWindowAdd and remove, and cannot handle behindActivityHave a showDialogThe scene.
  3. The gesture will return when the previous oneActivityView and Dialog View drawn to the currentActivityBehind this is the scheme provided by QMUI, the performance may be optimal because there is just more drawing, but the problem is if the previous oneActivityonPauseIf you change the display of the View later, it may behave incorrectly when the gesture is returned (such as the DraweeView provided by FaceBook). In addition, it does not support SurfaceView well. For this scenario, it is better to directly disable the gesture return. Other gesture returns don’t support SurfaceView very well either.

When Android 10 comes out, it comes with a new Navigation Gesture, which many native phones already do. In this case, the system’s Gesture return takes precedence over our Gesture return, and this Gesture return becomes a bit of a chore (although the interaction must be more systematic, More iOS friendly).

Some of the other features that QMUI provides are just util functions. StartFragment, startFragmentAndDestroyCurrent, etc, for example, after A is A specialized implementation, for the official design, they think we should follow the user behavior, I’m from A click to enter B, then into C, So if I return yes, I should go from C to B, and then back to A. For example, if I share an article with a user, I will first open the user list, select a user, and then enter the user’s chat interface. When I return from the chat screen, I should return to the article screen, not the user list screen in the middle. And our fragments did not finish way to end his Activity, this is the value of the existence of startFragmentAndDestroyCurrent.

Also need to mention is FragmentManager popBackStack () this method, it means that the current fragments removed from the stack, our return is through it, but it can not call at any time, OnCreate is fine if the user is actively clicking on it, but if you want to call it in a fragment. onCreate, it won’t work, even if it’s in an onResume. If you want to return to the list of users immediately popBackStack to replace startFragmentAndDestroyCurrent, that is will fail, if you are interested, you can try it yourself, official fragments will crash out directly, QMUIFragment ignores this execution. The reason for this is that the state machine in FragmentManager is assigned its current state after onResume. This is a very common reentrant error, and you can guess what it is after I write a similar example:

interface Observer{
    fun doSomething()
}

class Observable{
    private val observers = arrayListOf<Observer>()
    
    fun addObserver(observer: Observer){
        observers.add(observer)
    }
    
    fun removeObserver(observer: Observer){
        observers.remove(observer)
    }
    
    fun dispatch(){
        val count = observers.size
        for(i in 0 until count){
            observers[i].doSomething()
        }
    }
}
Copy the code

The above code is very simple, is the use of observer mode, can you see the error?

Suppose my call looks like this:

val observable = Observable()
val secondObserver = object : Observer {
    override fun doSomething() {/ /... } } val firstObserver = object : Observer { override fundoSomething() {
        observable.removeObserver(secondObserver)
    }
}
observable.addObserver(firstObserver)
observable.addObserver(secondObserver)
observable.dispatch()
Copy the code

If you run this code, you will find quote IndexOutOfBoundsException, this is because we are the first Observer the second Observer will be removed, This causes an error count in the for loop in Observable.dispatch. Our fragment.onResume is also in this phase, and if you remove the Fragment from there, it may cause some variable values in the FragmentManager to fail. Of course, there are protections in FragmentManager that don’t allow you to perform certain operations, and when you do, you throw an error, terminating the entire execution.

The QMUI Arch framework is also currently trying to introduce annotations to simplify code, such as FirstFragment annotations, such as LatestVisitRecord annotations. I’ll detail the scenarios in which these annotations are used later in the game.

Today we mainly introduce some knowledge about activities and fragments. Knowledge points are relatively scattered, we need to use in the process of continuous learning and mastery. Besides, we are not doing multiple choice, but both. Introducing Fragment in appropriate scenes can greatly reduce workload. For example, in a plug-in scenario, we don’t need to solve the Activity registration problem. SaveState, ViewModel, gesture return and so on have buried a lot of details. If we want to skillfully control it, we need to be familiar with its various pits and the causes of pits. Novice often write point UI to see the effect, this is extremely unfamiliar to each control reason. If you can write half a day of code, compile it once, and get the results you want, then your ability to write a UI is a real step up.

GankWithQmui will adopt the architecture of single Activity and multiple fragments, which is my favorite architecture. Next time we’ll start building our first Fragment.

QMUI Combat (3) – How do you activate your first Fragment?