1. 程式人生 > >安卓開發筆記(五)——資料儲存SharedPreference以及Android中常見的檔案操作方法

安卓開發筆記(五)——資料儲存SharedPreference以及Android中常見的檔案操作方法

中山大學資料科學與計算機學院本科生實驗報告

(2018年秋季學期)


一、實驗題目

個人專案3

資料儲存(一)應用開發


二、實現內容

第九周任務


實驗目的

  1. 學習SharedPreference的基本使用。
  2. 學習Android中常見的檔案操作方法。
  3. 複習Android介面程式設計。

實驗內容

要求

  • Figure 1:首次進入,呈現建立密碼介面。
    preview
  • Figure 2:若密碼不匹配,彈出Toast提示。
    preview
  • Figure 3:若密碼為空,彈出Toast提示。
    preview
  • Figure 4:退出後第二次進入呈現輸入密碼介面。
    preview
  • Figure 5:若密碼不正確,彈出Toast提示。
    preview
  • Figure 6:檔案載入失敗,彈出Toast提示。
    preview
  • Figure 7:成功匯入檔案,彈出Toast提示。
    preview
  • Figure 8:成功儲存檔案,彈出Toast提示。
    preview

  1. 如Figure 1至Figure 8所示,本次實驗演示應用包含兩個Activity。
  2. 首先是密碼輸入Activity:
    • 若應用首次啟動,則介面呈現出兩個輸入框,分別為新密碼輸入框和確認密碼輸入框。
    • 輸入框下方有兩個按鈕:
      • OK按鈕點選後:
        • 若New Password為空,則發出Toast提示。見Figure 3。
        • 若New Password與Confirm Password不匹配,則發出Toast提示,見Figure 2。
        • 若兩密碼匹配,則儲存此密碼,並進入檔案編輯Activity。
      • CLEAR按鈕點選後:清楚兩輸入框的內容。
    • 完成建立密碼後,退出應用再進入應用,則只呈現一個密碼輸入框,見Figure 4。
      • 點選OK按鈕後,若輸入的密碼與之前的密碼不匹配,則彈出Toast提示,見Figure 5。
      • 點選CLEAR按鈕後,清除密碼輸入框的內容。
    • 出於演示和學習的目的,本次實驗我們使用SharedPreferences來儲存密碼。但實際應用中不會使用這種方式來儲存敏感資訊,而是採用更安全的機制。見
      這裡
      這裡
  3. 檔案編輯Activity:
    • 介面底部有三個按鈕,高度一致,頂對齊,按鈕水平均勻分佈,三個按鈕上方除ActionBar和StatusBar之外的全部空間由一個EditText佔據(保留margin)。EditText內的文字豎直方向置頂,左對齊。
    • 在編輯區域輸入任意內容,點選SAVE按鈕後能儲存到指定檔案(檔名隨意)。成功儲存後,彈出Toast提示,見Figure 8。
    • 點選CLEAR按鈕,能清空編輯區域的內容。
    • 點選LOAD按鈕,能夠從同一檔案匯入內容,並顯示到編輯框中。若成功匯入,則彈出Toast提示。見Figure 7.若讀取檔案過程中出現異常(如檔案不存在),則彈出Toast提示。見Figure 6.
  4. 特殊要求:進入檔案編輯Activity後,若點選返回按鈕,則直接返回Home介面,不再返回密碼輸入Activity。

三、實驗結果

(1)實驗截圖

首次進入,顯示新建密碼以及確認密碼介面

1

密碼不匹配,彈出Toast提示。
2

若密碼為空,彈出Toast提示。

3

退出後第二次進入呈現輸入密碼介面。
4

若密碼不正確,彈出Toast提示。

檔案載入失敗,彈出Toast提示。
6

成功匯入檔案,彈出Toast提示。
7

成功儲存檔案,彈出Toast提示。
8

(2)實驗步驟以及關鍵程式碼

1.實現登陸的UI介面

這裡還是使用ConstrainLayout來進行佈局,兩個輸入框EditText,由於是輸入密碼,不能以明文的形式顯示。故需要在inputType設定引數為textPassword.而提示資訊就寫在hint屬性中,在未輸入時,顯示提示New password等。

至於button方面,簡單的調整間距,設定id即可。

<EditText
        android:id="@+id/new_password"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:inputType="textPassword"
        android:hint="New Password"
        app:layout_constraintBottom_toTopOf="@id/confirm_password"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginRight="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginBottom="10dp"
        />

    <EditText
        android:id="@+id/confirm_password"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:inputType="textPassword"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:hint="Confirm Password"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />

2.實現SharedPreference的儲存與獲取

boolean變數is_new是用於確認是否第一次進入app。

