1. 程式人生 > >13.1 存儲選項

13.1 存儲選項

xposed 詳情 list 分組 close str 調試 cdi uil

Android 為您提供了多種選項來保存永久性應用數據。您所選擇的解決方案取決於您的特定需求,例如數據應該是應用的私有數據,還是可供其他應用(和用戶)訪問,以及您的數據需要多少空間等。

您的數據存儲選項如下:

共享首選項
在鍵值對中存儲私有原始數據。
內部存儲
在設備內存中存儲私有數據。
外部存儲
在共享的外部存儲中存儲公共數據。
SQLite 數據庫
在私有數據庫中存儲結構化數據。
網絡連接
在網絡中使用您自己的網絡服務器存儲數據。

Android 為您提供了一種方法 — 使用內容提供程序將您的數據(甚至是您的私有數據)公開給其他應用。 內容提供程序是一個可選組件,可根據您希望施加的任何限制公開您的應用數據的讀/寫訪問權限。 如需了解有關使用內容提供程序的更多信息,請參閱內容提供程序文檔。

使用共享首選項

SharedPreferences 類提供了一個通用框架,以便您能夠保存和檢索原始數據類型的永久性鍵值對。 您可以使用 SharedPreferences 來保存任何原始數據:布爾值、浮點值、整型值、長整型和字符串。 此數據將跨多個用戶會話永久保留(即使您的應用已終止亦如此)。

用戶首選項

嚴格來說,共享首選項並非用於保存“用戶首選項”,例如用戶所選擇的鈴聲。 如果您有興趣為您的應用創建用戶首選項,請參閱 PreferenceActivity,其中為您提供了一個 Activity 框架,用於創建將會自動永久保留(通過共享首選項)的用戶首選項。

要獲取應用的 SharedPreferences

對象,請使用以下兩個方法之一:

  • getSharedPreferences() - 如果您需要多個按名稱(使用第一個參數指定)識別的首選項文件,請使用此方法。
  • getPreferences() - 如果您只需要一個用於 Activity 的首選項文件,請使用此方法。 由於這將是用於 Activity 的唯一首選項文件,因此無需提供名稱。

要寫入值:

  1. 調用 edit() 以獲取 SharedPreferences.Editor
  2. 使用 putBoolean()putString() 等方法添加值。
  3. 使用 commit() 提交新值

要讀取值,請使用 getBoolean()getString()

SharedPreferences 方法。

以下是在計算器中保存靜音按鍵模式首選項的示例:

public class Calc extends Activity {
    public static final String PREFS_NAME = "MyPrefsFile";

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

       // Restore preferences
       SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
       boolean silent = settings.getBoolean("silentMode", false);
       setSilent(silent);
    }

    @Override
    protected void onStop(){
       super.onStop();

      // We need an Editor object to make preference changes.
      // All objects are from android.context.Context
      SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
      SharedPreferences.Editor editor = settings.edit();
      editor.putBoolean("silentMode", mSilentMode);

      // Commit the edits!
      editor.commit();
    }
}

使用內部存儲

您可以直接在設備的內部存儲中保存文件。默認情況下,保存到內部存儲的文件是應用的私有文件,其他應用(和用戶)不能訪問這些文件。 當用戶卸載您的應用時,這些文件也會被移除。

要創建私有文件並寫入到內部存儲:

  1. 使用文件名稱和操作模式調用 openFileOutput()。 這將返回一個 FileOutputStream
  2. 使用 write() 寫入到文件。
  3. 使用 close() 關閉流式傳輸。

例如:

String FILENAME = "hello_file";
String string = "hello world!";

FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(string.getBytes());
fos.close();

MODE_PRIVATE 將會創建文件(或替換具有相同名稱的文件),並將其設為應用的私有文件。 其他可用模式包括:MODE_APPENDMODE_WORLD_READABLEMODE_WORLD_WRITEABLE

:自 API 級別 17 以來,常量 MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 已被棄用。從 Android N 開始,使用這些常量將會導致引發 SecurityException。這意味著,面向 Android N 和更高版本的應用無法按名稱共享私有文件,嘗試共享“file://”URI 將會導致引發 FileUriExposedException。 如果您的應用需要與其他應用共享私有文件,則可以將 FileProviderFLAG_GRANT_READ_URI_PERMISSION 配合使用。另請參閱共享文件。

要從內部存儲讀取文件:

  1. 調用 openFileInput() 並向其傳遞要讀取的文件名稱。 這將返回一個 FileInputStream
  2. 使用 read() 讀取文件字節。
  3. 然後使用 close() 關閉流式傳輸。

提示:如果在編譯時想要保存應用中的靜態文件,請在項目的 res/raw/ 目錄中保存該文件。 可以使用 openRawResource() 打開該資源並傳遞 R.raw.<filename> 資源 ID。 此方法將返回一個 InputStream,您可以使用該流式傳輸讀取文件(但不能寫入到原始文件)。

保存緩存文件

