FragmentActivity+Fragment After a long period of time in the background, the App will crash when it wakes up from the desktop or the latest task list. This is usually caused by the App being killed in the background, and the App will encounter problems when it resumes. For example, if the Fragment does not provide a default constructor, it will crash when the Fragment fails to be created by reflection. For example, if you create a new FragmentDialog in onCreate and show it, it will show two dialogs when it is killed in the background and woken up again. Why is that? In fact, this involves the mechanism of background kill and recovery.

FragmentActivity resumes logic after it is killed by the background

When the App is killed by a background exception and you click the icon again, or when you enter from the recent task list, the system will help restore the scene and recreate the Activity. For FragmentActivity, the logic is more complicated because of Framgent. The system will rebuild the destroyed Fragment first. Look at the onCreat code for FragmentActivity:

When we create an Activity, create and show a DialogFragment in the onCreate function, and then somehow kill the APP (RogueKiller emulates the background kill tool), and again invoke the APP from the recent task, You will find two Dialogfragments displayed as follows:

public class DialogFragmentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DialogFragment dialogFragment = new FragmentDlg();
        dialogFragment.show(getSupportFragmentManager(), "");
    }
Copy the code

The onCreate function is executed only once, but the onCreate function is executed only once. Why there are two dialogfragments? In fact, there is a DialogFragment that is reconstructed by Android’s own recovery and reconstruction mechanism. The savedInstanceState parameter of onCreate(Bundle savedInstanceState) is not null in the case of an exception kill. Instead, it contains the scene information saved when the onCreate(Bundle savedInstanceState) is killed. For a crash example, create a new CrashFragment and discard the default no-argument constructor:

public class CrashFragment extends Fragment { public CrashFragment(String tag) { super(); }}Copy the code

Then Add or replace to the Activity to Add the CrashFragment. After the CrashFragment is displayed, simulate the background killing using RogueKiller simulation tool. When the App is recalled from the recent task list, it will crash.

Caused by: android.support.v4.app.Fragment$InstantiationException: 
  Unable to instantiate fragment xxx.CrashFragment: 
  make sure class name exists, is public, and has an empty constructor that is public
		at android.support.v4.app.Fragment.instantiate(Fragment.java:431)
		at android.support.v4.app.FragmentState.instantiate(Fragment.java:102)
		at android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1952)
		at android.support.v4.app.FragmentController.restoreAllState(FragmentController.java:144)
		at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:307)
		at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:81)
Copy the code

Super.oncreate (savedInstanceState) does a lot of things that we don’t see when it recovers. Let’s look at crashes:

Why does a Fragment without a no-argument constructor cause a crash

Take a look at the following onCreate code for FragmentActivity in support-V4:

protected void onCreate(@Nullable Bundle savedInstanceState) { mFragments.attachHost(null /*parent*/); super.onCreate(savedInstanceState); .if(savedInstanceState ! = null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, nc ! = null ? nc.fragments : null); } mFragments.dispatchCreate(); }Copy the code

You can see if savedInstanceState! . = null, executes mFragments restoreAllState logic, logic, here is actually involved in recovery reconstruction was killed by the background anomaly before again, or before the Activity’s onStop execution, Both the Activity and Fragment scenes are already saved in ActivityManagerService as FragmentState. When recreating the Fragment, reflection is used to recreate the Fragment

void restoreAllState(Parcelable state, List<Fragment> nonConfig) {
 
 	 ...
         for (int i=0; i<fms.mActive.length; i++) {
        FragmentState fs = fms.mActive[i];
        if(fs ! = null) { Fragment f = fs.instantiate(mHost, mParent); mActive.add(f); .Copy the code

It’s basically calling Instantiate of FragmentState, then instantiate of Fragment, and then reflection to build the Fragment, that is, the Fragment that was added to the FragmentActivity, Will be automatically created and will take the Fragment’s default no-argument constructor, otherwise it will throw a InstantiationException, which is why the crash occurred in the second example.

*/ public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) { try { Class<? > clazz = sClassMap.get(fname);if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it clazz = context.getClassLoader().loadClass(fname); sClassMap.put(fname, clazz); } Fragment f = (Fragment)clazz.newInstance(); if (args ! = null) { args.setClassLoader(f.getClass().getClassLoader()); f.mArguments = args; } return f; } catch (ClassNotFoundException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); } catch (java.lang.InstantiationException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); } catch (IllegalAccessException e) { throw new InstantiationException("Unable to instantiate fragment " + fname + ": make sure class name exists, is public, and has an" + " empty constructor that is public", e); }} the errormsg in the Fragment is the same as the exception thrown in the Fragment.  /** * Default constructor. Every fragment must have an * empty constructor, so it can be instantiated when restoring its * activity's state.  It is strongly recommended that subclasses do not
 * have other constructors with parameters, since these constructors
 * will not be called when the fragment is re-instantiated; instead,
 * arguments can be supplied by the caller with {@link #setArguments}
 * and later retrieved by the Fragment with {@link #getArguments}.
 * 
 * <p>Applications should generally not implement a constructor.  The
 * first place application code an run where the fragment is ready to
 * be used is in {@link #onAttach(Activity)}, the point where the fragment
 * is actually associated with its activity.  Some applications may also
 * want to implement {@link #onInflate} to retrieve attributes from a
 * layout resource, though should take care here because this happens for
 * the fragment is attached to its activity.
 */
 
public Fragment() {}Copy the code

The idea is that Fragments must have an empty constructor so they can rebuild, and subclasses of fragments don’t recommend constructors with parameters. Better to hold parameters in setArguments. Let’s see why there are two Dialogfragments.

Why are there two DialogFragments

After a Fragment is created, it will not be displayed if it is not added to the Activity’s layout by adding or replacing it. When you kill DialogFragmentActivity in the background, or rotate the screen, two FragmentDialogS appear, one restored by the system and one newly created.

Add a Fragment and display the principle – the so-called Fragment lifecycle

Normally we use the Fragment method for fragmentActivities as follows: let’s say in the onCreate function:

@Override
protected void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
		Fragment fr = Fragment.instance("")
		getSupportFragmentManager().beginTransaction()
		.add(R.id.container,fr).commit();
Copy the code

The getSupportFragmentManager return is a subclass of FragmentManager FragmentManagerImpl, FragmentManagerImpl FragmentActivity is an inner class, The Fragment management logic is handled by the FragmentManagerImpl. This article is based on 4.3. Later versions introduce FragmentController, which is only a layer of encapsulation.

public class FragmentActivity extends Activity{ ... final FragmentManagerImpl mFragments = new FragmentManagerImpl(); . final FragmentContainer mContainer = newFragmentContainer() {
        @Override
        @Nullable
        public View findViewById(int id) {
            return FragmentActivity.this.findViewById(id);
        }

        @Override
        public boolean hasView() {
            Window window = FragmentActivity.this.getWindow();
            return(window ! = null && window.peekDecorView() ! = null); }};Copy the code

The FragmentManagerImpl beginTransaction() function returns a BackStackRecord()

@Override
public FragmentTransaction beginTransaction() {
    return new (this);
}
Copy the code

As the name suggests, beginTransaction generates a Transaction for the FragmentActivity, which can either be executed or reversed, as a basis for unstacking. The FragmentTransaction add function implements the following.

public FragmentTransaction add(Fragment fragment, String tag) {
    doAddOp(0, fragment, tag, OP_ADD); // Async, similar to Handerreturn this;
}
 
 
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { fragment.mFragmentManager = mManager; . Op op = new Op(); op.cmd = opcmd; op.fragment = fragment; addOp(op); }Copy the code

Commit the Transaction, insert the Transaction into the Transaction queue, and eventually call the FragmentManager addFragment method. Add FragmentManagerImpl to the FragmentManagerImpl maintenance Fragment list and adjust the Fragment to the appropriate state based on the current Activity state as follows:

public void addFragment(Fragment fragment, boolean moveToStateNow) {

    if (mAdded == null) {
        mAdded = new ArrayList<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); }}}Copy the code

The FragmentManager is the core of the FragmentActivity management Fragment.

