Android文档Training之数据保存

大多数Android应用都需要保存数据,甚至说在onPause()状态为了防止用户进度没有丢失而保存用户app状态的消息,大多数非平凡的app也需要保存用户设置,一些app必须要管理文件或者数据库中大量的信息,这里我们将介绍在android中几种基本的数据存储,包括:

在shared preferences文件中保存简单的键值对
在Android文件系统中保存任意的文件
通过SQLite管理使用数据库

键值对保存

如果你有相对比较少的键值对数据需要保存,你应该使用SharedPreferences APIs. 一个SharedPreferences 对象是一个包含键值对并提供简单的方法来读写他们的文件系统,每一个SharedPreferences 文件都是framework来管理的,可以是私有的,可以被分享.
下面我们来介绍怎样使用SharedPreferece APIs来保存或者获取简单的值.
注意: SharedPreferences APIs只能用来读写键值对,不要和Preference APIs搞混淆,Preference类是用来帮助你构造你的app设置里的用户界面(但是它们使用SharedPreferences 作为保存app设置的工具),想知道更多关Preference APIs的使用,请看设置引导.

获取一个SharedPreferences的句柄

你可以创建一个新的shared preference文件或者通过调用下面两个方法之一来获取已经存在的shared preference文件:

getSharedPreferences() — 如果你需要根据文件名获取不同的share preference文件, 你可以使用这个方法并制定第一个参数(文件名).你可以在你的app中任何Context里调用这个方法.
getPreferences() —如果你需要使用这个activity对应唯一的一个shared preference文件你可以在activity中调用该方法.这个方法将取回属于这个activity的默认的一个shared preference文件,你不必提供文件名.

例如: 以下的代码是在一个Fragment中执行的,它通过使用字符串R.string.preference_file_key来访问shared preferences文件并用私有的方式打开,这样这个文件只能被你自己的app访问.

Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
        getString(R.string.preference_file_key), Context.MODE_PRIVATE);

当给你的shared preference 文件命名的时候,你应该使用一个对你的app文艺标示的名称,比如 “com.example.myapp.PREFERENCE_FILE_KEY”.

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

写入数据到SharedPreferences

要写入数据到一个shared preferences文件, 要先通过你的SharedPreferences调用edit()方法来获取到一个SharedPreferences.Editor对象.

把键值对通过写入数据的方法(比如putInt(),putString())来写入Editor,然后再调用commit()方法来提交改变,例如:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

从SharedPreferences文件中读取数据

要从shared preferences 文件中获取值, 调用类似getInt()或者getString()的方法,并准备好你需要的值对应的key,有时候会需要你提供一个默认的值以防在sharedPreferences中找不到对应的值,例如:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
long highScore = sharedPref.getInt(getString(R.string.saved_high_score), defaultValue);

文件保存

android使用文件系统和其他平台以磁盘为基础的文件系统是类似的,下面我们来介绍用File APIs来读写android文件系统中的文件.

一个文件对象适合用来都或者写大量的从开头到结尾中间不跳跃的数据. 例如:它比较适合用来处理图片文件或者仍以其他网络上的信息交流.

下面我们来介绍在你的app中执行基本的文件相关的任务,在这里我们假设你对Linux 文件系统非常熟悉,并且了解java.io中的标准的文件输入输出APIs.

选择内部或者外部存储

素有的android设备都有两个文件存储区域:“内部”和“外部”,这个名称来自早期的android,那时候大部分设备提供了内置的不易丢失的内存(内部存储),再加上里一个可以移除的中等尺寸的存储比如micro SD卡(外部存储).一些设备把永久的存储空间分为两部分 “内部”和“外部”,所以就算没有可以移除的外部存储,也会总有两个存储空间,不管你有没有移除外部存储,APIs都是一样的.下面的列表描述了每一个存储空间的相关信息.

内部存储:
    总是可以使用的.
    在这里保存的文件默认只有你自己的app才能访问到.
    当用户卸载你的app时,系统将移除内部存储里的所有的文件.
内部存储在你不想把文件直接暴露给用户或者别的app的时候是最好的存储位置.


外部存储:
    并不总是可以使用的,因为用户可以连接USB存储来增加容量,也可以通过把它从设备上移除.
    这里保存的文件谁都有权利去读,所以这里保存的文件不再是完全在你的掌控之下.
    当用户卸载你的app的时候,系统将会移除你保存在getExternalFilesDir()里的你的app的文件.
