image/svg+xml $ $ ing$ ing$ ces$ ces$ Res Res ea ea Res->ea ou ou Res->ou r r ea->r ch ch ea->ch r->ces$ r->ch ch->$ ch->ing$ T T T->ea ou->r

Conservation des données temporaires

Exemple : une activité pour réaliser une conservation d'état

Ecriture de l'activité ConfigChangeSupportActivity réalisant une sauvegarde dans une Map statique d'un objet d'état contenant toutes les données de l'activité avec implantation du code de restauration dans onCreate(Bundlbe b) et de sauvegarde dans onSaveInstanceState(Bundle b).

// Code sample from Coursand [http://igm.univ-mlv.fr/~chilowi/], under the Apache 2.0 License

package fr.upem.coursand.configchange;

import android.os.Bundle;
import android.os.SystemClock;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.util.Pair;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;


/** This class can be used as an ancestor for classes that need to support configuration changes.
 * By default when there is a configuration change, the activity is destroyed then recreated.
 * In this case all the date stored into the fields of the old class instance is lost
 * for the new class instance.
 *
 * This ancestor class uses the followin approach to store and restore data from the ancien activity:
 * - We assume that there is only one field storing state data for the activity; this field is named 'state'
 *   (typically one create an inner static class State implementing containing all the required data)
 * - Before the destruction of the activity we save the reference of state in a map
 * - Then we reinitialize the 'state' field with this new stored value
 */
public class ConfigChangeSupportActivity extends AppCompatActivity {
    public static final String STATE_FIELD_NAME = "state";
    public static final String ACTIVITY_ID_KEY = ConfigChangeSupportActivity.class.getName() + ".activityID";

    /** Maximal lifetime of a state entry (time between destruction and recreation of the activity)
     */
    public static final int DEFAULT_STATE_LIFETIME = 600; // in seconds

    /** A map saving the states for all the activities that are going to be destroyed and recreated
     *  The value is a pair: the first element is the state objet and the second the date when the hosting
     *  was about to be destroyed (using SystemClock.elapsedTime()
     */
    private static final Map<String, Pair<Object, Long>> statesMap =
            new HashMap<>();

    /** Current instance of activity for each identifier.
     * Using a WekReference is an extra precaution that is not compulsory
     * if we remove the value from the map on onDestroy().
     *
     */
    private static final Map<String, WeakReference<ConfigChangeSupportActivity>> activityInstances =
            new HashMap<>();

    /** Persistent identifier of the activity (should be saved across activity recreations) */
    private String activityID = null;

    /* Get the current activity instance for the given identifier */
    protected ConfigChangeSupportActivity getCurrentActivityInstance() {
        return activityInstances.get(activityID).get();
    }

    /** Find the 'state' field using introspection */
    protected Field findStateField() {
        try {
            Field f = getClass().getDeclaredField(STATE_FIELD_NAME);
            f.setAccessible(true); // to render the field accessible
            return f;
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

    /** Purge the statesMap by removing all the expired states (inserted more than x second ago) */
    private void purgeStatesMap() {
        Set<String> identifiersToBeRemoved = new HashSet<>();
        for (Map.Entry<String, Pair<Object, Long>> entry: statesMap.entrySet())
            if (entry.getValue().second < SystemClock.elapsedRealtime())
                identifiersToBeRemoved.add(entry.getKey());
        statesMap.keySet().removeAll(identifiersToBeRemoved);
    }

    /** Return the lifetime of the state in seconds (can be overridden) */
    protected int getStateLifetime() {
        return DEFAULT_STATE_LIFETIME;
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityID = savedInstanceState != null ? savedInstanceState.getString(ACTIVITY_ID_KEY) : null;
        if (activityID != null) {
            Pair<Object, Long> stateAndDate = statesMap.get(activityID);
            if (stateAndDate != null)
                try {
                    findStateField().set(this, stateAndDate.first);
                    statesMap.remove(activityID);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e); // improbable
                }
            else
                Log.w(getClass().getName(),
                        "The state of the activity " + getClass().getName() + " of identifier " + activityID
                                + " is not present");
        } else
            activityID = UUID.randomUUID().toString(); // choose a random value for the activityID

        // update the activity instances map
        activityInstances.put(activityID, new WeakReference<>(this));

        // purge the statesMap by removing all the expired states (inserted more than x second ago)
        purgeStatesMap();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(ACTIVITY_ID_KEY, activityID);
        Object state = null;
        try {
            Field f = findStateField();
            if (f != null)
                state = f.get(this);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        if (state != null)
            statesMap.put(activityID, new Pair<>(state, SystemClock.elapsedRealtime() + getStateLifetime() * 1000L));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        activityInstances.remove(activityID);
    }
}

Gestion du changement de configuration

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboard|keyboardHidden|screenSize"
          android:label="@string/app_name">

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}