第10章 儲存偏好設定、檔案與資料庫 10-1 存取偏好設定 10-2 檔案存取 10-3 關聯式資料庫與SQLite 10-5 Storage Access Framework
10-1 存取偏好設定 10-1-1 存取活動的偏好設定 10-1-2 存取應用程式的偏好設定
10-1-1 存取活動的偏好設定 – 說明 在Android提供SharedPreferences物件儲存應用程式資料,主要是指一些偏好設定的字型尺寸、使用者帳號、色彩或遊戲分數,實際上,就是使用XML格式的偏好設定檔來儲存這些資料。 SharedPreferences類別屬於android.content套件,可以儲存少量且簡單的應用程式資料,這些資料是儲存成類似Bundle物件的鍵值與對應值,支援Boolean、Float、Integer、Long和String型態的資料。
10-1-1 存取活動的偏好設定 – 取得SharedPreferences物件 在活動存取偏好設定需要取得SharedPreferences物件,通常我們是在onCreate()覆寫方法取得此物件,如下所示: private SharedPreferences prefs; …. prefs = getPreferences(MODE_PRIVATE); 程式碼使用Activity類別的getPreferences()方法取得SharedPreferences物件,參數是操作模式常數,MODE_PRIVATE只允許建立偏好設定檔的活動存取,只允許同一個活動來存取。
10-1-1 存取活動的偏好設定 – 讀取偏好設定資料 讀取偏好設定建議是在onResume()覆寫方法,在取得SharedPreferences物件prefs後,使用getString()、getFloat()或getInt()等方法取得儲存字串、浮點數和整數等值,如下所示: String amount = prefs.getString(PREF_AMOUNT, "10000"); txtAmount.setText(amount); float rate = prefs.getFloat(PREF_RATE, 28.9F); txtRate.setText(String.valueOf(rate));
10-1-1 存取活動的偏好設定 – 儲存偏好設定資料1 儲存偏好設定建議是在onPause()覆寫方法,我們是使用SharedPreferences.Editor物件來編輯存入的資料,如下所示: SharedPreferences.Editor prefEdit = prefs.edit(); 然後使用putString()、putInt()和putFloat()等方法存入字串、整數和浮點數等資料,如下所示: prefEdit.putString(PREF_AMOUNT, txtAmount.getText().toString()); float rate; rate = (float) Double.parseDouble(txtRate.getText().toString()); prefEdit.putFloat(PREF_RATE, rate);
10-1-1 存取活動的偏好設定 – 儲存偏好設定資料2 最後記得使用apply()或commit()方法將資料寫入偏好設定檔,如下所示: prefEdit.apply(); 上述apply()方法是API 9新增的方法,它會馬上更改記憶體中的資料,然後以非同步方式寫入XML檔案,所以速度比較快且沒有傳回值。
10-1-2 存取應用程式的偏好設定 – 說明 如果偏好設定檔是Android應用程式中各活動之間的分享資料,也就是說在應用程式中的每一個活動都可以存取,屬於應用程式層級(Application-level)的偏好設定。
10-1-2 存取應用程式的偏好設定 – 取得物件 我們需要使用繼承自Context類別的getSharedPreferences()方法來取得SharedPreferences物件,如下所示: private SharedPreferences prefs; … prefs = getSharedPreferences("MyPref", MODE_PRIVATE); 第1個參數是偏好設定檔名稱,第2個參數值MODE_PRIVATE只允許建立偏好設定檔的應用程式存取。在各活動就可以使用SharedPreferences物件prefs來存取偏好設定值。
10-1-3 存取偏好設定頁面的設定值 – 載入偏好設定頁面的預設值 10-1-3 存取偏好設定頁面的設定值 – 載入偏好設定頁面的預設值 一般來說,因為偏好設定頁面不會是應用程式的主活動,所以,在主活動的onCreate()覆寫方法需要載入偏好設定頁面的預設值,如下所示: PreferenceManager.setDefaultValues( this,R.xml.preferences,false); PreferenceManager類別的setDefaultValues()類別方法有3個參數,第1個參數是Context物件,第2個參數是偏好設定頁面的資源索引,最後1個參數值false,表示不重新讀取預設值;true是再重新讀取預設值。
10-1-3 存取偏好設定頁面的設定值 – 取出偏好設定值1 10-1-3 存取偏好設定頁面的設定值 – 取出偏好設定值1 在載入偏好設定頁面的預設值後,因為偏好設定值是使用預設的SharedPreferences物件來存取,我們需要使用PreferenceManager類別的方法來取得此物件,如下所示: SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
10-1-3 存取偏好設定頁面的設定值 – 取出偏好設定值2 10-1-3 存取偏好設定頁面的設定值 – 取出偏好設定值2 接著可以一一取出偏好設定值,方法第1個參數的鍵值是android:key屬性值,如下所示: String username = settings.getString("username", ""); String password = settings.getString("password", ""); boolean isMorePrefs = settings.getBoolean("more_pref", false); String color = settings.getString("color_pref", "");
10-2 檔案存取 10-2-1 存取內部儲存裝置的檔案 10-2-2 讀取原料資源的檔案 10-2-3 存取外部SD卡的檔案
10-2 檔案存取 Android應用程式可以使用java.io套件的類別來寫入與讀取檔案,在Context類別提供openFileInput()和openFileOutput()方法來分別讀取與寫入檔案。Android應用程式可以在3個地方存取檔案資料,如下所示: 內部儲存裝置:在保護目錄儲存空間讀寫檔案資料。 專案的原料資源:在APK套件保護的唯讀空間,即Android專案的原料資源(Raw Resources),唯讀檔案是位在「\res\raw\」目錄。 外部儲存裝置:在外部儲存空間讀寫檔案,例如:SD卡,應用程式需要WRITE_EXTERNAL_STORAGE權限和API層級4以上,如果使用Android 6.0以上版本,我們需要在執行期取得權限才能在SD卡讀寫檔案。
10-2-1 存取內部儲存裝置的檔案– 說明 在Android應用程式如果需要將資料儲存至檔案,第一個選擇就是行動裝置的內部儲存裝置。
10-2-1 存取內部儲存裝置的檔案– 開啟檔案輸出串流 Android程式碼是使用java.io套件FileOutputStream類別將資料寫入檔案,如下所示: FileOutputStream out = openFileOutput( fname,MODE_PRIVATE); 程式碼使用繼承自Context類別的openFileOutput()方法來開啟FileOutputStream檔案輸出串流,第1個參數是檔案名稱字串,第2個參數是檔案操作模式常數,預設值MODE_PRIVATE,即值0 。
10-2-1 存取內部儲存裝置的檔案– 寫入檔案 我們可以使用OutputStreamWriter類別,將字元串流轉換成位元組串流,如下所示: OutputStreamWriter sw = new OutputStreamWriter(out); 然後就可以將資料寫入檔案,如下所示: sw.write(str); 最後呼叫flush()方法輸出串流資料,和close()方法關閉串流,如下所示: sw.flush(); sw.close();
10-2-1 存取內部儲存裝置的檔案– 開啟檔案輸入串流 讀取檔案是使用FileInputStream搭配InputStreamReader物件,如下所示: FileInputStream in = openFileInput(fname); InputStreamReader sr = new InputStreamReader(in); 程式碼使用繼承自Context類別的openFileInput()方法開啟FileInputStream檔案輸入串流,參數是檔案名稱字串,然後使用此物件建立InputStreamReader物件。
10-2-1 存取內部儲存裝置的檔案– 讀取檔案 char[] buffer = new char[READ_BLOCK_SIZE]; 我們需要自行建立讀取緩衝區的char[]陣列,然後使用while迴圈來讀取檔案內容,如下所示: char[] buffer = new char[READ_BLOCK_SIZE]; while ((count = sr.read(buffer)) > 0) { String s = String.copyValueOf(buffer,0, count); str += s; buffer = new char[READ_BLOCK_SIZE]; } 最後使用close()方法關閉串流,如下所示: sr.close();
10-2-2 讀取原料資源的檔案 - 原料資源 原料資源(Raw Resources)是位在Android專案的「\res\raw\」目錄,如圖所示:
10-2-2 讀取原料資源的檔案 – 開啟檔案的讀取串流 10-2-2 讀取原料資源的檔案 – 開啟檔案的讀取串流 在Android程式是使用Resources物件的openRawResource()方法開啟檔案的讀取串流,如下所示: InputStream in = this.getResources(). openRawResource(R.raw.note); InputStreamReader sr = new InputStreamReader(in); 程式碼呼叫openRawResource()方法開啟InputStream串流,參數是檔案的資源索引,然後使用建構子建立InputStreamReader讀取串流。
10-2-2 讀取原料資源的檔案 – 讀取文字檔內容 使用BufferedReader緩衝區串流,如下所示: 10-2-2 讀取原料資源的檔案 – 讀取文字檔內容 使用BufferedReader緩衝區串流,如下所示: BufferedReader br = new BufferedReader(sr); 建構子參數是之前InputStreamReader讀取串流sr,在建立BufferedReader緩衝區串流後,就可以使用串流物件的readLine()方法來一行一行讀取文字檔內容,直到值為null為止,如下所示: String s = null, str = ""; while ((s = br.readLine()) != null) str += s + "\n";
10-2-2 讀取原料資源的檔案 – 關閉串流 最後一樣需要使用close()方法來關閉串流,如下所示: br.close(); 10-2-2 讀取原料資源的檔案 – 關閉串流 最後一樣需要使用close()方法來關閉串流,如下所示: br.close(); sr.close();
10-2-3 存取外部SD卡的檔案– 說明 除了將檔案儲存至內部儲存裝置外,對於大量資料或需要分享給其他使用者的資料,我們可以將檔案儲存在外部儲存裝置,最常使用的是SD卡。 Android Studio專案Ch10_2_3和第10-2-1節相同,只是將存取檔案儲存在外部儲存裝置的SD卡,請注意!Android 6.0之上版本,在第1次執行時需取得使用者授權才能存取SD卡,如下圖所示:
10-2-3 存取外部SD卡的檔案– 檢查是否有外部儲存裝置 if (!Environment.getExternalStorageState(). equals(Environment.MEDIA_MOUNTED)) { Toast.makeText(this, "沒有外部儲存裝置...", Toast.LENGTH_SHORT).show(); finish(); return; }
10-2-3 存取外部SD卡的檔案– 在SD卡建立目錄和存取檔案內容 在確認有掛載SD卡後,就可以在外部儲存裝置建立目錄和檔案,try/catch例外處理如下所示: try { File sd = Environment.getExternalStorageDirectory(); File dir = new File(sd.getAbsolutePath() + fpath); if (!dir.exists()) dir.mkdir(); file = new File(dir, fname); } catch (Exception ex) { ex.printStackTrace();
10-2-3 存取外部SD卡的檔案– 在SD卡建立目錄和存取檔案內容 在建立目錄後,就可以在此目錄下建立檔名為fname字串的檔案,即File物件file,這是我們準備寫入和讀取資料的檔案。接著使用File物件file來建立FileOutputStream和FileInputStream物件,以便寫入和讀取檔案內容,如下所示: FileOutputStream out = new FileOutputStream(file); FileInputStream in = new FileInputStream(file);
10-3 關聯式資料庫與SQLite 10-3-1 認識關聯式資料庫 10-3-2 SQLite資料庫引擎 10-3-3 SQL語言的基礎
10-3 關聯式資料庫與SQLite 資料庫(Database)是一種資料儲存單位,一些經過組織的資料集合,眾多出勤管理系統、倉庫管理系統、進銷存系統或小至錄影帶店管理系統,這些應用程式都屬於不同應用的資料庫系統。 資料庫系統是由資料庫和資料庫管理系統所組成,資料庫管理系統是一套管理資料庫的應用程式。
10-3-1 認識關聯式資料庫 – 說明 關聯式資料庫系統(Relational Database System)是目前資料庫系統的主流,巿面上大部分資料庫管理系統都屬於關聯式資料庫管理系統(Relational Database Management System),例如:Access、MySQL、SQL Server和Oracle等。 關聯式資料庫(Relational Database)是由一個或多個資料表所組成,在多個資料表間使用欄位的資料值來建立連接,以便實作資料表之間的關聯性。
10-3-1 認識關聯式資料庫 – 圖例 在關聯式資料庫是使用二維表格的資料表來儲存記錄資料,在各資料表間使用欄位值建立關聯性,透過關聯性來存取其他資料表的資料。例如:使用【學號】欄位值建立兩個資料表之間的關聯性,如右圖所示:
10-3-1 認識關聯式資料庫 – 組成 關聯式資料庫的資料是儲存在資料庫的「資料表」(Tables),每一個資料表使用「欄位」(Fields)分類成多個群組,每一個群組是一筆「記錄」(Records),例如:通訊錄資料表的記錄,如下表所示:
10-3-2 SQLite資料庫引擎 SQLite是目前世界上最廣泛使用的免費資料庫引擎,一套實作大部分SQL 92標準的函數庫,它不需要管理、不需要伺服器、也不需要安裝設定,不但體積輕巧,而且還是一套支援交易(Transaction)的SQL資料庫引擎,其官方網址為:http://www.sqlite.org/。 SQLite是Android作業系統內建的資料庫系統,其主要特點如下所示: SQLite資料庫只是一個檔案,可以直接使用檔案權限來管理資料庫,而不用自行處理資料庫的使用者權限管理,所以沒有提供SQL語言的DCL存取控制。 單一檔案的SQLite資料庫,可以讓Android應用程式很容易進行安裝,而且不用特別進行資料庫系統的設定與管理。 SQLite不需要啟動,換句話說,不會浪費行動裝置的記憶體資源。
10-3-3 SQL語言的基礎 – 說明 「SQL」(Structured Query Language)為「ANSI」(American National Standards Institute)標準的資料庫語言,可以存取和更新資料庫的記錄資料。SQLite支援的SQL指令主要分成兩大類,如下所示: 資料定義語言(Data Definition Language,DDL):建立資料表和定義資料表欄位。 資料操作語言(Data Manipulation Language,DML):資料表記錄的查詢、插入、刪除和更新。
10-3-3 SQL語言的基礎 – CREATE TABLE建立資料表 CREATE TABLE titles ( _id integer primary key autoincrement, title text no null, price real no null )
10-3-3 SQL語言的基礎 – SELECT查詢記錄(語法) SELECT 欄位1, 欄位2 FROM 資料表 WHERE conditions ORDER BY 欄位清單 SELECT指令的欄位1~2為記錄的欄位,conditions為查詢條件,使用口語來說就是「從資料表取回符合WHERE子句條件的記錄,顯示欄位1和2,並且以ORDER BY子句的欄位來排序」。
10-3-3 SQL語言的基礎 – SELECT查詢記錄("*"符號) SELECT * FROM titles
10-3-3 SQL語言的基礎 – SELECT查詢記錄(WHERE子句-1) SQL查詢如果是單一條件,在WHERE子句條件的基本規則和範例,如下所示: 文字欄位需要使用單引號括起,例如:書名為Java時的SQL指令字串,如下所示: SELECT * FROM titles WHERE title='Java' 數字欄位不需要使用單引號括起,例如:價格為600元的SQL指令,如下所示: SELECT * FROM titles WHERE price=600
10-3-3 SQL語言的基礎 – SELECT查詢記錄(WHERE子句-2) 文字和備註欄位可以使用【LIKE】包含運算子,只需包含此字串即符合條件,再配合"%"或"_"萬用字元,可以代表任何字串或單一字元,只需包含的子字串就符合條件。例如:查詢擁有Java子字串圖書資料的SQL指令,如下所示: SELECT * FROM titles WHERE titles LIKE '%Java%' 數字或日期/時間欄位可以使用<>、>、<、>=和<=不等於、大於、小於、大於等於和小於等於等運算子建立多樣化的查詢條件。
10-3-3 SQL語言的基礎 – INSERT新增記錄 SQL新增記錄INSERT指令可以新增一筆記錄例如:在titles資料表新增一筆記錄的SQL指令,如下所示: INSERT INTO titles (title, price) VALUES ('Access', 600) SQL指令的括號中是欄位清單(不用全部,但需包含所有not null欄位),字串欄位值使用單引號括起,數字沒有。
10-3-3 SQL語言的基礎 – UPDATE更新記錄 SQL更新記錄UPDATE指令是將資料表內符合條件的記錄,更新其欄位內容,例如:在titles資料表更改書價的SQL指令,如下所示: UPDATE titles SET price =650 WHERE title='Access' SQL指令的WHERE條件為書名title欄位,然後使用SET子句更新price欄位資料。
10-3-3 SQL語言的基礎 – DELETE刪除記錄 SQL刪除記錄DELETE指令是將資料表內符合條件的記錄刪除掉。例如:在titles資料表刪除記錄的SQL指令,如下所示: DELETE FROM titles WHERE title='Access' SQL指令的WHERE條件為書名title欄位,也就是將符合書名條件的圖書記錄刪除掉。
10-4 SQLite資料庫的使用 10-4-1 使用SQLiteOpenHelper類別建立資料庫與資料表 10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 10-4-3 使用SQL指令存取資料庫
10-4-1 使用SQLiteOpenHelper類別建立資料庫與資料表 – 說明 SQLiteOpenHelper類別是一個幫助我們存取SQLite資料庫的幫助類別(Helper Class),Android應用程式建立SQLite資料庫就是繼承SQLiteOpenHelper類別來覆寫相關方法,事實上,其主要目的是讓我們可以在SQLite資料庫新增資料表(因為資料庫的建立已經在父類別實作)。
10-4-1 使用SQLiteOpenHelper類別建立資料庫與資料表 - 繼承SQLiteOpenHelper類別1 在MyDBHelper(自行命名)子類別需要新增建構子,覆寫onCreate()和onUpgrade()方法,首先是建構子,如下所示: public class MyDBHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "MyBooks"; private static final int DATABASE_VERSION = 1; public MyDBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); }
10-4-1 使用SQLiteOpenHelper類別建立資料庫與資料表 - 繼承SQLiteOpenHelper類別2 我們需要覆寫onCreate()和onUpgrade()方法,如下所示: @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE titles (_id integer " + "primary key autoincrement, " + "title text no null, price real no null)"); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS titles"); onCreate(db);
10-4-1 使用SQLiteOpenHelper類別建立資料庫與資料表 - 相關方法 說明 getReadableDatabase() 建立或開啟(如果存在)一個唯讀資料庫,成功開啟傳回SQLiteDatabase物件 getWritableDatabase() 建立或開啟(如果存在)一個讀寫資料庫,成功開啟傳回SQLiteDatabase物件 close() 關閉開啟的資料庫
10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 – 說明
10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 - 開啟可讀寫的資料庫 因為需要在資料庫新增、更新和刪除記錄,所以開啟可讀寫資料庫,通常是在Activity類別的onCreate()方法開啟資料庫,如下所示: dbHelper = new MyDBHelper(this); db = dbHelper.getWritableDatabase(); 程式碼在建立MyDBHelper物件後,呼叫getWritableDatabase()方法來取得SQLiteDatabase物件的資料庫。
10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 - 關閉資料庫 關閉資料庫通常是在onStop()方法,只需呼叫SQLiteDatabase類別的close()方法,就可以關閉資料庫,如下所示: db.close();
10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 - 新增記錄 SQLiteDatabase物件可以使用insert()方法來新增記錄,首先,我們需要使用ContentValues類別建立欄位值,使用put()方法加入欄位,如下所示: long id; ContentValues cv = new ContentValues(); cv.put("title", txtTitle.getText().toString()); double price = Double.parseDouble(txtPrice.getText().toString()); cv.put("price", price); 在建立後,呼叫insert()方法來新增記錄,如下所示: id = db.insert(DATABASE_TABLE, null, cv);
10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 - 更新記錄 SQLiteDatabase物件可以使用update()方法來更新記錄,同樣需要使用ContentValues類別建立更新的欄位值,如下所示: ContentValues cv = new ContentValues(); double price = Double.parseDouble( txtNewPrice.getText().toString()); cv.put("price", price); count = db.update(DATABASE_TABLE, cv, "title='" + title + "'", null);
10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 - 刪除記錄 SQLiteDatabase物件可以使用delete()方法來刪除記錄,如下所示: count = db.delete(DATABASE_TABLE, "title='" + title + "'", null); 方法的傳回值是影響的記錄數,第1個參數是資料表名稱,第2個就是WHERE子句的刪除條件,如果是參數條件字串,其參數值就是最後一個參數。
10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 – 查詢記錄1 一般來說,如果是取出單筆的記錄資料,我們可以在query()方法加上WHERE子句的條件(不含WHERE本身),如下所示: Cursor c = db.query(DATABASE_TABLE, colNames, "_id = " + rowId , null, null, null,null); 上述程式碼指定第3個參數的條件為_id欄位值等於rowId變數,可以取回指定記錄編號的記錄資料。
10-4-2 使用SQLiteDatabase類別存取資料表的記錄資料 – 查詢記錄2 在取得Cursor物件後,我們就可以移動記錄指標來取得查詢結果的每一筆記錄,如下所示: c.moveToFirst(); for (int i = 0; i < c.getCount(); i++) { str += c.getString( c.getColumnIndex(colNames[0])) + "\t\t"; str += c.getString(1) + "\t\t"; str += c.getString(2) + "\n"; c.moveToNext(); } 程式碼首先呼叫moveToFirst()方法移至第1筆記錄,getCount()方法傳回記錄數,然後使用for迴圈來走訪每一筆記錄,moveToNext()方法可以移至下一筆。
10-4-3 使用SQL指令存取資料庫 – execSQL()方法 在第10-4-2節是使用SQLiteDatabase物件的方法來新增、更新、刪除和查詢記錄資料,事實上,我們也可以如同第10-4-1節建立資料表一般,直接使用execSQL()方法下達SQL指令來存取資料庫,如下所示: db.execSQL("INSERT INTO " + DATABASE_TABLE + " (" + "title, price) VALUES ('" + txtTitle.getText().toString() + "'," + txtPrice.getText().toString() + ")"); 上述程式碼建立SQL指令的INSERT指令來新增記錄,同理,可以建立UPDATE指令來更新記錄;DELETE指令刪除記錄資料。
10-4-3 使用SQL指令存取資料庫 – rawQuery()方法 查詢部分是使用rawQuery()方法下達SELECT指令,如下所示: Cursor c = db.rawQuery("SELECT * FROM " + DATABASE_TABLE, null); colNames = c.getColumnNames(); for (int i = 0; i < colNames.length; i++) str += colNames[i] + "\t\t"; str += "\n"; rawQuery()方法的第1個參數是SELECT指令,第2個參數是當第1個參數是參數查詢時,指定「?」符號取代的參數值,可以傳回Cursor物件,此時可以使用getColumnNames()方法取得欄位名稱陣列,然後使用for迴圈顯示查詢結果。
10-5 Storage Access Framework – 說明 Storage Access Framework(SAF)提供系統內建檔案選擇器,可以幫助我們輕鬆建立讓使用者選擇和開啟文件、圖片或其他檔案的應用程式,SAF是透過Document Providers取得檔案資訊,換句話說,只需廠商提供支援的Document Provider,我們不只可以瀏覽行動裝置的檔案系統,雲端硬碟也一樣可以使用相同介面來瀏覽與開啟檔案。 SAF是使用隱含意圖(Intent)來開啟系統內建的檔案選擇器,可以讓使用者自行建立與選擇開啟的檔案,其說明如下所示: ACTION_OPEN_DOCUMENT:開啟檔案選擇器來開啟檔案。 ACTION_CREATE_DOCUMENT:開啟檔案選擇器來建立檔案。
10-5 Storage Access Framework –建立步驟 步驟一:開啟和執行Android Studio專案 步驟二:建立使用介面的版面配置 步驟三:建立Activity活動類別
10-5 Storage Access Framework – 圖例
End