外部存储是在你不需要对文件进行限制访问,并且你希望分享给其他的app或者允许用户在电脑上访问时最好的选择.

提示: 虽然app是默认安装在内部存储的,但是你能在manifest指定android:installLocation属性,这样你的app将会被安装在外部存储上,用户一般比较喜欢当apk安装包比较大而且外部存储比内部存储大得多的时候安装到外部存储,想了解更多,可以查看App Install Location.

为外部存储获取相应的权限:想要获取外部存储的权限,你需要在manifest 文件中请求WRITE_EXTERNAL_STORAGE 权限:

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

注意: 一般来说,所有的app在没有特殊权限的情况下都有读取外部存储的能力. 然而, 在以后这都将会被改变. 如果你的app需要读取外部存储 (但不需要写入), 那你需要在manifest中定义 READ_EXTERNAL_STORAGE 权限. 为了确保你的app能正常工作,你应该在它权限申请有效之前先定义这个权限.

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

记住, 如果你的app使用了 WRITE_EXTERNAL_STORAGE 权限, 则它也隐式的拥有了读取外部存储的权限.

保存文件到内部存储你不需要任何权限,你的应用总是有权限来读写它的内部的存储文件夹里的文件.

保存文件到内部存储

当保存文件到内部存储的时候,你可以通过调用下面的两个方法之一来获取到对应的文件夹:

getFilesDir()
    返回你的app对应的内部存储文件夹.
getCacheDir()
    返回你的app的临时缓存文件的内部存储文件夹. 一旦你不再需要这些文件或者缓存文件超过了你所设置的最大容量(比如1MB)请删除此文件夹里面的每一个文件. 如果系统存储比较低的时候,它将会毫无警告就删掉这些缓存文件. 

在这些文件夹中创建一个新文件,你可以使用File()构造方法,把上面的指定内部存储目录的方法值传递给File,例如:

File file = new File(context.getFilesDir(), filename);

一般来说,你可以调用 openFileOutput()来获得FileOutputStream 用来写入文件到你的内部存储. 例如,这里是写入一部分文本到一个文件中的代码:

String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;

try {
  outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
  outputStream.write(string.getBytes());
  outputStream.close();
} catch (Exception e) {
  e.printStackTrace();
}

或者,如果你需要缓存一些文件,你应该使用createTempFile().例如,一下的方法从URL获取文件名然后在你的app内部缓存文件夹中那个文件名创建一个文件:

