第一行代碼讀書筆記 6 -- 持久化技術(上)

本篇文章主要介紹以下幾個知識點:

  • 文件存儲;
  • SharedPreference 存儲;
  • 實戰:實現記住密碼功能
圖片來源網絡

數據持久化是指將那些內存中的瞬時數據保存到存儲設備中,保證即使在手機或電腦關機的情況下,這些數據仍然不會丟失。保存在內存中的數據是處于瞬時狀態的,而保存在存儲設備中的數據是處于持久狀態的,持久化技術則是提供了一種機制可以讓數據在瞬時狀態和持久狀態之間進行轉換。

Android 系統中主要提供了三種方式實現數據持久化功能, 即文件存儲、SharedPreference 存儲以及數據庫存儲。當然,也可以將數據保存在手機的 SD 卡中,但相對會復雜一些,而且不安全。

6.1 文件存儲

文件存儲是 Android 中最基本的一種數據存儲方式,它不對存儲的內容進行任何的格式化處理,所有數據都是原封不動地保存到文件當中的,因而它比較適合用于存儲一些簡單的文本數據或二進制數據。

Context 類中提供了一個 openFileOutput () 方法,用于將數據存儲到指定的文件中。Context 類中還提供了一個 openFileInput() 方法,用于從文件中讀取數據。

下面通過一個例子學習下在 Android 項目中使用文件存儲的技術。首先創建一個項目,并修改其布局代碼:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"  >

    <EditText
        android:id="@+id/edit_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Type something here"/>

</RelativeLayout>

這里只是在布局中加入了一個 EditText,用于輸入文本內容。我們的目的是在文本輸入框中隨意輸入內容,然后按下 Back 鍵,將輸入的內容保存到文件中,再重新啟動程序,剛才輸入的內容不丟失。

修改 activity 中的代碼(具體內容見注解),如下:

