前言
之前一直在講AndroidUI的內容,但是還沒有完結,之后會慢慢補充。今天講講其他的,關於數據持久化的內容。對於一個應用程序而言,不可避免的要能夠對數據進行存儲,Android程序也不例外。而在Android中,提供了幾種實現數據持久化的方法。后面會分別介紹。
在Android中,可以使用幾種方式實現數據持久化:
- Shared Preferences:共享參數形式,一種以Key-Value的鍵值對形式保存數據的方式,Android內置的,一般應用的配置信息,推薦使用此種方式保存。
- Internal Storage:使用Android設備自帶的內存存儲數據。
- External Storage:使用外部存儲設備存儲數據,一般是指Sdcard。
- SQLite Databases:以SQLite數據庫存儲結構化的數據。
- Network Connection:使用基於網絡的服務獲取數據,可以參見另外一篇博客:Android--Apache HttpClient。
后面幾天會分別介紹以上幾種方式實現的數據持久化,對於SharedPreferences而言,之前寫過一篇博客,但是自己不是很滿意,之后有時間會再重新寫一份關於SharedPreferences的博客,有興趣的朋友可以先去看看,Android--使用SharedPreferences。今天先介紹Internal Storage以及External Storage。
Internal Storage
內部存儲,在Android中,開發者可以直接使用設備的內部存儲器中保存文件,默認情況下,以這種方式保存的和數據是只能被當前程序訪問,在其他程序中是無法訪問到的,而當用戶卸載該程序的時候,這些文件也會隨之被刪除。
使用內部存儲保存數據的方式,基本上也是先獲得一個文件的輸出流,然后以write()的方式把待寫入的信息寫入到這個輸出流中,最后關閉流即可,這些都是Java中IO流的操作。具體步驟如下:
- 使用Context.openFileOutput()方法獲取到一個FileOutputStream對象。
- 把待寫入的內容通過write()方法寫入到FileOutputStream對象中。
- 最后使用close()關閉流。
上面介紹的Context.openFileOutput()方法有兩個重載函數,它們的簽名分別是:
- FileOutputStream openFileOutput(String name):以MODE_PRIVATE的模式打開name文件。
- FileOutputStream openFileOutput(String name,int mode):以mode的模式打開name文件。
上面第二個重載函數中,mode為一個int類型的數據,這個一般使用Context對象中設置好的常量參數,有如下幾個:
- MODE_APPEND:以追加的方式打開一個文件,使用此模式寫入的內容均追加在原本內容的后面。
- MODE_PRIVATE:私有模式(默認),如果文件已經存在會重新創建並替換原文件,如果不存在直接創建。
- MODE_WORLD_READABLE:以只讀的方式打開文件。
- MODE_WORLD_WRITEABLE:以只寫的方式打開文件。
還有幾個方法需要特別注意一下,這幾個方法對於文件關系提供了更好的支持,配合上面介紹的方式,就可以對文件的數據進行常規的CRUD操作(增刪改查),方法如下:
- File getFIlesDir():獲取文件系統的絕對路徑。
- boolean deleteFile(String name):刪除一個指定文件名為name的文件。
- String[] fileList():當前應用內部存儲路徑下的所有文件名。
講了這么多,下面通過一個簡單的Demo來演示一下上面提到的內容。在這個Demo中,指定文件名和內容,既可創建文件,並且可以對其內容進行追加、修改、刪除、查詢等操作。

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="file name:" /> <EditText android:id="@+id/etInternalFilename" android:layout_width="match_parent" android:layout_height="wrap_content" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Content:" /> <EditText android:id="@+id/etInternalContent" android:layout_width="match_parent" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/btnInternalSave" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="save" /> <Button android:id="@+id/btnInternalDelete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="delete" /> <Button android:id="@+id/btnInternalAppend" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="append" /> <Button android:id="@+id/btnInternalQuery" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="query" /> </LinearLayout> <!-- 以一個ListView的形式展示當前程序內部存儲路徑下的所有文件 --> <ListView android:id="@+id/lvInternalData" android:layout_width="match_parent" android:layout_height="fill_parent" > </ListView> </LinearLayout>
內部存儲的操作類,對其實現CRUD操作:

package com.example.internal; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.content.Context; import android.os.Environment; import android.util.Log; public class MyInternalStorage { //需要保存當前調用對象的Context private Context context; public MyInternalStorage(Context context) { this.context = context; } /** * 保存內容到內部存儲器中 * @param filename 文件名 * @param content 內容 */ public void save(String filename, String content) throws IOException { // FileOutputStream fos=context.openFileOutput(filename, // Context.MODE_PRIVATE); File file = new File(context.getFilesDir(), filename); FileOutputStream fos = new FileOutputStream(file); fos.write(content.getBytes()); fos.close(); } /** * 通過文件名獲取內容 * @param filename 文件名 * @return 文件內容 */ public String get(String filename) throws IOException { FileInputStream fis = context.openFileInput(filename); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] data = new byte[1024]; int len = -1; while ((len = fis.read(data)) != -1) { baos.write(data, 0, len); } return new String(baos.toByteArray()); } /** * 以追加的方式在文件的末尾添加內容 * @param filename 文件名 * @param content 追加的內容 */ public void append(String filename, String content) throws IOException { FileOutputStream fos = context.openFileOutput(filename, Context.MODE_APPEND); fos.write(content.getBytes()); fos.close(); } /** * 刪除文件 * @param filename 文件名 * @return 是否成功 */ public boolean delete(String filename) { return context.deleteFile(filename); } /** * 獲取內部存儲路徑下的所有文件名 * @return 文件名數組 */ public String[] queryAllFile() { return context.fileList(); } }
Activity代碼:

package com.internalstorageactivity; import java.io.IOException; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; public class InternalStorageActivity extends Activity { private EditText etFilename, etContent; private Button btnSave, btnQuery, btnDelete, btnAppend; private ListView lvData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); lvData = (ListView) findViewById(R.id.lvInternalData); lvData.setOnItemClickListener(new OnItemClickListener(){ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { System.out.println("1"); ListView lv = (ListView) parent; ArrayAdapter<String> adapter = (ArrayAdapter<String>) lv .getAdapter(); String filename = adapter.getItem(position); etFilename.setText(filename); MyInternalStorage myInternal = new MyInternalStorage( InternalStorageActivity.this); try { String content = myInternal.get(filename); etContent.setText(content); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); etFilename = (EditText) findViewById(R.id.etInternalFilename); etContent = (EditText) findViewById(R.id.etInternalContent); btnSave = (Button) findViewById(R.id.btnInternalSave); btnSave.setOnClickListener(new OnClickListener() { String filename = null; String content = null; public void onClick(View v) { filename = etFilename.getText().toString(); content = etContent.getText().toString(); MyInternalStorage myInternal = new MyInternalStorage( InternalStorageActivity.this); try { myInternal.save(filename, content); Toast.makeText(InternalStorageActivity.this, "保存文件成功", Toast.LENGTH_SHORT).show(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); Toast.makeText(InternalStorageActivity.this, "保存文件失敗", Toast.LENGTH_SHORT).show(); } } }); btnAppend = (Button) findViewById(R.id.btnInternalAppend); btnAppend.setOnClickListener(new OnClickListener() { String filename = null; String content = null; public void onClick(View v) { filename = etFilename.getText().toString(); content = etContent.getText().toString(); MyInternalStorage myInternal = new MyInternalStorage( InternalStorageActivity.this); try { myInternal.append(filename, content); Toast.makeText(InternalStorageActivity.this, "保存內容追加成功", Toast.LENGTH_SHORT).show(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); Toast.makeText(InternalStorageActivity.this, "文件內容追加失敗", Toast.LENGTH_SHORT).show(); } } }); btnQuery = (Button) findViewById(R.id.btnInternalQuery); btnQuery.setOnClickListener(new OnClickListener() { public void onClick(View v) { MyInternalStorage myInternal = new MyInternalStorage( InternalStorageActivity.this); String[] files = myInternal.queryAllFile(); ArrayAdapter<String> fileArray = new ArrayAdapter<String>( InternalStorageActivity.this, android.R.layout.simple_list_item_1, files); lvData.setAdapter(fileArray); Toast.makeText(InternalStorageActivity.this, "查詢文件列表", Toast.LENGTH_SHORT).show(); } }); btnDelete = (Button) findViewById(R.id.btnInternalDelete); btnDelete.setOnClickListener(new OnClickListener() { public void onClick(View v) { String filename = etFilename.getText().toString(); ; MyInternalStorage myInternal = new MyInternalStorage( InternalStorageActivity.this); myInternal.delete(filename); Toast.makeText(InternalStorageActivity.this, "刪除文件成功", Toast.LENGTH_SHORT).show(); } }); } }
使用內部存儲的方式進行數據持久化,文件的地址將保存在/data/data/<package_name>/files/路徑下,上面創建了三個文件,最后刪掉了一個,如果是使用的模擬器,可以直接在File Explorer中查看:
緩存(cache)
既然提到了內部存儲,這里再簡單的說說關於緩存文件(cache files)。cache files的操作與操作內部存儲中的文件方式基本一致,只是獲取文件的路徑有說不同。如果需要使用緩存的方式進行數據持久話,那么需要使用Context.getCacheDir()方法獲取文件保存的路徑。
對於緩存文件而言,當設備內部內存存儲空間不足的時候,Android會有自動刪除的機制刪除這些緩存文件,用來恢復可用空間,所以對於緩存文件而言,內容一般最好控制在1MB之下,並且也不要存放重要的數據,因為很可能下次去取數據的時候,已經被Android系統自動清理了。
External Storage
使用外部存儲實現數據持久化,這里的外部存儲一般就是指的是sdcard。使用sdcard存儲的數據,不限制只有本應用訪問,任何可以有訪問Sdcard權限的應用均可以訪問,而Sdcard相對於設備的內部存儲空間而言,會大很多,所以一般比較大的數據,均會存放在外部存儲中。
使用SdCard存儲數據的方式與內部存儲的方式基本一致,但是有三點需要注意的:
- 第一點,需要首先判斷是否存在可用的Sdcard,這個可以使用一個訪問設備環境變量的類Environment進行判斷,這個類提供了一系列的靜態方法,用於獲取當前設備的狀態,在這里獲取是否存在有效的Sdcard,使用的是Environment.getExternalStorageState()方法,返回的是一個字符串數據,Environment封裝好了一些final對象進行匹配,除了Environment.MEDIA_MOUNTED外,其他均為有問題,所以只需要判斷是否是Environment.MEDIA_MOUNTED狀態即可。
- 第二點,既然轉向了Sdcard,那么存儲的文件路徑就需要相對變更,這里可以使用Envir.getExternalStorageDirectory()方法獲取當Sdcard的根目錄,可以通過它訪問到相應的文件。
- 第三點,需要賦予應用程序訪問Sdcard的權限,Android的權限控制尤為重點,在Android程序中,如果需要做一些越界的操作,均需要對其進行授權才可以訪問。在AndroidManifest.xml中添加代碼:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
因為訪問Sdcard的方式和訪問內部存儲的方式差不多,這里就展示一個Save的方法,用於保存文件,其他CRUD操作,這里就不再一一給出了。

public void saveToSdcard(String filename, String content) throws IOException { if (Environment.MEDIA_MOUNTED.equals(Environment .getExternalStorageState())) { Log.i("main", "本設備有存儲卡!"); File file = new File(Environment.getExternalStorageDirectory(), filename); FileOutputStream fos = null; fos = new FileOutputStream(file); fos.write(content.getBytes()); fos.close(); } }
而如果使用SdCard存儲文件的話,存放的路徑在Sdcard的根目錄下,如果使用模擬器運行程序的話,創建的文件在/mnt/sdcard/目錄下:
補充:對於現在市面上很多Android設備,自帶了一個大的存儲空間,一般是8GB或16GB,並且又支持了Sdcard擴展,對於這樣的設備,使用Enviroment.getExternalStorageDirectory()方法只能獲取到設備自帶的存儲空間,對於另外擴展的Sdcard而言,需要修改路徑。