final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory {
	...
	
    ArrayList<Runnable> mPendingActions;
    Runnable[] mTmpActions;
    boolean mExecutingActions;
    
    ArrayList<Fragment> mActive;
    ArrayList<Fragment> mAdded;
    ArrayList<Integer> mAvailIndices;
    ArrayList<BackStackRecord> mBackStack;
Copy the code

The FragmentManagerImpl maintains a list of all managed fragments for the FragmentActivity. The State of the FragmentManagerImpl is the same as the State of the Activity. This is the key to managing fragments. The Fragment itself does not have a life cycle. It is simply a View wrapper that relies entirely on FragmentManagerImpl to simulate the life cycle synchronously. For example, after creating the Fragment in onCreate, add, The Fragment’s onCreateView is not executed until the onCreateView of the Activity itself is executed. That is, the Fragment is passive, consistent with the FragmentActivity. Since a Fragment is just a View wrapped in a Container, how does it get converted to a View and added to a Container? The key is the moveToState function, which forces the life cycle of a newly added Fragment to be synchronized with the Activity:

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
        boolean keepActive) {
        ...        
     if(f.state < newState) {// Lower than the current State of the Activity switch (f.state) {caseFragment.INITIALIZING: ... f.mActivity = mActivity; f.mParentFragment = mParent; f.mFragmentManager = mParent ! = null ? mParent.mChildFragmentManager : mActivity.mFragments; f.mCalled =false; f.onAttach(mActivity); .if(! f.mRetaining) { f.performCreate(f.mSavedFragmentState); }case Fragment.CREATED:
                if (newState > Fragment.CREATED) {
            
                      f.mView = f.performCreateView(f.getLayoutInflater(
                      f.mSavedFragmentState), container, f.mSavedFragmentState);
                      f.onViewCreated(f.mView, f.mSavedFragmentState);
                 
                    f.performActivityCreated(f.mSavedFragmentState);
                    if(f.mView ! = null) { f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; }case Fragment.ACTIVITY_CREATED:
            case Fragment.STOPPED:
                    if (newState > Fragment.STOPPED) {
                        f.performStart();
                    }
            case Fragment.STARTED:
                if (newState > Fragment.STARTED) {
       	           f.mResumed = true;
                    f.performResume();
Copy the code

After adding a Fragment, you need to keep the State of the Fragment consistent with the current Activity. Now, to return to the background kill state, why show two dialogfragments, we need to continue to look at the exception handling process of the Fragment, in the Fragment does not have no argument constructor will cause a crash, analysis just went to the Fragment construction. Now keep going down. After providing the no-argument constructor, the Fragment can be created correctly. After that? After that, some recovery logic, and then restoreAllState:

void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) {
 
    if (state == null) return;
    FragmentManagerState fms = (FragmentManagerState)state;
    mActive = new ArrayList<Fragment>(fms.mActive.length);
     for (int i=0; i<fms.mActive.length; i++) {
        FragmentState fs = fms.mActive[i];
        if(fs ! = null) { Fragment f = fs.instantiate(mActivity, mParent); mActive.add(f); fs.mInstance = null; // Build the list of currently added fragments.if(fms.mAdded ! = null) { mAdded = new ArrayList<Fragment>(fms.mAdded.length);for (int i=0; i<fms.mAdded.length; i++) {
            Fragment f = mActive.get(fms.mAdded[i]);
            if (f == null) {
                throwException(new IllegalStateException(
                        "No instantiated fragment for index #" + fms.mAdded[i]));
            }
            f.mAdded = true;
            if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ":" + f);
            if (mAdded.contains(f)) {
                throw new IllegalStateException("Already added!");
            }
            mAdded.add(f);
        }
    
    // Build the back stack.
    if(fms.mBackStack ! = null) { mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);for (int i=0; i<fms.mBackStack.length; i++) {
            BackStackRecord bse = fms.mBackStack[i].instantiate(this);
 
            mBackStack.add(bse);
            if (bse.mIndex >= 0) {
                setBackStackIndex(bse.mIndex, bse);
}
Copy the code

FragmentActiivyt’s onCreate function is used to retrieve FragmentActiivyt’s FragmentActiivyt’s onCreate function after it has been killed. By default, the Fragment has been added to the mAdded list. However, in scenario 1, we manually created a Fragment and added it to it, so there are two fragments in the mAdded function. After the FragmentActivity calls onStart, the Fragment view in the mAdded list is created, added to the corresponding container, and displayed when the Activity calls onReusume. It will show two copies, in fact, if, at this point, you kill again, restore, it will show three, kill, restart, four copies…

@Override
protected void onStart() {
    super.onStart();

    mStopped = false;
    mReallyStopped = false;
    mHandler.removeMessages(MSG_REALLY_STOPPED);

    if(! mCreated) { mCreated =true;
        mFragments.dispatchActivityCreated();
    }

    mFragments.noteStateNotSaved();
    mFragments.execPendingActions();

    mFragments.doLoaderStart();

    // NOTE: HC onStart goes here.

    mFragments.dispatchStart();
    mFragments.reportLoaderStart();
}
Copy the code

The above is some analysis of FramgentActivity for two scenarios, mainly dealing with Framgent when replying.

Call timing of onSaveInstanceState and OnRestoreInstance

OnSaveInstanceState is called when you click the home button or jump to another screen, but OnRestoreInstance is not called when you wake up. Why? Isn’t onSaveInstanceState paired with OnRestoreInstance? In Android, onSaveInstanceState is preprocessed to prevent the Activity from being killed by the background. If the Activity is not killed by the background, there is no need to restore the Activity on site and OnRestoreInstance will not be called. In most cases, activities are not killed that quickly.

When onSaveInstanceState is called

The onSaveInstanceState function is Android’s way of preventing an Activity from being killed in the background. The onSaveInstanceState function is executed before 2.3, before onPause, and after 2.3, before onStop. You might run out of memory and execute onSaveInstanceState when it’s reclaimed.

For example, when Activity A calls startActivity to startActivity B, AMS pauses Activity A, then calls B, displays B, and then stops A. When stopping A, you need to save the scene of A. Since invisible activities can be killed in the background, for example, by turning on an unreserved Activity in the developer options, the previous Activity’s save process would look something like this when starting another Activity

After 2.3, onSaveInstanceState is placed before onStop. Check out the FragmentActivity onSaveInstanceState source code.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
}
Copy the code

As can be seen, the first is the parent class onSaveInstanceState, mainly to save some window and View information, such as the ViewPager is currently displayed the number of views, etc.. After that, the FragmentManager’s saveAllState stores some of the state of the FragmentActivity’s own live Fragment. This data is needed for the FragmentActivity to recover from Framgent. If not handled properly, the above exception will occur.

When OnRestoreInstanceState is called

OnRestoreInstanceState is implemented in pairs with onSaveInstanceState, but its calls are not completely paired. OnSaveInstanceState will be called when the Activity jumps or returns to the main screen. OnRestoreInstanceState, however, does not. It is called only when the Activity or App is killed by an exception and the recovery process takes place. OnRestoreInstanceState will not be called back if the Activity is not killed.

You can see that OnRestoreInstanceState is called after onStart, before onPostCreate. So why is normal creation not called? Take a look at the source code for starting activities in ActivityThread:

 private Activity performLaunchActivity(Activi
         
         ...
          mInstrumentation.callActivityOnCreate(activity, r.state);
          	   
               r.activity = activity;
               r.stopped = true;
               if(! r.activity.mFinished) { activity.performStart(); r.stopped =false;
               }
               if(! r.activity.mFinished) {if (r.state != null) {
                       mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                   }
               }
               if(! r.activity.mFinished) { activity.mCalled =false; mInstrumentation.callActivityOnPostCreate(activity, r.state); }}Copy the code

As you can see, only R. state! . = null, only through mInstrumentation callActivityOnRestoreInstanceState callback OnRestoreInstanceState, R. state is the data that ActivityManagerService passes to ActivityThread through Binder for scenario recovery. So that’s some analysis of when onSaveInstanceState and OnRestoreInstance execute. The following combined with the specific system View control to analyze the specific application of these two functions: ViewPager and FragmentTabHost, for example, are two of the most commonly used controls on the main screen, and are internally compatible with background killing, which is why the ViewPager can automatically locate the last view after being killed.

ViewPager is compatible with background killing

First of all, ViewPager is compatible. Even if the ViewPager is killed in the background, it can still be restored to the last closed position. This is also an optimization of the experience. In the previous analysis of onSaveInstanceState and onRestoreInstanceState, we only focused on the Fragment processing. In fact, there are also some processing for Windows and Views. Let’s take a look at what onSaveInstanceState saves for the window:

protected void onSaveInstanceState(Bundle outState) {
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
  }
Copy the code

PhonwWinow.java

@Override
public Bundle saveHierarchyState() {
    Bundle outState = new Bundle();
    if (mContentParent == null) {
        returnoutState; } SparseArray<Parcelable> states = new SparseArray<Parcelable>(); mContentParent.saveHierarchyState(states); outState.putSparseParcelableArray(VIEWS_TAG, states); // save the focused view id View focusedView = mContentParent.findFocus(); . outState.putInt(FOCUSED_ID_TAG, focusedView.getId()); // save the panelsif (panelStates.size() > 0) {
        outState.putSparseParcelableArray(PANELS_TAG, panelStates);
    }
    if(mActionBar ! = null) { outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates); }return outState;
}
Copy the code

A Window is PhonwWinow, and saveHierarchyState is a collection of scene information for the View in the current Window, such as: The ID of the View currently getting focus, the ActionBar, some state of the View, and of course the saveHierarchyState recursively traverses all child Views, saving all the states that need to be saved:

ViewGroup.java

@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    super.dispatchSaveInstanceState(container);
    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        View c = children[i];
        if((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) ! = PARENT_SAVE_DISABLED) { c.dispatchSaveInstanceState(container); }}}Copy the code

