Dans l'article introductif on a expliqué l'importance des activités (activity en anglais). Ce sont des éléments centraux de l'application qui s'occupent de gérer le cycle de vie d'un écran (initialisation, démarrage, arrêt, redémarrage, fin). Cet article présentera plus en détail ce composant d'une application Android. On verra aussi comment l'implémenter et comment gérer ses états, en se basant sur l'exemple de l'application qu'on développe.
Cycle de vie d'une activité
Chaque activité commence à vivre grâce à la méthode onCreate(). C'est elle qui effectue toute l'initialisation des données. Ensuite on déclenche onStart() afin d'afficher l'écran préparé à l'utilisateur. La troisième méthode appelée dans le déploiement est onResume() grâce à qui l'application peut commencer à interagir avec l'utilisateur.
L'application peut cesser d'être en communication avec l'utilisateur. La méthode onPause() désactive l'activité en cours. Elle peut être utile pour décharger l'application et, par exemple, sauvegarder les données dans la base ou arrêter les processus gourmands (animations, chargement des ressources depuis les web services). Une autre méthode sert à arrêter complètement l'activité. Elle s'appelle onStop(). Elle est invoquée quand son activité n'est plus visible à l'utilisateur. La troisième méthode du groupe "destructeurs et désactivateurs" est utilisée pour détruire l'activité. Il s'agit de l'onDestroy(). Elle est invoquée quand quelqu'un fait appel à finish() ou quand le système a besoin d'espace pour fonctionner.
Une dernière méthode qui sert à gérer le cycle de vie d'une activité est onRestart(). Elle est invoquée après onStop() et est suivi par onStart(). On peut donc deviner que son but est de redémarrer une activité arrêtée.
Exemple pratique d'une activité
Notre application contiendra une classe de base, BaseActivity, qui implémentera les points partagés par toutes les activités : création de menu, destruction de l'activité, gestion de réponses ainsi que l'affichage des erreurs. Elle héritera aussi de FragmentActivity ce qui nous permettra de l'utiliser aussi dans les écrans dont l'affichage se base sur les onglets.
Or, il est important de savoir, que l'activité, à part de gérer ses cycles de vie, gère également tout ce qui se passe sur l'écran. Elle se charge donc de créer le menu d'options et d'effectuer une action après le choix d'une option de ce menu. Ces deux opérations se font respectivement dans les méthodes onCreateOptionsMenu() et onOptionsItemSelected(). Dans notre application on va les utiliser pour changer la version linguistique de l'application.
Deux autres fonctions de BaseActivity, checkBorrowingsToReturn() et handleBorrowingsToReturnResponse(), s'occuperont de retourner une réponse à l'utilisateur quand celui-ci va demander au web service s'il y a des livres à retourner.
La fonction refreshActivity() fait appel à la méthode qu'on a déjà mentionnée, finish(). Le rôle de cette première consiste donc à refraîchir une activité après avoir détruit l'activité en cours.
showErrorBox() s'occupe de générer un dialogue avec le message d'erreur. handleResponseType() a pour but de gérer la réponse du web service.
En ce qui concerne onDestroy(), cette méthode est cruciale dans la gestion de connexion à la base de données. Si une connexion a été ouverte, elle est fermée dans onDestroy(). Cela nous permet d'éviter d'avoir des exceptions SQLite sur la connexion non réutilisable, du type java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase.
Voici le code de notre activité de base :
public class BaseActivity extends FragmentActivity { private final static String LOG_TAG = "BaseActivity"; protected String[] locales; private String newLangName; private int langPosition; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); checkBorrowingsToReturn(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { boolean result = false; if(menuItem.getItemId() == R.id.lang_change) { result = true; Log.d(LOG_TAG, "Clicked on menu item lang_change"); Resources resources = getResources(); locales = resources.getStringArray(R.array.langs); if(locales == null || locales.length == 0) { showErrorBox(resources.getString(R.string.no_langs_title), resources.getString(R.string.no_langs_msg), resources.getString(R.string.dialog_error_refresh), resources.getString(R.string.dialog_error_quit)); } else { LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); final View langModifierView = inflater.inflate(R.layout.switch_lang, (ViewGroup) null, true); AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); // set title alertDialogBuilder.setTitle(resources.getString(R.string.lang_choose)); // set view with form data alertDialogBuilder.setView(langModifierView); final Context globalContext = this; final Spinner type = (Spinner) langModifierView.findViewById(R.id.newLangSwitched); type.setOnItemSelectedListener(new OnItemSelectedListener() { @Override public void onItemSelected(AdapterView> parentView, View selectedItemView, int position, long id) { Log.d(LOG_TAG, "Found spinner"+type); newLangName = type.getSelectedItem().toString(); langPosition = position; Log.d(LOG_TAG, "Selected item position is " + position); Log.d(LOG_TAG, "Found lang name" + newLangName); } @Override public void onNothingSelected(AdapterView> parentView) {} }); // set dialog message alertDialogBuilder .setMessage(resources.getString(R.string.lang_select)) .setCancelable(true) .setPositiveButton(resources.getString(R.string.form_submit),new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog,int id) { dialog.cancel(); Locale locale = new Locale(locales[langPosition]); Locale.setDefault(locale); Configuration config = new Configuration(); config.locale = locale; globalContext.getResources().updateConfiguration(config, globalContext.getResources().getDisplayMetrics()); refreshActivity(); } }) .setNegativeButton(resources.getString(R.string.form_cancel),new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog,int id) { dialog.cancel(); } }); // create alert dialog AlertDialog alertDialog = alertDialogBuilder.create(); // show it alertDialog.show(); } } return result; } /** * Looks for user borrowings waiting for to be returned. Check is done only once by day. * @access public * @return void */ public void checkBorrowingsToReturn() { // do only when user put a correct API credentials MainApplication app = (MainApplication) getApplication(); Log.d(LOG_TAG, "App state is :"+app.canDoBorrowingCheck()); if(app.canDoBorrowingCheck()) { Log.d(LOG_TAG, "Checking at the first time"); MapuserParams = new HashMap (); userParams.put("activity", this); BorrowingReturnReminderAsynTask borrowingReturnReminderAsynTask = new BorrowingReturnReminderAsynTask(); borrowingReturnReminderAsynTask.execute(userParams); app.setLastCheckedDate(new Date()); } } public void handleBorrowingsToReturnResponse(Map result) { Log.d(LOG_TAG, "Received response " + result); if(!handleResponseType((Map ) result.get(ClientREST.KEY_STATE))) { Log.d(LOG_TAG, "Borrowing returned checked; result is"+result); List borrowings = (List ) result.get("borrowings"); String message = getResources().getString(R.string.borrowing_to_return); if(borrowings == null || borrowings.size() == 0) { message = getResources().getString(R.string.borrowing_nothing_to_return); } else { Date biggestDate = null; for(Borrowing borrowing : borrowings) { if(biggestDate == null || borrowing.getReturnDate().before(biggestDate)) { biggestDate = borrowing.getReturnDate(); } } Map config = (Map ) result.get("config"); message = String.format(message, ""+borrowings.size(), UniversalConverter. fromDateToString(biggestDate, config.get("date-format"))); } Toast toast = Toast.makeText(this, message, Toast.LENGTH_LONG); if(toast == null) Log.d(LOG_TAG, "Toast is null"); else toast.show(); } } public void handleBorrow(Map result) { Log.d(LOG_TAG, "Received response " + result + " and state " + result.get(ClientREST.KEY_STATE)); if(!handleResponseType((Map ) result.get(ClientREST.KEY_STATE))) { String message = getResources().getString(R.string.borrowing_success); Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } } /** * Reloads current activity. * @access public * @return void */ public void refreshActivity() { Intent intent = getIntent(); overridePendingTransition(0, 0); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); finish(); overridePendingTransition(0, 0); startActivity(intent); } /** * Shows error dialog box. * @access public * @return void */ public void showErrorBox(String title, String message, String positiveButtonTxt, String negativeButton) { LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View dialogErrorView = inflater.inflate(R.layout.dialog_error, (ViewGroup) null, true); AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); // set title alertDialogBuilder.setTitle(title); // set view with form data alertDialogBuilder.setView(dialogErrorView); // set dialog message alertDialogBuilder .setMessage(message) .setCancelable(true) .setPositiveButton(positiveButtonTxt, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog,int id) { dialog.cancel(); refreshActivity(); } }) .setNegativeButton(negativeButton, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog,int id) { dialog.cancel(); finish(); } }); AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog.show(); } /** * Handle the response of web service. * @access protected * @param Map response Response header returned by web service. * @return boolean True if the response contains an error. False otherwise. */ protected boolean handleResponseType(Map response) { if(response.get("state").equals("error")) { Toast.makeText(this, response.get("message"), Toast.LENGTH_LONG).show(); return true; } return false; } @Override public void onDestroy() { Log.d(LOG_TAG, "Destroying activity"); if(MainDataSource.getDatabaseInstance() != null) { Log.d(LOG_TAG, "Database is opened. Close it"); MainDataSource.close(); } super.onDestroy(); } }