Android 数据存储 I-存储选项

概述:

Android为我们提供了多种选择来保存持久化应用数据.选择哪种方案取决于我们的需求, 比如数据是否应该对自己的应用是私有的, 对其他应用(和用户)是否可访问; 还有需要多大空间来保存数据. 我们的数据存储选项如下:

Shard Preferences: 用键值对模式保存私有原始数据. (卸载删除)

Internal Storage: 在设备的内部存储中保存私有数据. (卸载删除)

External Storage: 在设备的外部共享存储中保存公共数据. (公共/私有/缓存(私有的卸载删除))

SQLite Databases: 在私有数据库中保存结构化数据.

Network Connection: 在自己的网络服务器上保存数据.

Android为我们提供了一种可以暴露私有数据给其它APP的方法, 就是contentprovider. content provider是一种可选的组件, 让我们的APP可以为其它APP提供读写的接口,

使用SharedPreferences:

SharedPreferences类提供了一个通用的框架让我们可以以键值对的模式保存和读取原始类型的持久化数据. 我们可以使用SharedPreferences来保存任何原始数据: Boolean, float, int, long和string. 这些数据将会持久化并贯穿用户会话(甚至我们的APP被杀死了).

注意, shared preferences并不是严格的用来保存用户偏好的, 比如用户选择了什么铃声. 如果打算保存用户偏好, 那么应该考虑使用PreferenceActivity, 它提供了一个Activity框架给我们来创建用户偏好, 并自动持久化(使用shared preferences).

想要获取一个SharedPreferences对象, 使用这两种方法:

l  getSharedPreferences(): 如果我们需要多个preferences文件, 并用名字区别它们的话, 那么就使用这个方法. 我们可以使用第一个参数指定名字.

l  getPreferences(): 如果只需要一个preferences文件, 就用这个方法. 因为这是唯一的一个preferences文件, 所以不需要指定名字.

想要写入值的话:

1.      调用edit()方法来获取一个SharedPreferences.Editor.

2.      使用putBoolean和putString()这样的方法来添加值.

3.      用commit()方法提交新的值.

要读取值的话, 则需要使用SharedPreferences方法比如getBoolean()和getString().

下面是一个栗子, 它是用来保存计算器的无声按键模式的:

publicclass Calc extends Activity {
    public staticfinal 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();
    }
}

使用内部存储:

我们可以直接将文件保存在设备的内部存储器中. 默认情况下, 文件在内部存储器中是私有的, 其它的APP无法访问这些文件(用户也不行). 当用户卸载APP的时候, 这些文件将会被移除. 要创建和写入一个私有文件到内部存储器:

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_PROVATE将会创建一个文件(或者用同样的名字覆盖一个文件)并使它对APP是私有的. 其它可用的模式还有MODE_APPEND, MODE_WORLD_READABLE和MODE_WRITEABLE.

想要从内部存储读取一个文件的话:

1.      调用openFileInput()并传入文件名. 它会返回一个FileInputStream.

2.      使用read()方法读取bytes.

3.      写完之后用close()方法关闭它.

提醒: 如果我们想要在编译的时候保存一个静态文件, 那么请保存该文件在res/raw/目录下. 我们可以用openRawResource()方法打开它, 传入R.raw.<filename>资源ID. 该方法会返回一个InputStream让我们可以读取文件(但是我们不能写入到一个原始文件中).

保存缓存文件:

如果我们希望缓存一些数据, 而不是持久化保存, 我们应该使用getCacheDir()方法来打开一个File, 它表示一个我们的APP应该用来保存临时缓存文件的内部目录. 当设备处于内部存储不足的情况, Android可能会删除这些缓存文件来恢复空间. 但是我们不应该一来系统来清理这些文件. 而是自己维护这些缓存文件, 保持在一个合理的大小, 比如1M. 当用户卸载APP的时候, 这些文件会被删除.

其它有用的方法:

getFilesDir(): 取得保存内部文件的目录的绝对路径.