Visible, the function first by super. DispatchSaveInstanceState save their state, and recursion is passed to the View. OnSaveInstanceState is used to retrieve the State that the View needs to save and store its own ID as a Key in the SparseArray States list, which is actually a list of PhoneWindows. Binder saves this data to ActivityManagerService

View.java

protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    if(mID ! = NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; Parcelable state = onSaveInstanceState();if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
            throw new IllegalStateException(
                    "Derived class did not call super.onSaveInstanceState()");
        }
        if(state ! = null) { container.put(mID, state); }}}Copy the code

So what information is stored against the ViewPager? It is easy to see from the following code that a new SavedState scenario data is created and the current location mCurItem is stored in it.

@Override
public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    SavedState ss = new SavedState(superState);
    ss.position = mCurItem;
    if(mAdapter ! = null) { ss.adapterState = mAdapter.saveState(); }returnss; } That's pretty much what we're doing here. Now let's look at the restoration of ViewPager and what onRestoreInstanceState does. Protected void onRestoreInstanceState(Bundle savedInstanceState) {if(mWindow ! = null) { Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);if(windowState ! = null) { mWindow.restoreHierarchyState(windowState); }}}Copy the code

Can be seen from the code, it is to get at that time, the window of the saved information, through the mWindow. RestoreHierarchyState do data recovery,

@Override
public void restoreHierarchyState(Bundle savedInstanceState) {
    if (mContentParent == null) {
        return;
    }

    SparseArray<Parcelable> savedStates
            = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
    if (savedStates != null) {
        mContentParent.restoreHierarchyState(savedStates);
    }
    ...
    
    if (mActionBar != null) {
    	...
          mActionBar.restoreHierarchyState(actionBarStates);
      }
}
Copy the code

What happens to ViewPager? It is easy to see from the source code, in fact, is to retrieve the SavedState, and obtain the location of the exception killed, for subsequent recovery,

ViewPager.java

@Override
public void onRestoreInstanceState(Parcelable state) {
    if(! (state instanceof SavedState)) { super.onRestoreInstanceState(state);return;
    }

    SavedState ss = (SavedState)state;
    super.onRestoreInstanceState(ss.getSuperState());

    if(mAdapter ! = null) { mAdapter.restoreState(ss.adapterState, ss.loader);setCurrentItemInternal(ss.position, false.true);
    } else{ mRestoredCurItem = ss.position; mRestoredAdapterState = ss.adapterState; mRestoredClassLoader = ss.loader; }}Copy the code

This explains how ViewPager saves and restores the scene through onSaveInstanceState and onRestoreInstanceState. If you use ViewPager+FragmentAdapter, you’re involved in both the recovery of the FragmentActivity and the recovery of the ViewPager, and the FragmentAdapter is also compatible with background killing. To prevent repeated creation of fragments, see the source code for FragmentAdapter:

FragmentPagerAdapter.java

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if(mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? <! -- Have you created a Fragment? --> String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); 1 Do not create a Fragment using getItem if the Fragment contains the corresponding Tagif(fragment ! = null) { mCurTransaction.attach(fragment); }else{2. Create a Fragment = getItem(position) if there is no corresponding Tag in the Activity. mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); }if(fragment ! = mCurrentPrimaryItem) { FragmentCompat.setMenuVisibility(fragment,false);
        FragmentCompat.setUserVisibleHint(fragment, false);
    }

    return fragment;
}
Copy the code

The onCreate function of the FragmentActivity rebuilds the Fragment list. Those fragments are not recreated using getItem. Look at the FragmentTabHost control. FragmentTabHost is also a common home page control. FragmentTabHost also has a background kill mechanism. As the name suggests, this control was created specifically for fragments.

FragmentTabHost is compatible with background killing

FragmentTabHost is similar to ViewPager in that the current position is saved on onSaveInstanceState and postion is restored on onRestoreInstanceState and reassigned to Tabhost. Later, when FragmentTabHost is onAttachedToWindow, it can set the current position based on the restored postion as follows:

FragmentTabHost.java

@Override
protected Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    SavedState ss = new SavedState(superState);
    ss.curTab = getCurrentTabTag();
    return ss;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    if(! (state instanceof SavedState)) { super.onRestoreInstanceState(state);return;
    }
    SavedState ss = (SavedState) state;
    super.onRestoreInstanceState(ss.getSuperState());
    setCurrentTabByTag(ss.curTab);
}
Copy the code

When onAttachedToWindow is executed on FragmentTabHost, it will first getCurrentTabTag, which is the value of the SavedState that was recovered if it was killed in the background. After that, use doTabChanged to switch to the responsive Tab. Note that the Fragment will not be created again because it has been rebuilt.

@Override
protected void onAttachedToWindow() { super.onAttachedToWindow(); String currentTab = getCurrentTabTag(); . ft =doTabChanged(currentTab, ft);
    
    if (ft != null) {
        ft.commit();
        mFragmentManager.executePendingTransactions();
    }
}
Copy the code

App development for background kill processing

The simplest way, but not so effective: cancel system restore

For example: For FragmentActivity, do not rebuild:

protected void onCreate(Bundle savedInstanceState) {
     if(savedInstanceState ! = null) {savedInstanceState. PutParcelable (" android: support: fragments could ", null); } super.onCreate(savedInstanceState); }Copy the code

If the Actvity is “Android: Fragments”, please note that ViewPager and FragmentTabHost do not need to be processed.

For Windows, if you don’t want the View to use recovery logic, override the onRestoreInstanceState function in the FragmentActivity of the base class.

protected void onRestoreInstanceState(Bundle savedInstanceState) {
}
Copy the code

Of course, all the above methods are rude. It is better to follow the design of Android, save the site where it needs to be saved, and restore the site where it needs to be restored by removing the corresponding data.

ActivityManagerService Restores the App field mechanism

Suppose, for example, that an App is killed in the background, and the App is recalled again from the most recent task list. How does the system handle this?

How does the Android Framework layer (AMS) know that an App has been killed

First, how does the system know that an Application has been killed? Android uses Linux’s oomKiller mechanism, just a simple variation of LowmemoryKiller, but this is actually kernel level. After LowmemoryKiller kills a process, how does it send notifications to user space and tell the ActivityMangerService in the framework layer? Only when AMS knows whether the App or Activity is killed can AMS (ActivityMangerService) properly rebuild or invoke the process. For example, if the App is dead, the process needs to be restarted because there is a Service that needs to be revived. When does AMS know that the App or Activity has been killed by the background? Let’s take a look at what happens when we invoke the most recent task list.

Call up the App process again from the nearest task list or Icon

In the systemUi source code package, there is a RecentActivity, which is actually the entry of the recent task list, and its display interface is displayed through the RecentsPanelView, click the recent App, its execution code is as follows:

public void handleOnClick(View view) { ViewHolder holder = (ViewHolder)view.getTag(); TaskDescription ad = holder.taskDescription; final Context context = view.getContext(); final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); Bitmap bm = holder.thumbnailViewImageBitmap; . If TaskDescription is not actively closed, ad.taskId is >=0if (ad.taskId >= 0) {
        // This is an active task; it should just go to the foreground.
        am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
                opts);
    } else {
        Intent intent = ad.intent;
        intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
                | Intent.FLAG_ACTIVITY_TASK_ON_HOME
                | Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            context.startActivityAsUser(intent, opts,
                    new UserHandle(UserHandle.USER_CURRENT));
        }...
}
Copy the code

In the above code, there is a test that ad.taskId >= 0. If this test is met, then the APP is invoked by moveTaskToFront. RecentTasksLoader: RecentTasksLoader: RecentTasksLoader: RecentTasksLoader: RecentTasksLoader