如果您想要緩存一些數據,而不是永久存儲這些數據,應該使用 getCacheDir() 來打開一個 File,它表示您的應用應該將臨時緩存文件保存到的內部目錄。

當設備的內部存儲空間不足時,Android 可能會刪除這些緩存文件以回收空間。 但您不應該依賴系統來為您清理這些文件, 而應該始終自行維護緩存文件,使其占用的空間保持在合理的限制範圍內(例如 1 MB)。 當用戶卸載您的應用時,這些文件也會被移除。

其他實用方法

getFilesDir()
獲取在其中存儲內部文件的文件系統目錄的絕對路徑。
getDir()
在您的內部存儲空間內創建(或打開現有的)目錄。
deleteFile()
刪除保存在內部存儲的文件。
fileList()
返回您的應用當前保存的一系列文件。

使用外部存儲

每個兼容 Android 的設備都支持可用於保存文件的共享“外部存儲”。 該存儲可能是可移除的存儲介質(例如 SD 卡)或內部(不可移除)存儲。 保存到外部存儲的文件是全局可讀取文件,而且,在計算機上啟用 USB 大容量存儲以傳輸文件後,可由用戶修改這些文件。

註意:如果用戶在計算機上裝載了外部存儲或移除了介質,則外部存儲可能變為不可用狀態,並且在您保存到外部存儲的文件上沒有實施任何安全性。 所有應用都能讀取和寫入放置在外部存儲上的文件,並且用戶可以移除這些文件。

使用作用域目錄訪問

在 Android 7.0 或更高版本中,如果您需要訪問外部存儲上的特定目錄,請使用作用域目錄訪問。 作用域目錄訪問可簡化您的應用訪問標準外部存儲目錄(例如 Pictures 目錄)的方式,並提供簡單的權限 UI,清楚地詳細介紹應用正在請求訪問的目錄。 有關作用域目錄訪問的更多詳情,請參閱使用作用域目錄訪問。

獲取外部存儲的訪問權限

要讀取或寫入外部存儲上的文件,您的應用必須獲取 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 系統權限。 例如:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

如果您同時需要讀取和寫入文件,則只需請求 WRITE_EXTERNAL_STORAGE 權限,因為此權限也隱含了讀取權限要求。

:從 Android 4.4 開始,如果您僅僅讀取或寫入應用的私有文件,則不需要這些權限。 如需了解更多信息,請參閱下面有關保存應用私有文件的部分。

檢查介質可用性

在使用外部存儲執行任何工作之前,應始終調用 getExternalStorageState() 以檢查介質是否可用。介質可能已裝載到計算機,處於缺失、只讀或其他某種狀態。 例如,以下是可用於檢查可用性的幾種方法:

/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}

getExternalStorageState() 方法將返回您可能需要檢查的其他狀態(例如介質是否處於共享 [連接到計算]、完全缺失、錯誤移除等狀態)。 當您的應用需要訪問介質時,您可以使用這些狀態向用戶通知更多信息。

保存可與其他應用共享的文件

在媒體掃描程序中隱藏您的文件

在您的外部文件目錄中包含名為 .nomedia 的空文件(註意文件名中的點前綴)。 這將阻止媒體掃描程序讀取您的媒體文件,並通過 MediaStore 內容提供程序將其提供給其他應用。 但如果您的文件真正是應用的私有文件,則應該將其保存在應用私有的目錄中。

一般而言,應該將用戶可通過您的應用獲取的新文件保存到設備上的“公共”位置,以便其他應用能夠在其中訪問這些文件,並且用戶也能輕松地從該設備復制這些文件。 執行此操作時,應使用共享的公共目錄之一,例如 Music/Pictures/Ringtones/ 等。

要獲取表示相應的公共目錄的 File,請調用 getExternalStoragePublicDirectory(),向其傳遞您需要的目錄類型,例如 DIRECTORY_MUSICDIRECTORY_PICTURESDIRECTORY_RINGTONES 或其他類型。通過將您的文件保存到相應的媒體類型目錄,系統的媒體掃描程序可以在系統中正確地歸類您的文件(例如鈴聲在系統設置中顯示為鈴聲而不是音樂)。

例如,以下方法在公共圖片目錄中創建了一個用於新相冊的目錄:

public File getAlbumStorageDir(String albumName) {
    // Get the directory for the user‘s public pictures directory.
    File file = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

保存應用私有文件

如果您正在處理的文件不適合其他應用使用(例如僅供您的應用使用的圖形紋理或音效),則應該通過調用 getExternalFilesDir() 來使用外部存儲上的私有存儲目錄。此方法還會采用 type 參數指定子目錄的類型(例如 DIRECTORY_MOVIES)。 如果您不需要特定的媒體目錄,請傳遞 null 以接收應用私有目錄的根目錄。

從 Android 4.4 開始,讀取或寫入應用私有目錄中的文件不再需要 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 權限。 因此,您可以通過添加 maxSdkVersion 屬性來聲明,只能在較低版本的 Android 中請求該權限:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
    ...
</manifest>

:當用戶卸載您的應用時,此目錄及其內容將被刪除。此外,系統媒體掃描程序不會讀取這些目錄中的文件,因此不能從 MediaStore 內容提供程序訪問這些文件。 同樣,不應將這些目錄用於最終屬於用戶的媒體,例如使用您的應用拍攝或編輯的照片或用戶使用您的應用購買的音樂等 — 這些文件應保存在公共目錄中。

有時,已分配某個內部存儲器分區用作外部存儲的設備可能還提供了 SD 卡槽。在使用運行 Android 4.3 和更低版本的這類設備時,getExternalFilesDir() 方法將僅提供內部分區的訪問權限,而您的應用無法讀取或寫入 SD 卡。不過,從 Android 4.4 開始,可通過調用 getExternalFilesDirs() 來同時訪問兩個位置,該方法將會返回包含各個位置條目的 File 數組。 數組中的第一個條目被視為外部主存儲;除非該位置已滿或不可用,否則應該使用該位置。 如果您希望在支持 Android 4.3 和更低版本的同時訪問兩個可能的位置,請使用支持庫中的靜態方法 ContextCompat.getExternalFilesDirs()。 在 Android 4.3 和更低版本中,此方法也會返回一個 File 數組,但其中始終僅包含一個條目。

註意 盡管 MediaStore 內容提供程序不能訪問 getExternalFilesDir()getExternalFilesDirs() 所提供的目錄,但其他具有 READ_EXTERNAL_STORAGE 權限的應用仍可訪問外部存儲上的所有文件,包括上述文件。 如果您需要完全限制您的文件的訪問權限,則應該轉而將您的文件寫入到內部存儲。

保存緩存文件

要打開表示應該將緩存文件保存到的外部存儲目錄的 File,請調用 getExternalCacheDir()。 如果用戶卸載您的應用,這些文件也會被自動刪除。

與前述 ContextCompat.getExternalFilesDirs() 相似,您也可以通過調用 ContextCompat.getExternalCacheDirs() 來訪問輔助外部存儲(如果可用)上的緩存目錄。

提示:為節省文件空間並保持應用性能,您應該在應用的整個生命周期內仔細管理您的緩存文件並移除其中不再需要的文件,這一點非常重要。

使用數據庫

Android 提供了對 SQLite 數據庫的完全支持。應用中的任何類(不包括應用外部的類)均可按名稱訪問您所創建的任何數據庫。

創建新 SQLite 數據庫的推薦方法是創建 SQLiteOpenHelper 的子類並覆蓋 onCreate() 方法,在此方法中,您可以執行 SQLite 命令以創建數據庫中的表。 例如:

public class DictionaryOpenHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 2;
    private static final String DICTIONARY_TABLE_NAME = "dictionary";
    private static final String DICTIONARY_TABLE_CREATE =
                "CREATE TABLE " + DICTIONARY_TABLE_NAME + " (" +
                KEY_WORD + " TEXT, " +
                KEY_DEFINITION + " TEXT);";

    DictionaryOpenHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(DICTIONARY_TABLE_CREATE);
    }
}

然後您可以使用已定義的構造函數獲取 SQLiteOpenHelper 實現的實例。 要從數據庫執行寫入和讀取操作,請分別調用 getWritableDatabase()getReadableDatabase()。二者都會返回一個表示數據庫的 SQLiteDatabase 對象,並提供用於 SQLite 操作的方法。

Android 沒有實施標準 SQLite 概念之外的任何限制。我們推薦包含一個可用作唯一 ID 的自動增量值關鍵字段,以便快速查找記錄。 私有數據不要求這樣做,但如果您實現了一個內容提供程序,則必須包含使用 BaseColumns._ID 常量的唯一 ID。

您可以使用 SQLiteDatabase query() 方法來執行 SQLite 查詢,這些方法可接受各種查詢參數,例如要查詢的表、投影、選擇、列、分組和其他參數。 對於復雜的查詢,例如需要列別名的查詢,應該使用 SQLiteQueryBuilder,它將提供多種便捷的方法來構建查詢。

每個 SQLite 查詢都會返回一個指向該查詢找到的所有行的 Cursor。 您始終可以使用 Cursor 機制來瀏覽數據庫查詢結果,以及讀取行和列。

如需演示 Android 中的 SQLite 數據庫使用方法的示例應用,請參閱 記事本和 可搜索字典應用。

數據庫調試

Android SDK 包含一項 sqlite3 數據庫工具,利用此工具可以瀏覽表內容,運行 SQL 命令,以及在 SQLite 數據庫上執行其他實用功能。 請參閱從遠程 shell 檢查 sqlite3 數據庫,了解如何運行此工具。

使用網絡連接

您可以使用網絡(如果可用)來存儲和檢索有關您自己的網絡服務的數據。 要執行網絡操作,請使用以下包中的類:

  • java.net.*
  • android.net.*

13.1 存儲選項