getDir(): 在内部存储空间中创建(或者打开一个已经存在的)目录.

deleteFile(): 在内部存储中删除一个文件.

fileList(): 返回APP保存的当前文件列表.

使用外部存储:

每个兼容Android的设备都支持一个共享的”外部存储”让我们可以存储文件. 它可以是一个可移除的存储媒介(比如SD卡)或者一个内部的存储(不可移除). 保存在外部存储中的文件是全局可读的, 并可以被用户修改. 比如他们连接一台电脑时.

注意, 外部存储可能会在用户在电脑上绑定外部存储或者移除了媒介后变成不可读的, 并且在外部存储的文件并没有强制加密措施. 所有的APP都可以读写和移除这些文件.

获取外部存储的访问:

为了在外部存储区读写文件, 我们的APP必须取得READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE系统权限, 栗子:

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

如果我们既需要读取有需要写文件, 那么只需要WRITE_EXTERNAL_STORAGE权限就可以了, 因为它隐含了读访问权限. 注意, 从Android 4.4开始, 如果只读写APP的私有文件, 那么就不用再需要这些权限了.

检查媒介可用性:

在对外部存储做任何操作之前, 我们应该总是调用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()方法会返回其它我们可能有兴趣的状态, 比如是媒介否在共享状态(跟电脑连接ing), 是否完全不见了, 已经被暴力移除了等. 我们可以使用这些来提醒用户更多的信息.

可以共享给其它APP的文件:

通常, 公共的文件应该保存在设备的公共目录下, 这样其他的app就可以访问它们, 用户也可以简单的从设备copy它们. 这种情况下我们应该选择公共目录比如Music/, Pictures/, 和Ringtones/. 想要获取一个代表合适公共目录的File, 应该调用getExternalStoragePublicDirectory(), 并传入一个想要的目录类型, 比如DIRECTORY_MUSIC,DIRECTORY_PICTURES, DIRECTORY_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;
}

我们还可以对媒体扫描器隐藏我们的文件. 只需要在文件夹中包含一个.nomedia文件即可(请留意文件名中的点), 这会阻止文件扫描器读取我们的媒体文件和通过MediaStore content provider将它们提供给其它的APP. 如果想要完全的私有该文件,那么:

保存文件为APP私有:

如果我们创建的文件不打算让其它的APP使用(比如只给自己APP用的图片和声音资源), 我们应该使用一个外部存储的私人的存储目录, 可以调用getExternalFilesDir()方法. 该方法需要一个类型参数来指定子目录的类型(比如DIRECTORY_MOVIES). 如果我们不需要指定的媒体目录, 那么传入null来获取私人目录的根目录.

从Android4.4开始, 读写私人目录中的文件不需要READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE权限. 所以我们声明的权限只有在低版本中才有必要, 可以通过maxSdkVersion来实现, 栗子:

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

注意: 当用户卸载我们的APP的时候, 这个目录和其中所有的内容将会被删除. 同样, 系统媒体扫描器不会读取这些目录的文件, 所以它们从MediaStore content provider是不可访问的. 因此我们不应该使用这些目录来存放最终属于用户的媒体, 比如相册或者用户从APP中购买的音乐等– 这些文件应该保存在公共目录中.

有时, 一个设备分配其内部存储的一部分用来作为外部存储, 也可能提供一个SD卡插槽. 当这样的一个设备运行在Android 4.3及更低版本中, getExternalFilesDir()方法将只提供对内部存储部分的访问, 而APP不能读写SD卡的部分. 从Android 4.4开始, 我们可以通过getExternalFilesDirs()方法访问这两个区域, 它会返回一个包含各部分路径的文件列表. 列表的第一个部分是基本的外部存储, 除非它满了或者不可用, 否则我们应该使用该路径. 如果我们希望在Android 4.3及更低版本中访问这两种可用路径, 则需要使用support library的静态方法ContextComopat.getExternalFilesDirs(). 该方法也会返回一个File的列表, 但是在Android4.3及更低版本中总是只包含一个条目.