@Override
protected Void doInBackground(Void... params) {
    // We load intwo stages: first, we update progress with just the first screenful // of items. Then, we update with the rest of the items final int origPri = Process.getThreadPriority(Process.myTid()); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); final PackageManager pm = mContext.getPackageManager(); final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE); . TaskDescription item = createTaskDescription(recentInfo.id, recentInfo.persistentId, recentInfo.baseIntent, recentInfo.origActivity, recentInfo.description); . }Copy the code

ActivityManger getRecentTasks requests the latest task information from AMS, and createTaskDescription creates the TaskDescription. Recentinfo. id is the taskId of TaskDescription, so let’s see what it means:

public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { ... IPackageManager pm = AppGlobals.getPackageManager(); final int N = mRecentTasks.size(); .for (int i=0; i<N && maxNum > 0; i++) {
            TaskRecord tr = mRecentTasks.get(i);
            if(i == 0 || ((flags&ActivityManager.RECENT_WITH_EXCLUDED) ! = 0) || (tr.intent == null) || ((tr.intent.getFlags() &Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)) { ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); rti.id = tr.numActivities > 0 ? tr.taskId : -1; rti.persistentId = tr.taskId; rti.baseIntent = new Intent( tr.intent ! = null ? tr.intent : tr.affinityIntent);if(! detailed) { rti.baseIntent.replaceExtras((Bundle)null); }Copy the code

Select * from TaskRecord where numActivities > 0; select * from TaskRecord where numActivities > 0; ActivityRecord ActivityRecord ActivityRecord ActivityRecord ActivityRecord ActivityManagerService ActivityStack As long as the APP is alive, or killed by LowmemoryKiller, its AMS ActivityRecord is intact, which is the basis for recovery. The data that RecentActivity gets is a copy of AMS, and it doesn’t know if the APP it is about to invoke is alive, as long as TaskRecord tells RecentActivity it is in stock, so it goes straight through the invoke process. This is done by invoking the App via ActivityManager’s moveTaskToFront, and AMS takes care of the rest of the work. Take a look at the flow chart so far:

How does AMS recover the scene when the whole APP is killed by the background

The communication between AMS and the client is “full-duplex” with the Binder, which means that when AMS issues a command to the client, AMS is the client and vice versa. Note that Binder has an obituary function: If a server (S) based on Binder communication dies, the client (C) can receive an obituary from the Binder driver informing the client that the service has died. The Binder driver can be regarded as a third-party undead postal agency that sends death notifications to the client. For the case of APP being killed abnormally, this obituary is sent to AMS. After receiving the notification, AMS will sort out the case of APP being killed abnormally. The codes driven by Binder are involved here, and you can check them yourself. In the AMS source code, the entry is actually appDiedLocked.

final void appDiedLocked(ProcessRecord app, int pid,
            IApplicationThread thread) {
			  ...
	        if(app.pid == pid && app.thread ! = null && app.thread.asBinder() == thread.asBinder()) { booleandoLowMem = app.instrumentationClass == null; HandleAppDiedLocked (app,false.true); // If you are killed by the background, how to handle key 2if (doLowMem) {
                boolean haveBg = false;
                for (int i=mLruProcesses.size()-1; i>=0; i--) {
                    ProcessRecord rec = mLruProcesses.get(i);
                    if(rec.thread ! = null && rec.setAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { haveBg =true;
                        break; }}if(! haveBg) { <! If it is killed by LowmemoryKiller, memory is running low, and other background apps will be notified. Eventlog.writeevent (eventLogtags.am_low_memory, mlruprocesses.size ()); long now = SystemClock.uptimeMillis();for (int i=mLruProcesses.size()-1; i>=0; i--) {
                        ProcessRecord rec = mLruProcesses.get(i);
                        if(rec ! = app && rec.thread ! = null && (rec.lastLowMemory+GC_MIN_INTERVAL) <= now) {if (rec.setAdj <= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
                                rec.lastRequestedGc = 0;
                            } else {
                                rec.lastRequestedGc = rec.lastLowMemory;
                            }
                            rec.reportLowMemory = true; rec.lastLowMemory = now; mProcessesToGc.remove(rec); addProcessToGcListLocked(rec); } } mHandler.sendEmptyMessage(REPORT_MEM_USAGE); <! --> scheduleAppGcsLocked(); }}}... }Copy the code

Key point 1: After a process is killed, AMS should selectively clear the process information, and then determine whether other background processes need to be cleaned based on whether the background process is killed due to low memory. Now look at how handleAppDiedLocked cleans up. Here’s how it works when rebuilding: ActivityRecord doesn’t clean up, but gives it an unbound flag for the APP

private final void handleAppDiedLocked(ProcessRecord app, boolean restarting, Boolean allowRestart) {key point 1 cleanUpApplicationRecordLocked (app, restarting, allowRestart, 1); . // Remove this application's activities from active lists. boolean hasVisibleActivities = mMainStack.removeHistoryRecordsForAppLocked(app); app.activities.clear(); . If (! restarting) { if (! mMainStack.resumeTopActivityLocked(null)) { // If there was nothing to resume, and we are not already // restarting this process, but there is a visible activity that // is hosted by the process... then make sure all visible // activities are running, taking care of restarting this // process. if (hasVisibleActivities) { mMainStack.ensureActivitiesVisibleLocked(null, 0); }}}}Copy the code

See point 1, cleanUpApplicationRecordLocked, is mainly responsible for clean up some of the will, receivers, information service, and in the process of cleaning according to the configuration of some information to decide whether to need and start the reconstruction process, Point 2 is related to arouse the process of judgment, 3 key, mainly is the process of the current foreground process was killed if it is, requires to be rebuilt, and immediately shows: simple to see cleanUpApplicationRecordLocked cleaning process

private final void cleanUpApplicationRecordLocked(ProcessRecord app, boolean restarting, boolean allowRestart, int index) { <! - cleaning service - > mServices. KillServicesLocked (app, allowRestart); . boolean restart =false; <! -- cleaning will. -- -- >if(! app.pubProviders.isEmpty()) { Iterator<ContentProviderRecord> it = app.pubProviders.values().iterator();while(it.hasNext()) { ContentProviderRecord cpr = it.next(); . app.pubProviders.clear(); }... <! --> // Unregister any receivers.if (app.receivers.size() > 0) {
        Iterator<ReceiverList> it = app.receivers.iterator();
        while(it.hasNext()) { removeReceiverLocked(it.next()); } app.receivers.clear(); }... Key point 1, whether the process needs to be restarted,if(restart && ! app.isolated) { // We have components that still need to be runningin the
        // process, so re-launch it.
        mProcessNames.put(app.processName, app.uid, app);
        startProcessLocked(app, "restart", app.processName); }... }Copy the code

For example, if a Service is set to START_STICKY, it needs to be revived after it is killed. This is also the cause of rogue software. Then see mMainStack. RemoveHistoryRecordsForAppLocked (app), for intuitive understanding app reconstruction, the code in a core position,

boolean removeHistoryRecordsForAppLocked(ProcessRecord app) {
    ...
    while (i > 0) {
        i--;
        ActivityRecord r = (ActivityRecord)mHistory.get(i);
        if(r.app == app) { boolean remove; <! -- Key point 1-->if((! r.haveState && ! r.stateNotNeeded) || r.finishing) { remove =true;
            } else if (r.launchCount > 2 &&
                remove = true;
            } else{// In general, gofalse
                remove = false; } <! -- Key point 2-->if (remove) {
                ...
                removeActivityFromHistoryLocked(r);

            } else{... r.app = null; . }return hasVisibleActivities;
}
Copy the code

When an Activity jumps, the Activity scene is saved before stopActivity, so that r.Haestate ==true on the AMS side, that is, its ActivityRecord is not removed from ActivityStack. At the same time, the App field of ActivityRecord is left blank, which is the key decision between resume and rebuild when restoring. Moving down to moveTaskToFrontLocked, this is a function in ActivityStack that manages the ActivityRecord stack. All start activities keep an ActivityRecord in ActivityStack. And that’s one of the ways THAT AMS manages Activiyt, and eventually moveTaskToFrontLocked will call resumeTopActivityLocked to invoke activities, The main way THAT AMS gets information about an Activity that is about to resume is through ActivityRecord. It doesn’t know if the Activity itself is alive or not. After that, AMS doesn’t know that the App or Activity has been killed until it wakes up the Activity. Take a look at resumeTopActivityLocked.

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { ... The key point 1if(next.app ! = null && next.app.thread ! = null) { ... }else{ // Whoops, need to restart this activity! . startSpecificActivityLocked(next,true.true);
        }

        return true;
    }
Copy the code

See point 1 judgment conditions, since has put ActivityRecord app fields empty, AMS knew this app or Activity been abnormal kill, therefore, will go startSpecificActivityLocked rebuilt. If AMS detects that the APP is still alive, the last Activity is called by scheduleResumeActivity. But if the APP or the Activity been abnormal kill, then AMS by startSpecificActivityLocked will APP rebuild again, and finally the Activity of reconstruction.

How does AMS recover the scene if the APP is alive but the Activity is killed by the background

Another possibility is that the APP is not killed, but the Activity is killed. First of all, the Activity must be managed by AMS, and the kill of the Activity must be recorded by AMS. Strictly speaking, this situation does not belong to the background killing, because it belongs to the normal management of AMS. In controllable scope, for example, when the “Do not retain Activity” in developer mode is turned on, at this time, Although it kills the Activity, it still keeps the ActivitRecord, so there is still a trace when it wakes up or falls back. Take a look at the Destroy callback code for ActivityStack.

 final boolean destroyActivityLocked(ActivityRecord r,
            boolean removeFromApp, boolean oomAdj, String reason) {
        ...
        if (hadApp) {
          ...
           boolean skipDestroy = false; Try {1 key code of state Richard armitage pp. Thread. ScheduleDestroyActivity (state Richard armitage ppToken, r.f inishing, r.c onfigChangeFlags); .if(r.finishing && ! skipDestroy) {if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYING: " + r
                        + " (destroy requested)");
                r.state = ActivityState.DESTROYING;
                Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG);
                msg.obj = r;
                mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
            } elseState = activitystate.destroyed;if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity "+ r); r.app = null; }}return removedFromHistory;
    }  
Copy the code

AcvitityThread clears the Activity and sets the state of the ActivityRecord to ActivityState.DESTROYED if AMS closes the Activity abnormally. And empty its ProcessRecord reference: R.pp = null. They should set ActivityState.DESTROYED is set to avoid the subsequent clearing logic.

final void activityDestroyed(IBinder token) {
    synchronized (mService) {
        final long origId = Binder.clearCallingIdentity();
        try {
            ActivityRecord r = ActivityRecord.forToken(token);
            if(r ! = null) { mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); } int index = indexOfActivityLocked(r);if(index >= 0) { 1 <! -- here will be whether fromhistoryList removed ActivityRecord-->if (r.state == ActivityState.DESTROYING) {
                    cleanUpActivityLocked(r, true.false); removeActivityFromHistoryLocked(r); } } resumeTopActivityLocked(null); } finally { Binder.restoreCallingIdentity(origId); }}}Copy the code

If r.state == ActivityState, the last message will remove the ActivityRecord. DESTROYED, the state of the DESTROYED message will not be set to ActivityState.DESTROYING the DESTROYED message will destroy the DESTROYED message. So not removeActivityFromHistoryLocked, namely retained ActivityRecord scene, as if also rely on exceptions to distinguish whether the end of the normal Activity. How does the Activity start in this case? Two key points emerge from the above analysis

  • ActivityRecord has not been moved from the HistoryRecord list
  • The ProcessRecord field for ActivityRecord is null, with R. pp = null

Thus ensure the when resumeTopActivityLocked startSpecificActivityLocked branch

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
		  ...
		 	
        if(next.app ! = null && next.app.thread ! = null) { ... }else{ // Whoops, need to restart this activity! . startSpecificActivityLocked(next,true.true);
        }

        return true;
    }
Copy the code

At this point, AMS knows if the APP or Activity has been killed abnormally and decides whether to resume or restore.

How to save the scene before the App is killed: the new Activity is started and the old Activity is saved

The saving process of the App site is relatively simple. When the entry is basically startActivity, the jump of the interface involves switching the Activity and saving the current Activity scene: Here’s a look at how AMS manages these jumps and saves the scene: When Activity A starts Activity B, A is not visible at this time and may be destroyed. What is the process of saving the scene of Activity A

  • ActivityA startActivity ActivityB
  • ActivityA pause
  • ActivityB create
  • ActivityB start
  • ActivityB resume
  • ActivityA onSaveInstance
  • ActivityA stop

The process might look like this:

Now let’s go through the source code step by step and see what AMS does when starting a new Activity and saving an old one: skip the simple startActivity and go straight to AMS

ActivityManagerService

public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
        Intent intent, String resolvedType, IBinder resultTo,
        String resultWho, int requestCode, int startFlags,
        String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
    enforceNotIsolatedCaller("startActivity"); .return mMainStack.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
            resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
            null, null, options, userId);
}
Copy the code

ActivityStack

final int startActivityMayWait(IApplicationThread caller, int callingUid,
                  
        int res = startActivityLocked(caller, intent, resolvedType, aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, startFlags, options, componentSpecified, null); . }Copy the code

StartActivityMayWait starts a new APP, or a new Activity, using startActivityMayWait. ResumeTopActivityLocked is a unified entry point for interface switching. The first time you enter, ActivityA will not be paused, so you will need to pause ActivityA.

ActivityStack

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { ... <! We need to start pausing the current Activity so the top one // can be resumed...if(mResumedActivity ! = null) {if(next.app ! = null && next.app.thread ! = null) { mService.updateLruProcessLocked(next.app,false);
        }
        startPausingLocked(userLeaving, false);
        return true; }... }Copy the code

The Binder tells AMS to suspend the ActivityA thread. When the ActivityThread is finished, the Binder tells AMS to resume the ActivityB thread.

private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) {

    if(prev.app ! = null && prev.app.thread ! = null) { ... try { prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing, userLeaving, prev.configChangeFlags);Copy the code

ActivityThread

private void handlePauseActivity(IBinder token, boolean finished,
            boolean userLeaving, int configChanges) {
        ActivityClientRecord r = mActivities.get(token);
        if(r ! = null) { ... performPauseActivity(token, finished, r.isPreHoneycomb()); . // Tell the activity manager we have paused. try { ActivityManagerNative.getDefault().activityPaused(token); } catch (RemoteException ex) { } } }Copy the code

When AMS receives A pause message from ActivityA, it will invoke ActivityB, resumeTopActivityLocked, and wake UP ActivityB. After that, IT will further stop ACTIvitylocked.

ActivityStack

private final void completePauseLocked() {
   
    if(! mService.isSleeping()) { resumeTopActivityLocked(prev); }else{... } ActivityB will stop ActivityA through ActivityStack stopActivityLocked after ActivityB is up.  private final void stopActivityLocked(ActivityRecord r) { ...if(mMainStack) { r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags); . }Copy the code

Back to the APP side, look at the call in ActivityThread: first by callActivityOnSaveInstanceState, to save the scene in the Bundle,

private void performStopActivityInner(ActivityClientRecord r,
        StopInfo info, boolean keepShown, boolean saveState) {
       ...
        // Next have the activity save its current state and managed dialogs...
        if(! r.activity.mFinished && saveState) {if (r.state == null) {
                state = new Bundle();
                state.setAllowFds(false); mInstrumentation.callActivityOnSaveInstanceState(r.activity, state); r.state = state; . }Copy the code

Later, through ActivityManagerNative. GetDefault (.) activityStopped, notify the AMS, Stop action at the time of notification, will also save the field data of the past.

private static class StopInfo implements Runnable {
    ActivityClientRecord activity;
    Bundle state;
    Bitmap thumbnail;
    CharSequence description;

    @Override public void run() {
        // Tell activity manager we have been stopped.
        try {
   
            ActivityManagerNative.getDefault().activityStopped(
                activity.token, state, thumbnail, description);
        } catch (RemoteException ex) {
        }
    }
}
Copy the code

Through the above process, AMS not only started a new Activity, but also saved the site of the previous Activity. Even if the previous Actiivity was killed due to various reasons, AMS can also know how to arouse Activiyt and restore it in the process of backtracking or re-awakening.

Now solve two problems: 1, how to save the scene, 2, how AMS can determine whether the APP or Activity is killed by an exception, then the last question remains: how AMS can recover the APP or Activity that is killed by an exception?

The Activity or Application restores the process

Application was killed by the background

In fact, when explaining how AMS determines whether an APP or Activity is killed abnormally, we have already involved the logic of recovery. We also know that once AMS knows that the APP is killed by the background, it is not a normal resuem process, but a new laucher. ResumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked resumeTopActivityLocked

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
  ....
  if(next.app ! = null && next.app.thread ! = null) {if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: "+ next); . try { ... } catch (Exception e) { // Whoops, need to restart this activity! Here is the slog. I (TAG,"Restarting because process died: " + next);
                next.state = lastState;
                mResumedActivity = lastResumedActivity;
                Slog.i(TAG, "Restarting because process died: " + next);
              
                startSpecificActivityLocked(next, true.false);
                return true;
            }
Copy the code

Can know from the above code, is actually walk startSpecificActivityLocked, there is no much difference in this first from the desktop to raise APP, just one thing to note, that is the time to launch the Activity is the last time the field data of decent, Since all of the Activity scenes were saved and passed to AMS when it was backlogged, this recovery startup returns this data to the ActivityThread. Take a closer look at the special handling code for recovery in performLaunchActivity:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 

    ActivityInfo aInfo = r.activityInfo;
     Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        if(r.state ! = null) { r.state.setClassLoader(cl); } } catch (Exception e) { ... } try { Application app = r.packageInfo.makeApplication(false, mInstrumentation); . Key point 1 mInstrumentation. CallActivityOnCreate (activity, r.s Tate); . r.activity = activity; r.stopped =true;
            if(! r.activity.mFinished) { activity.performStart(); r.stopped =false; } Key point 1if(! r.activity.mFinished) {if (r.state != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                }
            }
            if(! r.activity.mFinished) { activity.mCalled =false; mInstrumentation.callActivityOnPostCreate(activity, r.state); . }Copy the code

Look at the key points 1 and 2, 1, look at the key points. MInstrumentation callActivityOnCreate will callback Actiivyt onCreate, this function is mainly aimed at FragmentActivity do some fragments recovery work, The r.state in ActivityClientRecord is passed by AMS to the APP to restore the scene, and these are null on normal startup. Key point 2, at R. State! . = null is not empty when performing mInstrumentation callActivityOnRestoreInstanceState, this function default is mainly aimed at the Window with some recovery work, such as display position before ViewPager recovery, etc., It can also be used to recover user-saved data.

Application is not killed by the background

In the above analysis, we know that when AMS actively kills an Activity, the AcitivityRecord app field is null, so resumeTopActivityLocked is not the same as when the whole app is killed. It’s going to go down the branch

 final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
     ...
    	
    if(next.app ! = null && next.app.thread ! = null) { ... }else// Whoops, need to restart this Activity! startSpecificActivityLocked(next,true.true);
    }

    return true;
}
Copy the code

Although different, but also go startSpecificActivityLocked process, just don’t new APP process, the rest are all the same, no longer.

The difference between active clearing of recent tasks and abnormal killing: Whether ActivityStack clears normally

Why restore in reverse order: this is the stack order of HistoryRecord in ActivityStack, strictly on the AMS side

In a word, the principle of Android background Kill recovery is summarized: The Application process is killed, but the scene is saved by AMS, and AMS can restore the Application scene according to the save

LowMemoryKiller principle

LowMemoryKiller is a multi-level oomKiller extended by Andorid based on oomKiller principle. OomKiller (Out Of MemoryKiller) is a multi-level oomKiller extended by Andorid based on oomKiller principle. OomKiller (Out Of MemoryKiller) is a multi-level oomKiller extended by Andorid based on oomKiller principle. LowMemoryKiller is a memory reclamation mechanism triggered by the memory threshold. When the available memory is low, LowMemoryKiller will selectively kill processes, which is more flexible than OOMKiller.

  • Process priority definition: Only with priority can you decide who to kill first and who to kill later
  • Dynamic process priority management: the priority of a process should not be fixed, but should change dynamically according to its changes. For example, the priority of a foreground process must be reduced when it switches to the background
  • When do processes need to be killed? When do processes need to be killed? When do processes need to be killed
  • How to kill

Android uses the Linux kernel at the bottom, and its process management is based on the Linux kernel. LowMemoryKiller is also placed in the kernel module accordingly, which also means that the user space is invisible to the background killing, just as AMS does not know whether an APP has been killed by the background. Only when AMS wakes up the APP will it be known if the APP has been killed by LowMemoryKiller. In fact, the principle of LowmemoryKiller is very clear, first take a look at the overall flow chart, and then analyze step by step:

Two things to keep in mind:

  • LowMemoryKiller is a passive kill process
  • Android applications use AMS to update process information using the Proc file system

Android application process priority and oomAdj

Android tries to keep applications alive as long as possible, but in order to build or run more important processes, it may be necessary to remove older processes to reclaim memory. When selecting a process to Kill, the system evaluates the “importance” of the process based on its running status. The trade-off is based on four main components. If memory needs to be reduced, the system first eliminates the least important processes, then the less important ones, and so on, to reclaim system resources. In Android, there are 5 levels of APP progression (from Google Docs) : There are 5 levels of APP importance in Android:

  • Foreground Process
  • Visible Process
  • Service Process
  • Background Processes
  • Empty Processes

Foreground process

Processes that are necessary for the user’s current operation. A process is considered a foreground process if it meets any of the following criteria:

  • Contains activities that are interacting (resumed
  • Contains services bound to the Activity you are interacting with
  • Contains Service running “foreground” (Service called startForeground())
  • Contains a Service (onCreate(), onStart(), or onDestroy()) that is performing a lifecycle callback
  • Contains a BroadcastReceiver that is executing its onReceive() method

Typically, there are not many foreground processes at any given time. The system terminates them only when it is absolutely necessary that there is not enough memory to support them while still running. At this point, the device is usually paging out of memory, so some foreground processes need to be terminated to ensure that the user interface responds properly.

Visible process

A process that does not have any foreground components but still affects what the user sees on the screen. A process is considered visible if it meets any of the following criteria:

  • Contains activities (whose onPause() method has been called) that are not in the foreground but are still visible to the user. For example, this might happen if the foreground Activity starts a dialog box that allows the previous Activity to be displayed after it.
  • Contains services bound to visible (or foreground) activities.

Visible processes are considered extremely important and will not be terminated unless necessary to keep all foreground processes running at the same time.

Service process

A process that is running a service started with the startService() method and does not belong to either of the higher categories of processes described above. Although server processes are not directly related to what the user sees, they are usually performing some action that the user cares about (for example, playing music in the background or downloading data from the network). Therefore, unless there is not enough memory to keep all foreground and visible processes running at the same time, the system will leave the server processes running.

Background processes

The process that contains an Activity that is currently invisible to the user (the Activity’s onStop() method has been called). These processes have no direct impact on the user experience, and the system may terminate them at any time to reclaim memory for foreground, visible, or server processes. There are usually many background processes running, so they are saved in the LRU (Least Recently used) list to ensure that the process containing the Activity the user recently viewed is the last to terminate. If an Activity implements the lifecycle method correctly and saves its current state, terminating its process does not have a significant impact on the user experience, because the Activity resumes all its visible states when the user navigates back to the Activity.

An empty process

A process that does not contain any active application components. The only purpose of keeping such processes is to cache them to reduce the startup time needed to run components in them the next time. This is known as hot startup. To balance system resources between the process cache and the underlying kernel cache, systems often kill these processes.

Android rates a process as the highest level it can be based on the importance of the currently active components in the process. For example, if a process hosts services and visible activities, it is rated as a visible process, not a service process. In addition, the level of a process may be increased by the dependence of other processes on it, that is, a process that serves another process is never ranked lower than the process it serves. For example, if A content provider in process A provides A service to A client in process B, or if A service in process A is bound to A component in process B, process A is always considered at least as important as process B.

Here, we further subdivide the importance of different processes, define the importance level of ADJ, and store the priority in the process structure of kernel space. For LowmemoryKiller’s reference:

The purpose of this introduction is only one point: The Android application process has a priority, and its priority depends on whether there is a display interface and whether it can be perceived by the user. The more perceived by the user, the higher the priority of the application (system process is not considered).

How are Android app priorities updated

Lowmemorykiller traverses the process structure queue of the kernel and selects the one with the lower priority to kill. How does the APP operation write to the kernel space? Linxu has a distinction between users and kernel space. Both apps and system services run in user space. Strictly speaking, the operation of user controls cannot directly affect the kernel space, let alone change the priority of the process. Actually here is passed a proc file system of Linux, the/proc file system is mapped to the kernel space can simply to see many user can operate on the file system, is not, of course, all processes have the right operation, through the proc file system, user space will be able to modify the process of kernel space data, such as modifying process priority, In Android family, before 5.0, the system is directly modified by AMS process. After 5.0, the operation of modifying priority is encapsulated into an independent service – LMKD. LMKD service is located in user space, and its function level is similar to AMS and WMS, which is a common system service. Let’s take a look at the code before 5.0. Here we still use 4.3 source code to look at the simulation of a scenario, the APP only has one Activity, we actively finish the Activity, the APP is back to the background, remember that although there is no Activity available, However, the APP itself is not dead, which is called hot launch. Let’s take a look at the general process:

ActivityManagerService

public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
     ...
    synchronized(this) {
       
        final long origId = Binder.clearCallingIdentity();
        boolean res = mMainStack.requestFinishActivityLocked(token, resultCode,
                resultData, "app-request".true); . }}Copy the code

The initial process is similar to startActivity. It starts by suspending the Activity that is currently resumed, which is itself.

final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode,
            Intent resultData, String reason, boolean immediate, boolean oomAdj) {
         ...
            if (mPausingActivity == null) {
                if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
                if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
                startPausingLocked(false.false); }... }Copy the code

If the current APP’s Activity stack is empty, you can return to the previous APP or desktop application. If the current APP’s Activity stack is empty, you can return to the previous APP or desktop application. Note the difference between startActivity and waking up the APP that is about to return to the background.

ActivityStack

private final void completePauseLocked() {
    ActivityRecord prev = mPausingActivity;
     
    if(prev ! = null) {if(v. Finishing) {1, Difference <! - take the initiative to finish the walk is the branch of state transform details, please contact your code - > prev = finishCurrentActivityLocked (prev FINISH_AFTER_VISIBLE,false); }... 2. Similaritiesif(! mService.isSleeping()) { resumeTopActivityLocked(prev); }Copy the code

1 is different from startActivity’s completePauseLocked, and active finish’s prev.finishing is true. Therefore performs finishCurrentActivityLocked branch, add the Activity of the current pause to mStoppingActivities queue, and wake up the next need to go to the front desk of the Activity, after wake up, will continue to perform the stop:

private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r,
	        int index, int mode, boolean oomAdj) {
	    if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {
	        if(! mStoppingActivities.contains(r)) { mStoppingActivities.add(r); . }...returnr; }... }Copy the code

So let’s go back to resumeTopActivityLocked, and if resume calls back completeResumeLocked, stop, This function calls the activityIdleInternal function back and forth by sending an IDLE_TIMEOUT_MSG message to Handler. Finally, destroyActivityLocked destroys the ActivityRecord.

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
		...
   if(next.app ! = null && next.app.thread ! = null) { ... Try {... next.app.thread.scheduleResumeActivity(next.appToken, mService.isNextTransitionForward()); . . try { next.visible =true; completeResumeLocked(next); }... }Copy the code

When destroying an Activity, if the current APP’s Activity stack is empty, it means that the current Activity has no visible interface. In this case, you need to dynamically update the APP’s priority.

final boolean destroyActivityLocked(ActivityRecord r,
            boolean removeFromApp, boolean oomAdj, String reason) {
    		...
       if (hadApp) {
            if(removeFromApp) {// Here remove from ProcessRecord, but not from ProcessRecordhistoryDelete int idx = r.app.activities.indexof (r);if(idx >= 0) { r.app.activities.remove(idx); }...if(r.app.activities.size() == 0) { // No longer have activities, so update oom adj. mService.updateOomAdjLocked(); . }Copy the code

Finally, AMS’s updateOomAdjLocked function is called to update the Process’s priority. In the 4.3 source code, the priority is set primarily through the setOomAdj function of the Process class:

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj, int clientHiddenAdj, int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) { ... ComputeOomAdjLocked (APP, hiddenAdj, clientHiddenAdj, emptyAdj, TOP_APP,false, doingAll); . <! -- If not, set a new OomAdj-->if(app.curAdj ! = app.setAdj) {if (Process.setOomAdj(app.pid, app.curAdj)) {
        ...
}
Copy the code

SetOomAdj in Process is a native method, prototype in android_util_process.cpp

android_util_Process.cpp

jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz,
                                      jint pid, jint adj)
{
#ifdef HAVE_OOM_ADJ
    char text[64];
    sprintf(text, "/proc/%d/oom_adj", pid);
    int fd = open(text, O_WRONLY);
    if (fd >= 0) {
        sprintf(text, "%d", adj);
        write(fd, text, strlen(text));
        close(fd);
    }
    return true;
#endif
    return false;
}
Copy the code

In native code, the proc file system is used to modify the kernel information. In this case, the proc file system is used to dynamically update the priority of the process. Here is the flow chart of 4.3 updating oomAdj, note the red execution points:

Android5.0 after the framework layer implementation: LMKD service

Android5.0 encapsulates the entry for setting the process priority as a separate service, the LMKD service. Instead of accessing the proc file system directly, AMS does this through the LMKD service, seeing the configuration of the service from the init.rc file.

service lmkd /system/bin/lmkd
    class core
    critical
    socket lmkd seqpacket 0660 system system
Copy the code

As can be seen from the configuration, the service communicates with other processes through socket. In fact, AMS sends a request to LMKD service through socket, asking LMKD to update the priority of the process. After receiving the request, LMKD updates the priority of the process in the kernel through /proc file system. Let’s take a look at what changes have been made to AMS in 5.0. In fact, most of the flow is similar to the previous 4.3 source code

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP, boolean doingAll, long now) { ... computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now); . applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime()); } private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now, long nowElapsed) { boolean success =true;

    if(app.curRawAdj ! = app.setRawAdj) { app.setRawAdj = app.curRawAdj; } int changes = 0; The difference between 1if(app.curAdj ! = app.setAdj) { ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
                "Set " + app.pid + "" + app.processName + " adj " + app.curAdj + ":"
                + app.adjType);
        app.setAdj = app.curAdj;
        app.verifiedAdj = ProcessList.INVALID_ADJ;
    }
Copy the code

The ProcessList class is used to set oomAdj. The socket is used to communicate with the LMKD service, and the LMKD service is passed the LMK_PROCPRIO command to update the process priority.

public static final void setOomAdj(int pid, int uid, int amt) {
    if (amt == UNKNOWN_ADJ)
        return;
   long start = SystemClock.elapsedRealtime();
    ByteBuffer buf = ByteBuffer.allocate(4 * 4);
    buf.putInt(LMK_PROCPRIO);
    buf.putInt(pid);
    buf.putInt(uid);
    buf.putInt(amt);
    writeLmkd(buf);
    long now = SystemClock.elapsedRealtime();
      }    
    
private static void writeLmkd(ByteBuffer buf) {
	 	for (int i = 0; i < 3; i++) {
	    if (sLmkdSocket == null) {
	      if (openLmkdSocket() == false) {... try { sLmkdOutputStream.write(buf.array(), 0, buf.position());return; . }Copy the code

Is actually openLmkdSocket open native socket port and send priority information in the past, so how LMKD server-side processing, the init. Rc configuration service is in start when the phone is switched on, take a look at LMKD service entry: the main function

LMKD. C functions

int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1,
    };

    mlockall(MCL_FUTURE);
    sched_setscheduler(0, SCHED_FIFO, &param);
    if(! init()) mainloop(); ALOGI("exiting");
    return 0;
}
Copy the code

If a request is received, the command is parsed and executed. Cmd_procprio is used to update the oomAdj file system. Look at the following code:

static void cmd_procprio(int pid, int uid, int oomadj) { struct proc *procp; . Snprintf (path, sizeof(path),"/proc/%d/oom_score_adj", pid);
    snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj)); writefilestring(path, val); . }Copy the code

The simple flow chart is as follows, different from 4.3

LomemoryKiller Kernel part: How to Kill

LomemoryKiller is a kernel driver module that scans the process queue when the system is running low on memory, finds low-priority (or more cost-effective) processes and kills them. For drivers, the entry is the __init function. Let’s look at the entry of the driver module:

static int __init lowmem_init(void)
{
	register_shrinker(&lowmem_shrinker);
	return 0;
}
Copy the code

As you can see, LomemoryKiller registers its lowmem_shrinker entry with the system’s memory detection module during init, register_shrinker is a function that belongs to another memory management module. If you have to root it, you can look at its definition, which is to add to a callback queue:

void register_shrinker(struct shrinker *shrinker)
{
	shrinker->nr = 0;
	down_write(&shrinker_rwsem);
	list_add_tail(&shrinker->list, &shrinker_list);
	up_write(&shrinker_rwsem);
}
Copy the code

Finally, take a look at how LomemoryKiller finds and kills low-priority processes when an out-of-memory callback is triggered: The entry function is the lowmem_shrink function registered during init (4.3), but the principle is similar:

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask) { struct task_struct *p; . Key point 1 Finds the threshold of the current memoryfor(i = 0; i < array_size; i++) {
		if (other_free < lowmem_minfree[i] &&
		    other_file < lowmem_minfree[i]) {
			min_adj = lowmem_adj[i];
			break; }}... Key point 2: Find the process whose priority is lower than this threshold and kill read_lock(&tasklist_lock); for_each_process(p) {if(p->oomkilladj < min_adj || ! p->mm)continue;
		tasksize = get_mm_rss(p->mm);
		if (tasksize <= 0)
			continue;
		if (selected) {
			if (p->oomkilladj < selected->oomkilladj)
				continue;
			if (p->oomkilladj == selected->oomkilladj &&
			    tasksize <= selected_tasksize)
				continue;
		}
		selected = p;
		selected_tasksize = tasksize;
 
	}
	if(selected ! = NULL) { force_sig(SIGKILL, selected); rem -= selected_tasksize; } lowmem_print(4,"lowmem_shrink %d, %x, return %d\n", nr_to_scan, gfp_mask, rem);
	read_unlock(&tasklist_lock);
	return rem;
}
Copy the code

Let’s look at key point 1: in fact, it is to determine the threshold corresponding to the current low memory; Key point 2: Find a process whose priority is lower than or equal to this threshold and which consumes a lot of memory (tasksize = get_mm_rss(p->mm)) and kill it. How was it killed? Very straightforward, through the Linux semaphore, send SIGKILL signal directly to kill the process. This concludes the analysis of how the LomemoryKiller kernel part works. It’s simple, in a word: passively scan, find low-priority processes, and kill them.

  • Android APP processes have priority, which is directly related to whether the process is perceived by the user
  • Activities such as APP switching, which can cause process priority changes, take advantage of AMS and are set to the kernel via proc files
  • LowmemoryKiller runs in the kernel and selects low-priority processes to kill when memory needs to be reduced

Binder obituary principle

Binder is a communication framework similar to C/S architecture. Sometimes the client may want to know the status of the server, for example, if the server is down, the client wants to be notified in time, instead of waiting for the server to know again. This scenario is most commonly used when the SERVER is C/S, such as AMS and APP. AMS wants to know when an APP process exits unexpectedly. Not only does AMS need to clean up some information on the APP side, such as ActivityRecord, ServiceRecord, etc., but it may also need to restore some self-started services in time. Binder implements a set of “obituary” features. When a server dies or exits properly, the Binder driver sends an obituary to the client informing it that the service has died.

Binder obituaries take a somewhat watter-like approach, so first you need to register the Observer with the target object. In effect, you need to register the Client with the Binder driver so that it can be sent when the Service fails. Binder obituaries have only one entry: When releasing a Binder device, all resources required by the process are reclaimed, including opened device files such as Binder character devices, regardless of normal or abnormal exit. At release time, the corresponding release function is called, and the obituary is sent at this point. Binder obituaries therefore consist of only two parts: registration and notice.

Binder “Obituary” registration entry

BindService will first request AMS to start the Service. Server process will call the function open to open the device file /dev/binder. At the same time, the Binder service entity is returned to AMS, and AMS passes the reference handle of the Binder entity to Client through Binder communication, that is, when AMS is returned to Client, it is registered with the Binder driver. In fact, this is easier to understand, the server side of the proxy, you should care about the server side. When AMS returns a Binder service entity to the Client using the IServiceConnection communication line, InnerConnection indirectly registers the death callback with the kernel:

private static class InnerConnection extends IServiceConnection.Stub {
        final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;

        public void connected(ComponentName name, IBinder service) throws RemoteException {
            LoadedApk.ServiceDispatcher sd = mDispatcher.get();
            if(sd ! = null) { sd.connected(name, service); }}}Copy the code

The ServiceDispatcher function further calls doConnected

public void doConnected(ComponentName name, IBinder service) {
    ServiceDispatcher.ConnectionInfo old;
    ServiceDispatcher.ConnectionInfo info;
    synchronized (this) {     
        if(service ! = null) { mDied =false; info = new ConnectionInfo(); info.binder = service; info.deathMonitor = new DeathMonitor(name, service); try { <! Service. LinkToDeath (info.deathmonitor, 0); }}Copy the code

LinkToDeath is a Native function that will further call BpBinde’s linkToDeath:

status_t BpBinder::linkToDeath( const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags){ <! Self = IPCThreadState::self(); self->requestDeathNotification(mHandle, this); self->flushCommands(); }Copy the code

Finally call IPCThreadState’s requestDeathNotification(mHandle, this) to send the BC_REQUEST_DEATH_NOTIFICATION request to the kernel:

status_t IPCThreadState::requestDeathNotification(int32_t handle, BpBinder* proxy)
{
    mOut.writeInt32(BC_REQUEST_DEATH_NOTIFICATION);
    mOut.writeInt32((int32_t)handle);
    mOut.writeInt32((int32_t)proxy);
    return NO_ERROR;
}
Copy the code

Finally, let’s take a look at how registration works in the kernel:

int
binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
		    void __user *buffer, int size, signed long *consumed)
{
...
case BC_REQUEST_DEATH_NOTIFICATION:
		case BC_CLEAR_DEATH_NOTIFICATION: {
			...
			ref = binder_get_ref(proc, target);
			if(cmd == BC_REQUEST_DEATH_NOTIFICATION) { ... Death = kzalloc(sizeof(*death), GFP_KERNEL); binder_stats.obj_created[BINDER_STAT_DEATH]++; INIT_LIST_HEAD(&death->work.entry); death->cookie = cookie; ref->death = death;if (ref->node->proc == NULL) {
					ref->death->work.type = BINDER_WORK_DEAD_BINDER;
					if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) {
						list_add_tail(&ref->death->work.entry, &thread->todo);
					} else {
						list_add_tail(&ref->death->work.entry, &proc->todo);
						wake_up_interruptible(&proc->wait); }}}}Copy the code

Look at key point 1, which is essentially creating a binder_ref_death object for the Client and assigning it to binder_ref. Binder drivers use binder_node to log all binder_refs. When the binder_node process crashes, the binder_node driver can find binder_refs of all clients based on the global binder_ref list. And send an “obituary” to the Client with the death callback set, because when binder_get_ref_for_node inserts a binder_ref into the Client, the binder_node’s binder_ref list is also inserted.

static struct binder_ref *
binder_get_ref_for_node(struct binder_proc *proc, struct binder_node *node)
{
	struct rb_node *n;
	struct rb_node **p = &proc->refs_by_node.rb_node;
	struct rb_node *parent = NULL;
	struct binder_ref *ref, *new_ref;

	if (node) {
		hlist_add_head(&new_ref->node_entry, &node->refs);
		}
	return new_ref;
}
Copy the code

This way, the death callback entry is registered with the Binder kernel driver, which then triggers the death callback when the process terminates to release binder.

Death notification sent

When the binder_realEASE function is called to release the corresponding resource, the binder_deferred_release function is eventually called. This function iterates through all binder_nodes in the binder_proc and sends an obituary to the Client that registered the death callback.

static void binder_deferred_release(struct binder_proc *proc)
	{         ....
		if (ref->death) {
				death++;
				if(list_empty(&ref->death->work.entry)) { ref->death->work.type = BINDER_WORK_DEAD_BINDER; list_add_tail(&ref->death->work.entry, &ref->proc->todo); // Insert the binder thread wait queue into the binder_ref request process ????? Natural support for Binder communication? // When do YOU need a death callback as a binder service? wake_up_interruptible(&ref->proc->wait); }... }Copy the code

The death notice is sent directly to the Client’s binder process TODO queue, which seems to be useful only for C/S communication scenarios. When the Client’s binder thread is woken up, it does some cleanup for the “death notice” :

static int
binder_thread_read(struct binder_proc *proc, struct binder_thread *thread,
	void  __user *buffer, int size, signed long *consumed, int non_block)
	{
		case BINDER_WORK_DEAD_BINDER:
				case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
				case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
					struct binder_ref_death *death = container_of(w, struct binder_ref_death, work);
					uint32_t cmd;
					if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
						cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE;
					elsecmd = BR_DEAD_BINDER; . }Copy the code

A BR_DEAD_BINDER command is written to user space and the talkWithDriver function is returned, on which IPCThreadState continues to execute executeCommand,

Status_t IPCThreadState: : executeCommand (int32_t CMD) {/ / obituariescaseBR_DEAD_BINDER: { BpBinder *proxy = (BpBinder*)mIn.readInt32(); <! -- Keypoint 1 --> proxy->sendObituary(); mOut.writeInt32(BC_DEAD_BINDER_DONE); mOut.writeInt32((int32_t)proxy); }break;   }
Copy the code

In fact, Obituary is actually sent using BpBinder, and when the uary is processed, confirmation notifications are sent to the Binder driver.

void BpBinder::sendObituary()
{
    ALOGV("Sending obituary for proxy %p handle %d, mObitsSent=%s\n",
        this, mHandle, mObitsSent ? "true" : "false");
    mAlive = 0;
    if (mObitsSent) return;
    mLock.lock();
    Vector<Obituary>* obits = mObituaries;
    if(obits ! = NULL) { <! Self = IPCThreadState::self(); self->clearDeathNotification(mHandle, this); self->flushCommands(); mObituaries = NULL; } mObitsSent = 1; mLock.unlock();if(obits ! = NULL) { const size_t N = obits->size();for(size_t i=0; i<N; i++) { reportOneDeath(obits->itemAt(i)); } delete obits; }}Copy the code

Look at key point 1, which corresponds to registering, removing yourself from the observer list, and then reporting

void BpBinder::reportOneDeath(const Obituary& obit)
{
    sp<DeathRecipient> recipient = obit.recipient.promote();
    ALOGV("Reporting death to recipient: %p\n", recipient.get());
    if (recipient == NULL) return;

    recipient->binderDied(this);
}
Copy the code

Then invoke the upper-layer DeathRecipient callback to do some cleanup and the like. In the case of AMS, the binderDied function is quite complex, including some data cleaning and even process reconstruction.