public class FilePersistenceActivity extends AppCompatActivity {

    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_file_persistence);
        
        editText = (EditText) findViewById(R.id.edit_test);
        String inputText = load();
        if (!TextUtils.isEmpty(inputText)){
            editText.setText(inputText);
            editText.setSelection(inputText.length()); // 將輸入光標移到文本末尾位置
            ToastUtils.showShort("重啟讀取存儲的內容成功");
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        String inputText = editText.getText().toString();
        // 在活動銷毀前把輸入的內容存儲到文件中
        save(inputText);
    }

    /**
     * 把內容存儲到文件
     * @param inputText 存儲的內容
     */
    public void save(String inputText) {
        FileOutputStream out = null;
        BufferedWriter writer = null;
        try {
            // openFileOutput方法接收兩個參數:
            // 第一個是不包含路徑的文件名 (默認存儲到/data/data/<package name>/files/目錄下)
            // 第二個是文件的操作模式,有兩種可選:
            // 1.MODE_PRIVATE(默認):當指定同樣文件名的時候,所寫入的內容將會覆蓋原文件中的內容
            // 2.MODE_APPEND:若該文件已存在就往文件里面追加內容,不存在就創建新文件。
            // (注:其他操作模式過于危險,在 Android 4.2 已被廢棄)
            // openFileOutput方法返回的是一個FileOutputStream對象
            out = openFileOutput("data", Context.MODE_PRIVATE);
            // 構建OutputStreamWriter對象后,構建BufferedWriter對象
            writer = new BufferedWriter(new OutputStreamWriter(out));
            // 將文本寫入文件
            writer.write(inputText);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null){
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 從文件中讀取數據
     * @return 讀取的內容
     */
    public String load(){
        FileInputStream in = null;
        BufferedReader reader = null;
        StringBuilder content = new StringBuilder();
        try {
            // openFileInput只接收一個參數,即要讀取的文件名,然后系統會自動到
            // /data/data/<package name>/files/目錄下去加載這個文件,并返回一個FileInputStream對象.
            in = openFileInput("data");
            // 構建InputStreamReader對象后,構建BufferedReader對象
            reader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            // 讀取內容
            while ((line = reader.readLine())!= null){
                content.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null){
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return content.toString();
    }
}

運行程序,輸入wonderful 后退出程序再重啟,剛輸入的內容并不會丟失,還原到了輸入框中,如圖所示:

還原保存成功的內容

文件存儲所用到的核心技術就是 Context 類中提供的 openFileInput() 和 openFileOutput() 方法,之后就是利用 Java 的各種流來進行讀寫操作就可以了。

文件存儲的方式并不適合用于保存一些較為復雜的文本數據,因此,下面就來學習一下 Android 中另一種數據持久化的方式,它比文件存儲更加簡單易用,而且可以很方便地對某一指定的數據進行讀寫操作。

6.2 SharedPreferences 存儲

不同于文件的存儲方式,SharedPreferences 是使用鍵值對的方式來存儲數據的。

6.2.1 將數據存儲到 SharedPreferences 中

要想使用 SharedPreferences 來存儲數據,首先需要獲取到 SharedPreferences 對象。Android 提供了三種方法得到 SharedPreferences 對象:

  • Context 類中的 getSharedPreferences()方法
    ??此方法接收兩個參數,第一個參數指定 SharedPreferences 文件的名稱,第二個參數指定操作模式,目前只有 MODE_PRIVATE 一種模式,和直接傳入 0 效果相同。其他幾種模式已被廢棄。

  • Activity 類中的 getPreferences()方法
    ??此方法和上面的方法相似,但只接收一個操作模式參數,使用這個方法時會自動將當前活動的類名作為 SharedPreferences 的文件名。

  • PreferenceManager 類中的 getDefaultSharedPreferences()方法
    ??這是一個靜態方法,它接收一個 Context 參數,并自動使用當前應用程序的包名作為前綴來命名 SharedPreferences 文件。

得到了 SharedPreferences 對象之后,分為三步實現向 SharedPreferences 文件中存儲數據:
??(1)調用 SharedPreferences 對象的 edit()方法來獲取一個 SharedPreferences.Editor 對象。
??(2)向 SharedPreferences.Editor 對象中添加數據,如添加一個布爾型數據使用 putBoolean 方法,添加一個字符串使用 putString()方法,以此類推。
??(3) 調用 apply()方法將添加的數據提交,完成數據存儲。

當然,SharedPreference在提交數據時也可用 Editor 的 commit 方法,兩者區別如下:

  • apply() 沒有返回值而 commit() 返回 boolean 表明修改是否提交成功
  • apply() 將修改提交到內存,然后再異步提交到磁盤上;而 commit() 是同步提交到磁盤上。** 谷歌建議:若在UI線程中,使用 apply() 減少UI線程的阻塞(寫到磁盤上是耗時操作)引起的卡頓。**

接下來通過個例子來體驗下。新建一個項目,修改其布局文件代碼,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/save_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="保存數據" />

</LinearLayout>

放一個按鈕,將一些數據存儲到SharedPreferences 文件當中。然后修改 Activity 中的代碼,如下所示:

public class SharePreferencesActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_share_preferences);

        Button save_data = (Button) findViewById(R.id.save_data);
        save_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 1.指定文件名為 wonderful,并得到 SharedPreferences.Editor 對象
                SharedPreferences.Editor editor = getSharedPreferences("wonderful",MODE_PRIVATE).edit();
                // 2.添加數據
                editor.putString("name","開心wonderful");
                editor.putInt("age",20);
                editor.putBoolean("married",false);
                // 3.數據提交
                editor.apply();
            }
        });
    }
}

運行程序,點擊按鈕后,這時數據已保存成功了。接下來借助 File Explorer 來進行查看:打開 Android Device Monitor→點擊 File Explorer 標簽頁→進入/data/data/com. wonderful.myfirstcode/shared_prefs /目錄,可以看到生成了一個 wonderful.xml 文件,如下:

生成的 wonderful.xml 文件

用記事本打開這個文件,里面內容如圖所示:

wonderful.xml 文件中的內容

可以看到,在按鈕的點擊事件中添加的所有數據都已經成功保存下來了,并且SharedPreferences 文件是使用 XML 格式來對數據進行管理的。

6.2.2 從 SharedPreferences 中讀取數據

SharedPreferences 對象中提供了一系列的 get 方法用于對存儲的數據進行讀取,每種 get 方法都對應了 SharedPreferences. Editor 中的一種 put 方法。這些 get 方法接收兩個參數,第一個參數是鍵,即傳入存儲數據時使用的鍵;第二個參數是默認值,即當傳入的鍵找不到對應的值時,返回默認值。