而我通過getSharedPreferences的getString函式拿到對應的password,如果是第一次進入app,那麼裡面應該沒有這個屬性,故會賦值成defValue。否則是密碼的字串。

所以根據這一點,可以調整介面中EditText的可見情況,決定哪些可以顯示,哪些需要隱藏。

 	   sharedPreferences = getSharedPreferences ( PREFERENCE_NAME, MODE );
        String password = sharedPreferences.getString("password","defValue");
        if(password.equals("defValue")){
            // do nothing
        }
        else{
            new_password = findViewById(R.id.new_password);
            new_password.setVisibility(View.GONE);
            confirm_password = findViewById(R.id.confirm_password);
            confirm_password.setHint("Password");
            is_new = false;
        }

3.實現按鈕監聽事件的繫結

對於清除按鈕的事件,十分簡單,只需要將兩個EditText利用setText為空字串即可

but_clear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                confirm_password.setText("");
                new_password.setText("");
            }
        });

而對於ok按鈕,需要判斷是第一次登入app還是其他,這裡就利用上面所設定的is_new變數。

當是第一次進入該app,需要輸入新密碼以及確認密碼。這裡首先判斷這兩個密碼是否為空,然後再判斷密碼是否匹配,為空或者不匹配需要彈出Toast。

當密碼匹配時,將新密碼放入SharedPreference中。這裡用到的是Editor,通過edit(),putString()以及commit()這三個函式來進入寫入。然後startActivity來跳轉到檔案編輯的頁面

這裡要注意,在AndroidManifest中註冊新的活動,否則跳轉會失敗

but_ok.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String str_confirm_password = confirm_password.getText().toString();
                String str_new_password = new_password.getText().toString();
                if(is_new){
                    //密碼為空
                    if(str_confirm_password.isEmpty() || str_new_password.isEmpty()){
                        Toast.makeText(MainActivity.this, "Password cannot be empty", Toast.LENGTH_SHORT).show();
                    }
                    else{
                        //兩次密碼匹配
                        if(str_confirm_password.equals(str_new_password)){
                            SharedPreferences.Editor editor = sharedPreferences.edit();
                            editor.putString("password", confirm_password.getText().toString());
                            editor.commit();
                            //跳轉
                            Intent intent = new Intent();
                            intent.setClass(MainActivity.this,FileEditorActivity.class);
                            startActivity(intent);
                        }
                        //兩次密碼不匹配
                        else{
                            //彈出 Toast
                            Toast.makeText(MainActivity.this, "Password Mismatch", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
                else{
                    if(sharedPreferences.getString("password","defValue").equals(str_confirm_password)){
                        //跳轉
                        Intent intent = new Intent();
                        intent.setClass(MainActivity.this,FileEditorActivity.class);
                        startActivity(intent);
                    }
                    else{
                        //彈出 Toast
                        Toast.makeText(MainActivity.this, "Password Invalid", Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });

4.實現檔案編輯的UI介面

對於檔案編輯UI介面的設計,包括三個button,以及一個EditText。這裡使用的是線性佈局 orientation=“vertical”,然後利用layout_weight引數來進行調整頁面的佔用頁面比例。

	<EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_weight="1"
        android:gravity="top"/>

而對於三個按鈕也放置在一個線性佈局中,不過該佈局是水平的,調整邊距即可。

5.實現按鈕儲存檔案功能

這裡需要繫結三個按鈕的功能,清除按鈕與上面登陸的清除按鈕類似,不再複述。

對於檔案的儲存按鈕以及載入按鈕都必須採用try-catch結構,因為檔案讀取不到的時候會出現錯誤,這樣做可以避免應用程式的崩潰。

對於儲存事件,採用的是FileOutputStream來進行輸入,然後利用write()函式寫入,不過要注意將字串轉化為byte陣列,才能write。

	but_save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try (FileOutputStream fileOutputStream = openFileOutput(FILE_NAME, MODE_PRIVATE)) {
                    String str = editText.getText().toString();
                    fileOutputStream.write(str.getBytes());
                    //彈出 Toast
                    Toast.makeText(FileEditorActivity.this, "Save successfully", Toast.LENGTH_SHORT).show();
                    Log.i("TAG", "Successfully saved file.");
                } catch (IOException ex) {
                    Log.e("TAG", "Fail to save file.");
                }
            }
        });

對於載入事件,要使用fileInputStream,以及利用read()函式來讀入到byte陣列中。當讀取成功或者失敗的時候都要彈出相應的toast。

 	but_load.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try (FileInputStream fileInputStream = openFileInput(FILE_NAME)) {
                    byte[] contents = new byte[fileInputStream.available()];
                    fileInputStream.read(contents);
                    String str = new String(contents,"UTF-8");
                    editText.setText(str);
                    //彈出 Toast
                    Toast.makeText(FileEditorActivity.this, "Load successfully", Toast.LENGTH_SHORT).show();
                } catch (IOException ex) {
                    //彈出 Toast
                    Toast.makeText(FileEditorActivity.this, "Fail to read file.", Toast.LENGTH_SHORT).show();
                    Log.e("TAG", "Fail to read file.");
                }
            }
        });