注意: 虽然getExternalFilesDir()和getExternalFilesDirs()方法提供的目录不能用MediaStore content provider直接访问, 其它拥有READ_EXTERNAL_STORAGE权限的APP可以访问所有的外部存储的文件. 如果我们需要完全限制对文件的访问, 那么应该使用内部存储.

保存缓存文件:

想要打开一个存放在外部存储目录的缓存文件, 调用getExternalCacheDir(). 如果用户卸载了APP, 这些文件将会被自动删除. 跟ContextCompat.getExternalFilesDirs()类似, 我们我们还可以在备用外部存储通过ContextCompat.getExternalCacheDirs()方法访问一个缓存目录.

提醒: 想要保留文件空间并维护自己的APP的性能, 细心的管理缓存文件是很重要的, 当不需要的时候应该将它们删除.

使用数据库:

Android提供了对SQLite数据库的完整支持. 我们创建的任何数据库都可以通过名称被我们APP下的任何类访问, 但是不能被APP以外访问. 推荐的创建SQLite数据库的方法是创建一个SQLiteOpenHelper的子类, 并重写onCreate()方法, 在这里我们可以执行SQLite命令来创建数据库表哥, 栗子:

publicclass DictionaryOpenHelperextends SQLiteOpenHelper{

    private staticfinal int DATABASE_VERSION= 2;
    private staticfinal String DICTIONARY_TABLE_NAME= "dictionary";
    private staticfinal 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操作方法. 我们可以使用SQLiteDatabase query()方法执行SQLite查询, 它接受各种查询参数, 比如要查询的表, selection, 列, 分组和别的. 对于更加复杂的查询, 比如那些请求列别名, 我们应该使用SQLiteQueryBuilder, 它提供了一些方便的方法来构建查询.

每个SQLite查询将会返回一个Cursor, 它指向所有查询得到的行. Cursor可以用来浏览数据库查询得到的结果.

Android没有引入任何超出标准SQLite概念的限制. 官方建议使用一个自动递增的关键值来作为ID, 这样可以快速索引某个记录. 这对于私有数据并非必须的, 但是如果实现了一个content provider, 则必须包含一个唯一的ID, 并使用BaseColumns._ID常量.

数据库调试:

Android SDK包含了一个sqlite3数据库工具, 这让我们可以浏览表内容, 运行SQL命令和执行其他有用的功能.详情可以查看这里:Examiningsqlite3 databases from a remote shell.

使用网络连接:

我们可以使用网络(如果可用的话)在自己的服务器上存取数据. 想要执行这些操作, 这两个类将会有所帮助:java.net.*,android.net.*.

 

总结:

我们可以使用这些方法来保存自己的数据, 应该根据自己的需求来选择使用哪种:

Shard Preferences: 用键值对模式保存私有原始数据. (卸载删除); 通常我们用它来保存一些简单的原始数据, 但是不用来保存设置项(设置项通常用封装好的PreferenceActivity来实现), 比如当前登陆用户的私有信息等.

Internal Storage: 在设备的内部存储中保存私有数据. (卸载删除); 用来保存文件, 通常是一些比较重要的APP私有信息, 可以保存缓存信息, 但是通常情况下内部存储的空间比外部的小, 所以在保存一些比较大的文件类型的时候(比如视频缓存)应该考虑保存在外部存储中.

External Storage: 在设备的外部共享存储中保存公共数据. (公共/私有/缓存(私有的卸载删除)), 可以共有可以私有, 而且是全局唯一的共有文件保存位置. 所以公共文件一定要保存在这里.

SQLite Databases: 在私有数据库中保存结构化数据. 这个比较牛了, 用途广泛特别是在保存聊天记录这种量大又要求按顺序存取的时候, 数据库最合适不过了.

Network Connection: 在自己的网络服务器上保存数据. 几乎所有的网络相关的APP都会用到.

 

参考: https://developer.android.com/guide/topics/data/data-storage.html

 

发布了81 篇原创文章 · 获赞 4 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章