還是例子實在,修改上面項目中的布局代碼:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"  >

    <Button
        android:id="@+id/save_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="保存數據" />

    <Button
        android:id="@+id/restore_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="讀取數據" />
    
    <TextView
        android:id="@+id/show_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

這里增加了一個還原數據的按鈕和 TextView,點擊按鈕來從 SharedPreferences 文件中讀取數據并在 TextView 中顯示讀取的數據。修改 Activity 中的代碼,如下所示:

public class SharePreferencesActivity extends AppCompatActivity {
    
    private Button save_data,restore_data;
    
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_share_preferences);

        save_data = (Button) findViewById(R.id.save_data);
        save_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 1.指定文件名為 wonderful,并得到 SharedPreferences.Editor 對象
                SharedPreferences.Editor editor = getSharedPreferences("wonderful",MODE_PRIVATE).edit();
                // 2.添加不同類型的數據
                editor.putString("name","開心wonderful");
                editor.putInt("age",20);
                editor.putBoolean("married",false);
                // 3.數據提交
                editor.apply();
            }
        });

        textView = (TextView) findViewById(R.id.show_data);
        restore_data = (Button) findViewById(R.id.restore_data);
        restore_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 獲得 SharedPreferences 對象
                SharedPreferences pref = getSharedPreferences("wonderful",MODE_PRIVATE);
                // 獲取相應的值
                String name = pref.getString("name","");
                int age = pref.getInt("age",0);
                boolean married = pref.getBoolean("married",false);
                // 將獲取到的值顯示
                textView.setText("name is " + name + ",age is "+ age + ",married is "+ married);
            }
        });
    }
}

重新運行一下程序,并點擊界面上的 讀取數據 按鈕,然后查看 TextView中顯示的信息,如下:

讀取數據并顯示效果

所有之前存儲的數據都成功讀取出來了!相比之下,SharedPreferences 存儲要比文件存儲簡單方便了許多。

6.2.3 實現記住密碼功能

接下來,通過一個實現記住密碼功能,來加深對 SharedPreferences 的學習。這里繼續用上一章廣播的強制下線的例子。

修改項目前,先來簡單封裝下關于 SharedPreferences 的工具類,如下:

/**
 * SharePreference封裝
 * Created by KXwon on 2016/7/1.
 */
public class PrefUtils {

    private static final String PREF_NAME = "config";

    /**
     * 讀取布爾數據
     * @param ctx 上下文
     * @param key 鍵
     * @param defaultValue 默認值
     * @return
     */
    public static boolean getBoolean(Context ctx, String key,
                                     boolean defaultValue) {
        SharedPreferences sp = ctx.getSharedPreferences(PREF_NAME,
                Context.MODE_PRIVATE);
        return sp.getBoolean(key, defaultValue);
    }

    /**
     * 添加布爾數據
     * @param ctx 上下文
     * @param key 鍵
     * @param value 添加的數據
     */
    public static void setBoolean(Context ctx, String key, boolean value) {
        SharedPreferences sp = ctx.getSharedPreferences(PREF_NAME,
                Context.MODE_PRIVATE);
        sp.edit().putBoolean(key, value).apply();
    }

    /**
     * 讀取字符串
     * @param ctx
     * @param key
     * @param defaultValue
     * @return
     */
    public static String getString(Context ctx, String key, String defaultValue) {
        SharedPreferences sp = ctx.getSharedPreferences(PREF_NAME,
                Context.MODE_PRIVATE);
        return sp.getString(key, defaultValue);
    }

    /**
     * 添加字符串
     * @param ctx
     * @param key
     * @param value
     */
    public static void setString(Context ctx, String key, String value) {
        SharedPreferences sp = ctx.getSharedPreferences(PREF_NAME,
                Context.MODE_PRIVATE);
        sp.edit().putString(key, value).apply();
    }

    /**
     * 讀取int類型數據
     * @param ctx
     * @param key
     * @param defaultValue
     * @return
     */
    public static int getInt(Context ctx, String key, int defaultValue) {
        SharedPreferences sp = ctx.getSharedPreferences(PREF_NAME,
                Context.MODE_PRIVATE);
        return sp.getInt(key, defaultValue);
    }

