יום ראשון, ינואר 30

# איחסון נתונים: Shared Preferences חלק 1.

באנדרואיד קימות מספר אפשרויות לשמירת נתונים (data persistence):
  • שמירת נתונים בקובץ.  בזיכרון פנימי, או חיצוני- למשל SD Card.
  • SQlite Database - שימוש בבסיס הנתונים הסטנדרטי - ראה פוסטים בנושא: מבוא ל-SQlite.
  • Content Provider - האמצעי היחידי (נכון לעכשיו), שמאפשר שיתוף נתונים בין אפליקציות. מתוכנן פוסט בנושא.
  • שמירה על שרת ברשת.
  • Shared Preferences  - המנגנון עליו נדון כאן. 
מתוכננים שני פוסטים על ה-Shared Preferences:
  • הראשון - הנוכחי - ידגים שמירת ושליפת נתונים מזיכרון.
  • הפוסט השני בנושא יציג את ה-Preferences Framework - מנגנון שמשלב את ה-UI עם ה-Shared Preferences. הוא נותן widgets סטנדרטים לבחירה של העדפות משתמש. הגדרת ה-UI  מתבצעת ע"י קובץ xml. עם view groups מיוחדים.
ו...ניגש לעייננו כאן:
 Shared Preferences הוא מנגנון יעיל ונוח לשמירת נתונים. הנתונים נשמרים בצמדים של key/value.
מגבלות:

  1. שמירה של נתונים "פרימיטיבים" בלבד: booleans, floats, ints, longs,  strings.
  2. הנתונים נגישים רק בתוך האפליקציה - נכון לרגע זה. עקרונית ה-shared preferences אמורים להיות נגישים גם מתוך אפליקציות אחרות.
