安卓開發筆記(五)——資料儲存SharedPreference以及Android中常見的檔案操作方法
中山大學資料科學與計算機學院本科生實驗報告
(2018年秋季學期)
一、實驗題目
個人專案3
資料儲存(一)應用開發
二、實現內容
第九周任務
實驗目的
- 學習SharedPreference的基本使用。
- 學習Android中常見的檔案操作方法。
- 複習Android介面程式設計。
實驗內容
要求
- Figure 1:首次進入,呈現建立密碼介面。
- Figure 2:若密碼不匹配,彈出Toast提示。
- Figure 3:若密碼為空,彈出Toast提示。
- Figure 4:退出後第二次進入呈現輸入密碼介面。
- Figure 5:若密碼不正確,彈出Toast提示。
- Figure 6:檔案載入失敗,彈出Toast提示。
- Figure 7:成功匯入檔案,彈出Toast提示。
- Figure 8:成功儲存檔案,彈出Toast提示。
- 如Figure 1至Figure 8所示,本次實驗演示應用包含兩個Activity。
- 首先是密碼輸入Activity:
- 若應用首次啟動,則介面呈現出兩個輸入框,分別為新密碼輸入框和確認密碼輸入框。
- 輸入框下方有兩個按鈕:
- OK按鈕點選後:
- 若New Password為空,則發出Toast提示。見Figure 3。
- 若New Password與Confirm Password不匹配,則發出Toast提示,見Figure 2。
- 若兩密碼匹配,則儲存此密碼,並進入檔案編輯Activity。
- CLEAR按鈕點選後:清楚兩輸入框的內容。
- OK按鈕點選後:
- 完成建立密碼後,退出應用再進入應用,則只呈現一個密碼輸入框,見Figure 4。
- 點選OK按鈕後,若輸入的密碼與之前的密碼不匹配,則彈出Toast提示,見Figure 5。
- 點選CLEAR按鈕後,清除密碼輸入框的內容。
- 出於演示和學習的目的,本次實驗我們使用SharedPreferences來儲存密碼。但實際應用中不會使用這種方式來儲存敏感資訊,而是採用更安全的機制。見
- 檔案編輯Activity:
- 介面底部有三個按鈕,高度一致,頂對齊,按鈕水平均勻分佈,三個按鈕上方除ActionBar和StatusBar之外的全部空間由一個EditText佔據(保留margin)。EditText內的文字豎直方向置頂,左對齊。
- 在編輯區域輸入任意內容,點選SAVE按鈕後能儲存到指定檔案(檔名隨意)。成功儲存後,彈出Toast提示,見Figure 8。
- 點選CLEAR按鈕,能清空編輯區域的內容。
- 點選LOAD按鈕,能夠從同一檔案匯入內容,並顯示到編輯框中。若成功匯入,則彈出Toast提示。見Figure 7.若讀取檔案過程中出現異常(如檔案不存在),則彈出Toast提示。見Figure 6.
- 特殊要求:進入檔案編輯Activity後,若點選返回按鈕,則直接返回Home介面,不再返回密碼輸入Activity。
三、實驗結果
(1)實驗截圖
首次進入,顯示新建密碼以及確認密碼介面
密碼不匹配,彈出Toast提示。
若密碼為空,彈出Toast提示。
退出後第二次進入呈現輸入密碼介面。
若密碼不正確,彈出Toast提示。
檔案載入失敗,彈出Toast提示。
成功匯入檔案,彈出Toast提示。
成功儲存檔案,彈出Toast提示。
(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設計起來也更加熟練。