    /**
     * 添加int類型數據
     * @param ctx
     * @param key
     * @param value
     */
    public static void setInt(Context ctx, String key, int value){
        SharedPreferences sp = ctx.getSharedPreferences(PREF_NAME,
                Context.MODE_PRIVATE);
        sp.edit().putInt(key, value).apply();
    }

    /**
     * 將數據全部清除掉
     * @param ctx
     */
    public static void clear(Context ctx){
        SharedPreferences sp = ctx.getSharedPreferences(PREF_NAME,
                Context.MODE_PRIVATE);
        sp.edit().clear().apply();
    }
}

然后來編輯下登錄界面,修改 activity_login.xml 中的代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!--***************** 賬號 *********************-->
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:textSize="18sp"
            android:text="賬號:"/>

        <EditText
            android:id="@+id/et_account"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    </LinearLayout>

    <!--***************** 密碼 *********************-->
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:textSize="18sp"
            android:text="密碼:"/>

        <EditText
            android:id="@+id/et_password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword"/>
    </LinearLayout>

    <!--***************** 是否記住密碼 *********************-->
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <CheckBox
            android:id="@+id/cb_remember_pass"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:layout_gravity="center_vertical"
            android:text="記住密碼"/>

    </LinearLayout>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_margin="10dp"
        android:text="登錄"/>

</LinearLayout>

只是添加了個 CheckBox 來勾選記住密碼,接著修改 LoginActivity 的代碼,如下:

public class LoginActivity extends BaseActivity {

    private EditText et_account, et_password;
    private CheckBox cb_remember_pass;
    private Button btn_login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        et_account = (EditText) findViewById(R.id.et_account);
        et_password = (EditText) findViewById(R.id.et_password);
        cb_remember_pass = (CheckBox) findViewById(R.id.cb_remember_pass);
        btn_login = (Button) findViewById(R.id.btn_login);
        
        Boolean isRemember = PrefUtils.getBoolean(this,"remember_pass",false);
        if (isRemember){
            // 將賬號和密碼都設置到文本框中
            String account = PrefUtils.getString(this,"account","");
            String password = PrefUtils.getString(this,"password","");
            et_account.setText(account);
            et_password.setText(password);
            cb_remember_pass.setChecked(true);
        }

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String account = et_account.getText().toString();
                String password = et_password.getText().toString();
                // 若賬號是 wonderful 且密碼是 123456,就認為登錄成功
                if (account.equals("wonderful") && password.equals("123456")){
                    // 檢查復選框是否被勾選
                    if (cb_remember_pass.isChecked()){
                        // 保存數據到SharePreference文件中
                        PrefUtils.setBoolean(LoginActivity.this,"remember_pass",true);
                        PrefUtils.setString(LoginActivity.this,"account",account);
                        PrefUtils.setString(LoginActivity.this,"password",password);
                    }else {
                        // 清除SharePreference文件中的數據
                        PrefUtils.clear(LoginActivity.this);
                    }
                    // 登錄成功跳轉到主界面
                    IntentUtils.myIntent(LoginActivity.this,ForceOfflineActivity.class);
                    finish();
                }else {
                    ToastUtils.showShort("賬號或密碼無效!");
                }
            }
        });

    }

    @Override
    protected int initLayoutId() {
        return R.layout.activity_login;
    }
}

具體代碼就不解釋了,看注釋。

現在運行下程序,輸入賬號和密碼并選中記住密碼復選框后,點擊登錄,就會跳轉到 ForceOfflineActivity。接著在 ForceOfflineActivity 中發出一條強制下線廣播會讓程序重新回到登錄界面, 此時你會發現,賬號密碼都已經自動填充到界面上了,如下:

記住密碼功能效果

這樣我們就使用 SharedPreferences 技術將記住密碼功能成功實現了。當然這只是個簡單的示例,實際項目中將密碼以明文的形式存儲在 SharedPreferences 文件中是不安全的,還需配合加密算法進行保護。

關于 SharedPreferences 的學習就到這,接下來會在下一篇文章中學習 Android 中的數據庫技術。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容