קיימות 3 שיטות לניהול וגישה לקבצי ה-preferences, לכל שיטה API מעט שונה:

  1. (getSharedPreferences(String name, int mode: שיטה זו מאפשרת פתיחת קבצים רבים, כשכל קובץ מוגדר ע"י שם - String - ראו פרמטר ראשון של ה-API הנ"ל. לכל קובץ כזה ניתן לגשת מכל ה-Activities של האפליקציה. הפרמטר השני - security mode מגדיר מגבלות כתיבה וקריאה מתוך אפליקציות אחרות. כרגע לא רלוונטי, ואפשר לשים תמיד 0. אחזור לזה במהלך המעבר על הקוד.
  2. (getPreferences(int mode: ה-API הזה מגדיר קובץ יחיד, כך ש כאן התוכנית לא קובעת לקובץ שם. קובץ זה שייך ל-Activity בו הוא נוצר ורק ממנו היגשים לקובץ. בכל Activity אפשר להגדיר קובץ אחד כזה.
  3. ()getDefaultSharedPreferences: שימוש בקובץ יחיד. בשונה מה-getPreferences, בעזרת ה-API הזה ניתן לגשת לקובץ מכל Activity של האפליקציה.

ניגש לדוגמא: 
  • נדגים שמירה של נתונים. נשתמש ב-getSharedPreferences.
  • נדגים גם שימוש ב-  getDefaultSharedPreferences.
  • (לא נדגים את getPreference, אבל הוא מקרה פרטי של getSharedPreference).
  • נדגים גישה לנתונים מ-Activity אחר. המעבר בין ה-Activities בעזרת intents.
  • הנתונים אותם נשמור, הם מונים (counter) שעוקבים אחרי ה-callbacks של ה-Activity Lifecycle.
    • נשים מונה בשישה callbacksL:
      • onCreate
      • onStart
      • onResume
      • onPause
      • onStop
      • onDestroy
  • אגב, זוהי הזדמנות לעקוב אחרי ה-life cycle של ה-Activity ע"י צפיה בהתקדמות המונים.
הנה התצוגה של ה-Activity הראשי:




על המסך מופיעים:
  • כפתור למעבר ל-Activity השני. המעבר בין ה-Activities מפעיל את ששת ה-callbacks של  lifecycle וכך המונים מתקדמים.
  • כפתור לאיפוס ה-counters.
  • תצוגה של ששת ה-lifecycle counters + תצוגה של מס הלחיצות על הכפתור. כל 7 המונים הנ"ל נשמרים בקובץ ה-preferences.
התצוגה של ה-UI השני דומה, כך שמיותר להציג אותה.


הקוד:
הקוד מורכב משני  Activities:
  1. ה-Activity הראשי בו נשמרים ומוצגים ה-counters.
  2. ה-Activity השני שמבצע שליפה של הנתונים והצגתם.
המעבר בין ה-Activities ע"י הכפתור ששולח explicit intent.


הנה ה-Activity הראשי:

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class SimpleSharedPrefs extends Activity {
   
    Integer createCnt;
    Integer    startCnt;
    Integer resumeCnt;
    Integer    pauseCnt;
    Integer stopCnt;
    Integer destroyCnt;
    Integer    buttonPushCnt;
   
    public static final String CREATE_CNT = "CREATE_CNT";
    public static final String START_CNT = "START_CNT";
    public static final String RESUME_CNT = "RESUME_CNT";
    public static final String PAUSE_CNT = "PAUSE_CNT";
    public static final String STOP_CNT = "STOP_CNT";
    public static final String DESTROY_CNT = "DESTROY_CNT";
    public static final String BUTTON_CNT = "BUTTON_CNT";

    public static final String PREFS_NAME = "PREFS_NAME";
   
    SharedPreferences prefs;
    SharedPreferences prefsDefault;

   
   TextView textViewLifecycleCnt;
   TextView textViewButtonPushCnt;
   Button button_push;
   Intent intent;
   
   
  1.    @Override
  2.    public void onCreate(Bundle savedInstanceState) {
  3.        super.onCreate(savedInstanceState);
  4.        setContentView(R.layout.main);
  5.        textViewLifecycleCnt = (TextView)findViewById(R.id        .textview_lifecycle_cnt);
  6.        textViewButtonPushCnt = (TextView)findViewById(R.id        .text_viewbutton_push_cnt);
  7.        Button button_push = (Button)findViewById(R.id.button_push);
  8.        Button buttonClean = (Button)findViewById(R.id.button_clean);
  9.        restoreAll();
  10.        createCnt+=1;
  11.        buttonClean.setOnClickListener(new View.OnClickListener() {
  12.            @Override
  13.            public void onClick(View v) {
  14.              createCnt           = 0;
  15.              startCnt           = 0;
  16.              resumeCnt           = 0;
  17.              pauseCnt           = 0;
  18.              stopCnt           = 0;
  19.              destroyCnt       = 0;
  20.              buttonPushCnt    = 0;
  21.              displayAll();
  22.            }
  23.           });         
  24.         ButtonListener buttonListener = new ButtonListener();
  25.         button_push.setOnClickListener(buttonListener);
  26.         intent = new Intent (this, SecondActivity.class);
  27.     }
  28.     @Override
  29.     public void onStart() {
  30.         super.onStart();
  31.         startCnt++;
  32.     }
  33.     @Override
  34.     public void onResume() {
  35.         super.onResume();
  36.         resumeCnt++;
  37.         displayAll();
  38.     }
  39.     @Override
  40.     public void onPause() {
  41.         super.onPause();
  42.         pauseCnt++;
  43.         saveAll();
  44.     }
  45.     @Override
  46.     public void onStop() {
  47.         super.onStop();
  48.         stopCnt++;
  49.         saveAll();
  50.     }
  51.     @Override
  52.     public void onDestroy() {
  53.         destroyCnt++;
  54.         saveAll();
  55.         super.onDestroy();
  56.     }
  57. /* Before onStop. No guarantee if before or after onPause */
  58.       @Override
  59.         public void onSaveInstanceState(Bundle saveInstanceState) {
  60.     //      saveInstanceState.putInt(CREATE_CNT, createCnt);
  61.         }
  62.       /* Invoked after onStart: */
  63.       @Override
  64.       public void onRestoreInstanceState(Bundle savedInstanceState) {
  65.         if (savedInstanceState != null)
  66.           if (savedInstanceState.containsKey(CREATE_CNT)){
  67.     //          createCnt = savedInstanceState.getInt(CREATE_CNT, 0);
  68.           }
  69.       }
  70.        class ButtonListener implements View.OnClickListener{
  71.            public void onClick(View view) {
  72.               buttonPushCnt++; 
  73.               textViewButtonPushCnt.setText("Push Button Counter = "+buttonPushCnt);
  74.               startActivity(intent);
  75.               finish();
  76.            }
  77.       }
  78.        private void saveAll(){
  79.            // Demonstrate both API: getDefaultSharedPreferences and getSharedPreferences
  80.            // (Each API could do the job by itself)
  81. // Demo API 1:          
  82.             Context context = getApplicationContext();
  83.             prefsDefault = PreferenceManager.getDefaultSharedPreferences(context);
  84.             Editor editor1 = prefs.edit();
  85.             editor1.putInt(CREATE_CNT, createCnt);
  86.             editor1.commit(); 
  87. // Demo API 2:
  88.             prefs = getSharedPreferences(PREFS_NAME, MODE_WORLD_WRITEABLE);
  89.             Editor editor = prefs.edit();
  90.             editor.putInt(CREATE_CNT, createCnt);
  91.             editor.putInt(START_CNT, startCnt);
  92.             editor.putInt(RESUME_CNT, resumeCnt);
  93.             editor.putInt(PAUSE_CNT, pauseCnt);
  94.             editor.putInt(STOP_CNT, stopCnt);
  95.             editor.putInt(DESTROY_CNT, destroyCnt);
  96.             editor.putInt(BUTTON_CNT, buttonPushCnt);
  97.             editor.commit(); 
  98.            
  99.        }
  100.         private void restoreAll(){
  101.             // Demo API 1:
  102.             Context context = getApplicationContext();
  103.             prefsDefault = PreferenceManager.getDefaultSharedPreferences(context);
  104.             createCnt = prefsDefault.getInt(CREATE_CNT, 0);
  105.             // Demo API 2:
  106.             prefs = getSharedPreferences(PREFS_NAME, 0);
  107.             startCnt  = prefs.getInt(START_CNT, 0);
  108.             resumeCnt = prefs.getInt(RESUME_CNT, 0);
  109.             pauseCnt  = prefs.getInt(PAUSE_CNT, 0);
  110.             stopCnt   = prefs.getInt(STOP_CNT, 0);
  111.             destroyCnt   = prefs.getInt(DESTROY_CNT, 0);
  112.             buttonPushCnt = prefs.getInt(BUTTON_CNT, 0);
  113.         }
  114.       
  115.       private void displayAll(){
  116.           textViewLifecycleCnt.setText("Create Counter = "+createCnt
  117.                 +"\nStart Counter = "+startCnt
  118.                   +"\nResume Counter =  "+resumeCnt
  119.                   +"\nPause Counter = "+pauseCnt
  120.                   +"\nStop Counter =  "+stopCnt
  121.                   +"\nDestroy Counter =  "+destroyCnt);
  122.           textViewButtonPushCnt.setText("Push Button Counter = "+buttonPushCnt);
  123.       }
  124. }

 נתרכז בשתי המתודות שעוסקות ב-Shared Preferences:
  1. restoreAll
  2. saveAll
restoreAll:

  1.   private void restoreAll(){
  2.             // Demo API 1:
  3.             Context context = getApplicationContext();
  4.             prefsDefault = PreferenceManager.getDefaultSharedPreferences(context);
  5.             createCnt = prefsDefault.getInt(CREATE_CNT, 0);
  6.             // Demo API 2:
  7.             prefs = getSharedPreferences(PREFS_NAME, 0);
  8.             startCnt  = prefs.getInt(START_CNT, 0);
  9.             resumeCnt = prefs.getInt(RESUME_CNT, 0);
  10.             pauseCnt  = prefs.getInt(PAUSE_CNT, 0);
  11.             stopCnt   = prefs.getInt(STOP_CNT, 0);
  12.             destroyCnt   = prefs.getInt(DESTROY_CNT, 0);
  13.             buttonPushCnt = prefs.getInt(BUTTON_CNT, 0);
  14.         }
  15.       

כמו שצוין קודם, מודגמים שני API לשמירה ב-preferences.
  • ה-API הראשון (שורות 2-5), מטפל (=שומר) במונה אחד. 
  • ה-API השני מטפל ביתר 7 המונים.
ראו שורות 1-3 למטה:
  •  ה-DefaultSharedPreferences משתמש בקובץ יחיד משותף לכל האפליקציה. מביאים אותו תוך שימוש ב-context.
  • שורה 3: ה-key, במקרה הזה הוא  CREATE_CNT, והוא מתייחס למונה של מתודת onCreate.
  • שורה 3: אם לא קיים בקובץ רקורד עם key=CREATE_CNT, הוא ייוצר כעת. הפרמטר השני של המתודה getInt, הוא הערך שיוחזר במקרה שהרקורד לא היה קיים - במקרה הזה  0.

  1.     Context context = getApplicationContext();
  2.             prefsDefault = PreferenceManager.getDefaultSharedPreferences(context);
  3.             createCnt = prefsDefault.getInt(CREATE_CNT, 0);

ה-API השני מטפל ביתר ששת הפרמטרים:


  1.    prefs = getSharedPreferences(PREFS_NAME, 0);
  2.             startCnt  = prefs.getInt(START_CNT, 0);
  3.             resumeCnt = prefs.getInt(RESUME_CNT, 0);
  4.             pauseCnt  = prefs.getInt(PAUSE_CNT, 0);
  5.             stopCnt   = prefs.getInt(STOP_CNT, 0);
  6.             destroyCnt   = prefs.getInt(DESTROY_CNT, 0);
  7.             buttonPushCnt = prefs.getInt(BUTTON_CNT, 0);

 שורה מס 1: מביאה את הקובץ:
ה-API מקבל שני פרמטרים:
  1. שם הקובץ. 
  2. ה-security mode, יכול - תאורטית - לקבל 3 ערכים:
    1. 0 או MODE PRIVATE
    2. MODE_WORLD_WRITEABLE
    3. MODE_WORL_READABLE
בשלב זה, ה-preferences לא יכול להיות משותף בין אפליקציות, כך שאין לערכים הנ"ל משמעות. השתמשו בינתיים באופציה הראשונה = 0.


שורות 2-7  שולפות מהקובץ את ששת ה-counters שנישמרים בו.
  • גם כאן, הגישה היא ע"י ה-key. 
  • אם אין רקורד מתאים, הוא ייוצר עם הפקודה - getInt (או  getFloat, getString, getBoolean וכדומה). במקרה זה, ערך שיוחזר הוא הפרמטר השני במתודה get - אצלנו שמנו תמיד אפס.

saveAll


  1. private void saveAll(){
  2.            // Demonstrate both API: getDefaultSharedPreferences and getSharedPreferences
  3.            // (Each API could do the job by itself)
  4. // Demo API 1:          
  5.             Context context = getApplicationContext();
  6.             prefsDefault = PreferenceManager.getDefaultSharedPreferences(context);
  7.             Editor editor1 = prefs.edit();
  8.             editor1.putInt(CREATE_CNT, createCnt);
  9.             editor1.commit(); 
  10. // Demo API 2:
  11.             prefs = getSharedPreferences(PREFS_NAME, MODE_WORLD_WRITEABLE);
  12.             Editor editor = prefs.edit();
  13.             editor.putInt(CREATE_CNT, createCnt);
  14.             editor.putInt(START_CNT, startCnt);
  15.             editor.putInt(RESUME_CNT, resumeCnt);
  16.             editor.putInt(PAUSE_CNT, pauseCnt);
  17.             editor.putInt(STOP_CNT, stopCnt);
  18.             editor.putInt(DESTROY_CNT, destroyCnt);
  19.             editor.putInt(BUTTON_CNT, buttonPushCnt);
  20.             editor.commit(); 
  21.            
  22.        }

בהתאם למתודה הקודמת, restoreAll, שמירת הפרמטרים נעשית תוך הדגמת שתי השיטות:

  1. שורות 5-9: מונה ה-onCreate  עם ה-getDefaultSharedPreferences
  2. שורות 11-20: יתר ששת המונים עם  getSharedPreferences.
 חוץ מה-API שמביא את קובץ ה- preferences, (שורה 6\שורה 11, אותן ראינו כבר במתודה restoreAll), התהליכים בשני המקרים זהים:
שורה 7\שורה 12: יצירת אובייקט edit
שורה 8\שורות 13-19: שמירת הפרמטרים.
שורה 9\שורה 20: הפעלת commit.  בלי commit השמירה לא תתבצע.



לא נתעכב על ה-Activity השני, היות שהוא דומה מאד לראשון הנ"ל.
ה-UI שלו כולל:
  • כפתור שלחיצה עליו תפעיל את ה-Activity הראשי. שוב, ע"י שליחת intent.
  • תצוגה של ה-counters שנשלפים מתוך ה-preferences.


בכל זאת הנה ה-Activity:


import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class SecondActivity extends Activity {
    Intent intent;
    Integer createCnt;
    Integer    startCnt;
    Integer resumeCnt;
    Integer    pauseCnt;
    Integer stopCnt;
    Integer destroyCnt;
    Integer    buttonPushCnt;
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main2);
        Button    button = (Button)findViewById(R.id.button_push);
        restoreAll();
        displayAll();
         intent = new Intent(this, SimpleSharedPrefs.class);
        button.setOnClickListener(new View.OnClickListener() {
        @Override
            public void onClick(View v) {
                startActivity(intent);
            }
        });
     }
    private void restoreAll(){
// Demo API 1:
        Context context = getApplicationContext();
        SharedPreferences prefsDefault = PreferenceManager.getDefaultSharedPreferences(context);
        createCnt = prefsDefault.getInt(SimpleSharedPrefs.CREATE_CNT, 0);
// Demo API 2:
        SharedPreferences prefs = getSharedPreferences(SimpleSharedPrefs.PREFS_NAME, MODE_WORLD_WRITEABLE);
        startCnt  = prefs.getInt(SimpleSharedPrefs.START_CNT, 0);
        resumeCnt = prefs.getInt(SimpleSharedPrefs.RESUME_CNT, 0);
        pauseCnt  = prefs.getInt(SimpleSharedPrefs.PAUSE_CNT, 0);
        stopCnt   = prefs.getInt(SimpleSharedPrefs.STOP_CNT, 0);
        destroyCnt   = prefs.getInt(SimpleSharedPrefs.DESTROY_CNT, 0);
        buttonPushCnt = prefs.getInt(SimpleSharedPrefs.BUTTON_CNT, 0);
    }
  private void displayAll(){
        TextView    textView = (TextView)findViewById(R.id.text_view);
        textView.setText("Create Counter = "+createCnt
            +"\nStart Counter = "+startCnt
              +"\nResume Counter =  "+resumeCnt
              +"\nPause Counter = "+pauseCnt
              +"\nStop Counter =  "+stopCnt
              +"\nDestroy Counter =  "+destroyCnt
              +"\nPush=Button Counter =  "+buttonPushCnt
              +"\nPush Button Counter = "+buttonPushCnt);
  }
}





2 תגובות:

  1. אחי קודם כל תודה על כל ההשקעה זה באמת מדריך מושקע ומפורט...כל הכבוד!! דבר שני רציתי לשאול אותך איך אני יכול לדעת לאיזה ACTIVITY שייך כל קובץ JAVA זאת אומרת לאיזה XML אני רוצה לשמור נתונים מ DATAPICKER ו TIMEPICKER ולהציג אותם בACTIVITY חדש בטבלה יש לך מושג איל אני יכול לעשות את זה?? תודה איתן.

    השבמחק
  2. למרות שזה פוסט יישן תודה רבה :-) מעניין מאוד

    השבמחק