public File getTempFile(Context context, String url) {
    File file;
    try {
        String fileName = Uri.parse(url).getLastPathSegment();
        file = File.createTempFile(fileName, null, context.getCacheDir());
    catch (IOException e) {
        // Error while creating file
    }
    return file;
}

注意:你的app的内部存储文件夹是在android文件系统中的具体的位置由你的app包名指定的. 理论上说, 如果你设置文件模式是readable,那么其他的app都可以都可以读取内部文件. 然而,前提是其他的app知道你包名和对应的文件名. 其他的app不能浏览你内部的文件夹也不能读写文件除非你指定了这些文件是可读的或者可写的. 所以只要你在内部存储文件时使用 MODE_PRIVATE, 他们就永远不能被其它的app所访问.

保存文件到外部存储

因为外部存储可能是不可用的-例如当用户把存储空间转交给PC或者用户移除了提供外部存储的SD卡-你应该总是在访问之前验证一下外部存储是否可用.你可以通过调用来查询外部存储状态 getExternalStorageState(). 如果返回状态等于 MEDIA_MOUNTED,那么你就可以读写你的文件了.例如,下面的放啊对判断存储可用性是很有效的:

    /* 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;
}

虽然外部存储可以被用户或者其他的app修改,但是你也许有需要在这里保存的两类文件.

Public files(公有文件)
    该文件可以被其他app或者用户自由访问.当你卸载你的app的时候,这些文件会被留在磁盘,用户仍然可以访问.
    例如,你的app照相机照的照片或者其他的下载下来的文件.
Private files(私有文件)
    文件完全属于你的app,在用户卸载后将会被删除. 虽然这些文件能被用户或者其他app访问(因为它在外部存储上), 这些文件在app之外不提供值给用户,当用户卸载app时,系统将会删除你的app在外部存储的你的私有的文件夹里的所有文件.
    例如,你的app下载的额外的资源文件或者临时媒体文件.

如果你想在外部存储上保存公有文件,使用getExternalStoragePublicDirectory()方法来获取一个在外部存储上的合适的文件夹. 这个方法需要指定一个你需要保存的文件类型的参数比如DIRECTORY_MUSIC 或者 DIRECTORY_PICTURES. 例如:

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;
}

如果你想保存你的app的私有文件到外部存储,你可以通过调用getExternalFilesDir()来获取合适的文件夹,并传给它一个代表文件夹类型的名称,每个用这种方法创建的每个文件夹爱将会被添加到父目录,这个父目录囊括了所有你的app的外部存储文件,当用户卸载你的app的时候会将这个父目录删除.

例如,你可以使用下面这个方法来为你的个人相册创建一个文件夹:

public File getAlbumStorageDir(Context context, String albumName) {
    // Get the directory for the app's private pictures directory. 
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

如果预定义的子目录名称没有适合你文件的,你可以通过调用
getExternalFilesDir()并给它传null值 . 它将返回你的app在外部存储的私有目录的根目录.

记住getExternalFilesDir()在文件夹中创建了一个在app被卸载的时候会被删除的文件夹. 如果你想保存在app卸载后仍然可用的文件–例如当你的app是一个照相机,用户卸载后仍然想把这些照片保存起来–这时候你应该使用getExternalStoragePublicDirectory().

不管你是使用getExternalStoragePublicDirectory()(这里的文件可以分享)还是使用getExternalFilesDir() (这里的文件是你的app私有的), 文件夹名使用API提供的常数(例如DIRECTORY_PICTURES)是非常重要的. 这些文件夹名称确保了里面的文件会被系统正确的处理. 例如, 保存在DIRECTORY_RINGTONES的中的文件将会被系统的媒体扫描器识别为铃声还不是音乐.

查询剩余空间

如果你提前知道你将要保存多少数据,你可以通过调用getFreeSpace() 或者getTotalSpace()来判断存储空间是否充足以防止产生IO异常. 这些方法返回在存储盘中当前可用的空间和总空间,这些信息在避免存储数据超过磁盘总容量时是非常有用的.

然而, 系统不保证你能写入getFreeSpace()返回值给定的那么多的字节数. 如果返回值比你要保存的数据多几MB,或者如果文件系统使用不超过90%,那么保存数据到文件系统一般是比较安全的(不会出现因空间不足引起的IO异常),否则,你可能写入数据到存储空间会失败.

注意: 你再保存文件的时候不需要检测有多少空间是可用的. 你可以直接写入,然后如果发生一个IO异常你可以抓住它. 如果你不知道你需要使用多少空间你也许需要这样做. 例如, 如果在保存之前先通过把一个PNG图片转化为JPEG图片来改变文件编码,你肯定提前不知道文件的大小.

删除一个文件

你应该经常删除那些不再需要的文件,删除文件最直接的方法就是先获得一个打开的文件的引用,然后调用它自己的delete()方法.

myFile.delete();    

如果文件是保存在内部存储,你也可以通过在Context通过调用deleteFile()来锁定和删除文件:

myContext.deleteFile(fileName);

注意: 当用户卸载你的app的时候androd系统删除以下的文件::

所有你保存在内部存储里的文件
所哟你保存在外部存储里的用getExternalFilesDir()创建/保存的文件

然而, 你应该手动删除所有的用getCacheDir()创建的缓存文件,这些缓存文件是通过正规的方方法创建的,也要通过正规的方式来删除你不再需要的文件.

SQL数据库中数据保存

保存数据到数据库对重复或有结构的数据是非常理想的方法,比如联系人信息,在这里假设你对SQL数据库非常熟悉,这里帮助你熟悉android上的SQLite数据库. 你在android需要的使用的 APIs在android.database.sqlite里.

定义架构和协议

SQL数据库主要的原理之一是架构:一个个好似话的
One of the main principles of SQL databases is the schema: a formal declaration of how the database is organized. The schema is reflected in the SQL statements that you use to create your database. You may find it helpful to create a companion class, known as a contract class, which explicitly specifies the layout of your schema in a systematic and self-documenting way.

A contract class is a container for constants that define names for URIs, tables, and columns. The contract class allows you to use the same constants across all the other classes in the same package. This lets you change a column name in one place and have it propagate throughout your code.

A good way to organize a contract class is to put definitions that are global to your whole database in the root level of the class. Then create an inner class for each table that enumerates its columns.

Note: By implementing the BaseColumns interface, your inner class can inherit a primary key field called _ID that some Android classes such as cursor adaptors will expect it to have. It’s not required, but this can help your database work harmoniously with the Android framework.

For example, this snippet defines the table name and column names for a single table:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值