僅需6步,教你輕易撕掉app開發框架的神祕面紗(5):數據持久化

遇到的問題

有的時候程序中需要全局皆可訪問的變量,比如:用戶是否登錄,用戶個人信息(用戶名,地區,生日),或者一些其他信息如:是否是首次登錄,是否需要顯示新手引導等等。

其中有些數據需要持久化到本地硬盤中,比如:
大多數應用,當用戶第一次啓動應用的時候,需要顯示應用介紹和新手引導的頁面。

而應用介紹只在第一次啓動時顯示。所以我們需要記錄一個值表示當前是否已經顯示過了應用介紹。並且每次在應用開啓的時候檢查這個值是否存在,如果存在則直接跳入主頁面,否則就顯示應用介紹。

另外新手引導這個東西是分步驟的,當用戶第一次進入主頁面的時候,可能會提示用戶去做什麼,比如提示用戶註冊,登錄之類的。可能會有很多步的新手引導。
這時候問題就來了,如果新手引導一共有5步,而用戶只看了新手引導前2步之後就退出程序。當他重新打開應用的時候。前兩步就不應該顯示了。
所以此時需要記錄下當前新手引導已經走到了第幾步。當需要顯示新手引導的時候要檢查是否用戶已經看過了這個新手引導。如果看過了則不顯示引導。

另外,有些全局數據則不需要持久化到硬盤,比如:
用戶是否登錄了,用戶上次網絡請求的時間,服務器當前時間,等等。

解決方案

從需求上看,這些數據都是簡單數據,並且無需擔心安全問題,因此可以使用系統自帶的鍵值存儲系統來存儲這些值。

  • android 使用 SharedPreferece。
  • iOS 使用 NSUserDefaults。

許多人可能會認爲,系統調用誰不會,是個人都知道,哪有必要寫一個單章出來,還推到框架的高度。
系統調用直接使用確實很簡單,也能得到正確結果。但是問題也是顯而易見的:

  1. 規範:雖然是簡單的系統調用代碼,但是不同的人使用仍然會寫出不同的代碼,爲了底層代碼的一致,所以把系統調用封裝起來。
  2. 多態:封裝系統調用的另一個目的是,如果哪天不能用鍵值存儲系統,改成數據庫存取,則只需修改一處。
  3. 防止濫用:因爲是鍵值存儲,所以,對於鍵的取名,可能就比較隨意了。封裝之後,程序員需要把鍵寫在同一個或幾個文件中,防止命名重複,並且便於review代碼。
  4. 對於android來說,持久化數據有着更深的意義,因爲android系統的原因,導致應用進入後臺後,重新回到前臺,靜態變量會變爲null,所以對於這種數據也可以使用數據持久化來解決問題。

    由此可知,對於任何系統調用的封裝都是有意義的。

實現代碼

在這裏我們需要定義一個父類,把系統調用全部封裝在父類中,子類直接調用save/get方法即可。

//android: BaseModel.java
public class BaseModel extends BaseRecord {    
    private static final String TAG = "BaseModel";
    private static final String OBJ_PREFIX = "__object__";//防止重名,因此添加的特殊前綴
    private static final String SHAREDPREFERENCE_FILE = "__sharedpreference_file__";//防止重名,因此添加的特殊前綴

    public void save(String key, float value){
        SharedPreferences sharedPreferences = Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = sharedPreferences.edit();
        edit.putFloat(key, value);
        edit.apply();
        edit.commit();
    }

    public void save(String key, int value){
        SharedPreferences sharedPreferences = Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = sharedPreferences.edit();
        edit.putInt(key, value);
        edit.apply();
        edit.commit();
    }

    public void save(String key, String value){
        SharedPreferences sharedPreferences = Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = sharedPreferences.edit();
        edit.putString(key, value);
        edit.apply();
        edit.commit();
    }

    public void save(String key, Serializable value){
        FileOutputStream fos = null;
        try {
            fos = Util.context.openFileOutput(OBJ_PREFIX + TAG + key + OBJ_PREFIX, Context.MODE_PRIVATE);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(value);
        } catch (FileNotFoundException e) {
            LogUtil.e(TAG, e);
        } catch (IOException e) {
            LogUtil.e(TAG, e);
        } finally {
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    LogUtil.e(TAG, e);
                }
            }
        }
    }

    public String getString(String key){
        return Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE).getString(key, "null");
    }

    public int getInt(String key){
        return Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE).getInt(key, Integer.MAX_VALUE);
    }

    public float getFloat(String key){
        return Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE).getFloat(key, Integer.MAX_VALUE);
    }

    public Serializable getSerializable(String key){
        FileInputStream fis = null;
        try {
            fis = Util.context.openFileInput(OBJ_PREFIX + TAG + key + OBJ_PREFIX);
            ObjectInputStream ois = new ObjectInputStream(fis);
            return (Serializable) ois.readObject();
        } catch (FileNotFoundException e) {
            LogUtil.e(TAG, e);
        } catch (StreamCorruptedException e) {
            LogUtil.e(TAG, e);
        } catch (IOException e) {
            LogUtil.e(TAG, e);
        } catch (ClassNotFoundException e) {
            LogUtil.e(TAG, e);
        } finally {
            if (fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    LogUtil.e(TAG, e);
                }
            }
        }
        return null;
    }
}
//iOS: BaseModel.h
//單例聲明的宏,可在子類.h文件中直接使用
#define CREATE_SINGLE_INSTANCE_IN_MODEL_H +(instancetype)getInstance

//單例聲明的宏,可在子類.m文件中直接使用
#define CREATE_SINGLE_INSTANCE_IN_MODEL_M(modelType) \
+(instancetype)getInstance{ \
    static modelType *sModel; \
    if (![modelType isSubclassOfClass: [BaseModel class]]) { \
        return nil; \
    } \
    if (!sModel) { \
        sModel = [[modelType alloc] init]; \
    } \
    return sModel; \
}

//下面是setget方法的宏,可在子類直接使用,自動調用父類save/get。
//需要注意的是:這種寫法可能有些問題。這樣變量都成了強引用。寫了copy也無用。不過沒有影響。
//具體要看編譯器如何實現。它可以避免此種問題,也可以不避免。
#define CREATE_SETGET_IN_MODEL_H(type, copyOrStrong, param)     \
    @property (nonatomic, copyOrStrong) type param

#define CREATE_SETGET_IN_MODEL_M(type, param, upperFirstParam)  \
                                                                \
@synthesize param;                                              \
-(void)set##upperFirstParam:(type)p##param{                     \
    param = p##param;                                           \
    [self save:@#param data:param];                             \
}                                                               \
                                                                \
-(type)param{                                                   \
    if(!param){                                                 \
        type saved = [self get:@#param];                        \
        if (saved) {                                            \
            param = saved;                                      \
        }                                                       \
    }                                                           \
    return param;                                               \
}


@interface BaseModel : NSObject

-(void) save: (NSString *)key data: (id)data;

-(id) get: (NSString *)key;

@end
//iOS: BaseModel.m

#import "BaseModel.h"

@implementation BaseModel

//iOS只能存儲簡單數據,NSString, NSNumber, NSArray, NSDictionary。否則會報錯。
-(void) save:(NSString *)key data:(id)data{
    [[NSUserDefaults standardUserDefaults] setObject:data forKey:key];
}

-(id) get:(NSString *)key{
    return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}

@end

代碼清單:

android:
    BaseModel.java
iOS:
    BaseModel.h
    BaseModel.m
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章