(3)實驗遇到的困難以及解決思路

1.關於檔案儲存與讀出,byte陣列與string類的轉化

一開始在讀取檔案的時候,沒有注意讀取出來的是byte陣列,直接將其賦值給EditText導致出現亂碼的情況。檢視函式的返回值才得知需要將byte陣列轉化成string再setText

這裡我使用的是string的建構函式,將byte陣列作為引數構造字串,第二個引數為charsetName.

//contents為byte陣列
String str = new String(contents,"UTF-8");

而將string轉化為byte陣列只需要利用getBytes()函式即可

String str = editText.getText().toString();
fileOutputStream.write(str.getBytes());

2.如何按返回鍵退出整個應用

本次實驗要求在檔案編輯頁面按返回鍵要退出整個應用,不能返回第一個頁面。

我先測試不新增任何的東西的情況下,發現按下返回鍵是finish掉了當前的活動,回到了前面的第一個頁面。

所以,我必須重構onKeyDown函式來,改變返回鍵的功能,這裡我利用了launchMode="singleTask"的性質,將第一個主頁面設定成單個task,那麼返回到第一個頁面的時候會呼叫onNewIntent函式,我只需要在這個函式呼叫finish即可,結束掉第一個頁面。

點選返回鍵,第二個頁面會跳轉到第一個頁面,而跳轉的時候第一個頁面馬上執行onNewIntent函式結束頁面,即結束了整個應用。

 <activity android:name=".MainActivity" android:launchMode="singleTask">
@Override
    protected void onNewIntent(Intent intent) {
        finish();
    }
@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            Intent intent = new Intent(FileEditorActivity.this,MainActivity.class);
            startActivity(intent);
        }
        return super.onKeyDown(keyCode, event);
    }

3.SharedPreference的有效生命週期

當我在手機模擬器實驗SharedPreference的時候,在第一次進入應用設定密碼後,不斷重灌應用也不會再出現新密碼的頁面情況,一開始我還以為是我的頁面邏輯出錯,經過log除錯後才發現沒有問題。那麼就是SharePreference沒有被重置過。

於是我查詢了SharePreference的相關資訊,根據網上資料,只有當解除安裝應用的時候SharePreference才會被順帶刪除掉。而像我這樣在虛擬機器重灌是不會改變SharePreference的。

所以在解除安裝後再重灌發現,這個問題順利解決。


四、實驗思考及感想

實驗思考

關於Internal Storage和External Storage的區別,以及它們的適用場景。

Internal Storage
  • 預設情況下,儲存在 Internal Storage 的⽂件只有應⽤程式可⻅,其他應⽤,以及⽤⼾本⾝是⽆法訪問這些⽂件的。
  • Internal Storage 把資料儲存在裝置內部儲存器上,儲存在/data/data/<package name>/files目錄下。
  • 解除安裝應用程式後,內部儲存器的/data/data/<package name>目錄及其下子目錄和檔案一同被刪除。
External Storage
  • Android ⽀持使⽤ Java 的⽂件 API 來讀寫⽂件,但是關鍵的點在於要有⼀個合適的路徑。如果你要儲存⼀些公開的,體積較⼤的⽂件(如媒體⽂件),External Storage 就是⼀個⽐較合適的地⽅。
  • 儲存在這裡的檔案可能被其他程式訪問。
  • 解除安裝app時,系統僅僅會刪除external根目錄(getExternalFilesDir())下的相關檔案。

所以當不想被外部程式訪問,且需要儲存的資料檔案比較小的情況,例如密碼,使用者資訊等等可以儲存在Internal Storage。而需要被外部訪問的資料,而且這些資料檔案較大,佔用較多的記憶體空間,例如視訊相片等等可以儲存在External Storage

感想

這次實驗是關於資料的儲存,一個好的應用離不開對於資料的處理。如何放置這些資料是十分講究,這裡第一部分學習了SharedPreference與內部外部Storage兩方面的內容。我們應該將某些資料放入其中,而不是簡單地堆放在java檔案中。而這次實驗,我還了解到了應用程式退出的幾種方法,更加深入地認識了活動的生命週期。關於線性佈局佔頁面比也有了更好的認識,操作UI設計起來也更加熟練。