Android 数据存储

来源:互联网 发布:淘宝原道电商讲师东风 编辑:程序博客网 时间:2024/05/22 03:44

简介:

Android 提供了多种数据存储的方式,包括SharedPreference,内部/外部存储,网络存储,数据库存储等。本文只记录自己常用的几种存储方式:

  • SharedPreference
  • 内部/外部存储
  • 数据库(SQLite)

使用方法:

SharedPreference:

SharedPreference(以下简称sp):对象指向包含键值对的文件并提供读写这些文件的简单方法,主要保存相对较小的键值集合。


注:SharedPreferences API 仅用于读写键值对


在使用sp文件进行文件的存取之前首先需要获取sp的实例对象,在实际代码中有以下两种方式可以获得:

  • getSharedPreferences() — 如果您需要按照您用第一个参数指定的名称识别的多个共享首选项文件,请使用此方法。 您可以从您的应用中的任何 Context 调用此方法。
  • getPreferences() — 如果您只需使用Activity的一个共享首选项,请从 Activity 中使用此方法。 因为此方法会检索属于该Activity的默认共享首选项文件,您无需提供名称。

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


注:如果你创建了带 MODE_WORLD_READABLE 或 MODE_WORLD_WRITEABLE 的sp文件,那么知道文件标识符的任何其他应用都可以访问您的数据。


在获得了sp对象之后,你就可以开始操作sp文件了,但是sp对象仅支持读取数据,写入数据需要通过Editor对象,因此要想写入数据你需要通过sp对象调用 edit() 来创建一个 SharedPreferences.Editor,使用Editor传递你想要使用诸如 putInt() 和 putString() 方法写入的键和值。然后调用 commit() 以保存更改。

例如:

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

说完写入数据,说一下如何从sp文件中读取数据,要从sp文件中读取数据,请调用诸如getInt() 和 getString() 等方法,为你想要的值提供键,并根据需要提供要在键不存在的情况下返回的默认值。

例如:

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 设备都有两个文件存储区域:“内部”和“外部”存储。这些名称在 Android 早期产生,当时大多数设备都提供内置的非易失性内存(内部存储),以及移动存储介质,比如微型 SD 卡(外部存储)。一些设备将永久性存储空间划分为“内部”和“外部”分区,即便没有移动存储介质,也始终有两个存储空间,并且无论外部存储设备是否可移动,API 的行为均一致。


  • 内部存储

  • 始终可用。
  • 默认情况下只有您的应用可以访问此处保存的文件。
  • 当用户卸载您的应用时,系统会从内部存储中删除您的应用的所有文件。

所以当你希望确保用户或其他应用均无法访问您的文件时,内部存储是最佳选择。


  • 外部存储

  • 它并非始终可用,因为用户可采用 USB 存储的形式装载外部存储,并在某些情况下会从设备中将其删除。
  • 它是全局可读的,因此此处保存的文件可能不受您控制地被读取。
  • 当用户卸载您的应用时,只有在您通过 getExternalFilesDir() 将您的应用的文件保存在目录中时,系统才会从此处删除您的应用的文件。

所以对于无需访问限制以及您希望与其他应用共享或允许用户使用电脑访问的文件,外部存储是最佳位置。

将文件存储到内部存储:

在内部存储保存文件时不需要任何权限,可以通过调用以下两种方法之一获取作为 File 的相应目录:

  • getFilesDir()
    返回表示您的应用的内部目录的 File, 例如:/data/data/com.kn.codebase/files。
  • getCacheDir()
    返回表示您的应用临时缓存文件的内部目录的 File,例如:/data/data/com.kn.codebase/cache 。 务必删除所有不再需要的文件并对在指定时间您使用的内存量实现合理大小限制,比如,1MB。 如果在系统即将耗尽存储,它会在不进行警告的情况下删除您的缓存文件。

注: 您的应用的内部存储设备目录由您的应用在Android 文件系统特定位置中的软件包名称指定。在技术上,如果您将文件模式设置为可读,另一个应用可以读取您的内部文件。 但是,另一个应用也需要知道您的应用的软件包名称和文件名。 其他应用无法浏览您的内部目录并且没有读写权限,除非您明确将文件设置为可读或可写。 只要您为内部存储上的文件使用 MODE_PRIVATE, 其他应用便从不会访问它们。


将文件存储到外部存储:

在外部存储中存储文件需要在清单文件中添加操作SD卡的权限

  • READ_EXTERNAL_STORAGE 读取权限
  • WRITE_EXTERNAL_STORAGE 写入权限,添加了写入权限之后会默认存在读取权限

由于外部存储可能不可用,比如:当用户已将存储装载到电脑或已移除提供外部存储的 SD 卡时,因此,在访问它之前,您应始终确认其容量。 您可以通过调用 getExternalStorageState() 查询外部存储状态。 如果返回的状态为 MEDIA_MOUNTED,那么您可以对您的文件进行读写。尽管外部存储可被用户和其他应用进行修改,但您可在此处保存两类文件:

  • 公共文件
    可供其他应用和用户自由使用的文件。 当用户卸载您的应用时,用户应仍可以使用这些文件。例如,您的应用拍摄的照片或其他已下载的文件。
    如果您要使用外部存储上的公共文件,请使用 getExternalStoragePublicDirectory() 方法获取表示外部存储上相应目录的 File (例如:/storage/sdcard0/Download)。该方法使用指定 您想要保存以便它们可以与其他公共文件在逻辑上组织在一起的文件类型的参数,比如 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;
    }
  • 私有文件
    如果您要保存您的应用专用文件,您可以通过调用 getExternalFilesDir() 获取相应的目录并向其传递指示您想要的目录类型的名称(例如:/storage/sdcard0/Android/data/com.kn.codebase/cache)。 通过这种方法创建的各个目录将添加至封装您的应用的所有外部存储文件的父目录,当用户卸载您的应用时,系统会删除这些文件。
    例如,您可以使用以下方法来创建个人相册的目录:
    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。这将返回外部存储上您的应用的专用目录的根目录。
    切记,getExternalFilesDir() 创建的目录在用户卸载应用时会被删除。如果您希望保存的文件在用户卸载您的应用后仍然可用(比如,您的应用是照相机并且用户要保留照片)您应改用 getExternalStoragePublicDirectory()。
    无论您对于共享的文件使用 getExternalStoragePublicDirectory() 还是对您的应用专用文件使用 getExternalFilesDir() ,您使用诸如 DIRECTORY_PICTURES 的 API 常数提供的目录名称非常重要。 这些目录名称可确保系统正确处理文件。 例如,保存在 DIRECTORY_RINGTONES 中的文件由系统介质扫描程序归类为铃声,而不是音乐。

注:当用户卸载您的应用时,Android 系统会删除以下各项:

  • 您保存在内部存储中的所有文件
  • 您使用 getExternalFilesDir() 保存在外部存储中的所有文件。
  • 但是,您应手动删除使用 getCacheDir() 定期创建的所有缓存文件并且定期删除不再需要的其他文件。

SQLite数据库:

主要用来存储重复或者结构化数据。Android为SQLite数据库提供了全面的API支持,你创建的任何数据库可以通过名称来访问应用中的任何类。
我们要使用SQLite数据库存储数据,必须先创建一个数据库,然后才能对数据进行操作,下面逐个介绍创建数据库,操作数据库的方法。

  • 创建数据库:
    官方建议创建数据库的方法是首先创建一个SQLiteOpenHelper的子类并重写其onCreate()方法,在onCreate()方法中执行sql语句。
例如:public class DictionaryOpenHelper extends SQLiteOpenHelper {    //数据库版本号    private static final int DATABASE_VERSION = 2;    //数据库名称    private static final String DATABASE_NAME="test";    //数据库表名    private static final String DICTIONARY_TABLE_NAME = "dictionary";    //创建数据库的Sql语句    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);    }     @Override    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {        //数据库版本更新    }}

在创建好SQLite辅助类之后,我们就可以利用辅助类的实例来创建数据库了,具体可以通过 getWritableDatabase() 和 getReadableDatabase()了两种方式来获取数据库的实例,

helper=new DictionaryOpenHelper (context);        SQLiteDatabase database = helper.getWritableDatabase();

获取到SQLiteDatabase 的实例之后就可以操作数据了。在对数据库进行增删改查操作时我们既可以选择调用db.execSQL(“SQL语句”)也可以通过API已封装的方法进行操作。至于第一种本文不再介绍,主要简单介绍一下调用API实现增删改查

  • 插入数据(增):数据库的插入操作提供了3中方法,分别是:
    database.insert(“表名”,null,values);
    参数:1.表名 2.空值字段名称(当values不为空时可置为null) 3.插入的键值对
    database.insertOrThrow(“表名”,null,values);
    该方法与第一种唯一的区别就是会直接把异常抛出。
    database.insertWithOnConflict(“表名”,null,values,1);
    该方法增加了处理插入冲突的算法,其实这三种方法最终都通过第三种方式实现,只是前两种第四个参数给了默认值。
    对于这四个参数,第一个和第三个都很好理解,第四个是冲突解决方案,顾名思义很容易理解,而第二个参数就有点疑惑了,这个参数到底有什么作用呢?当values参数为空或者里面没有内容的时候,我们insert是会失败的(底层数据库不允许插入一个空行),为了防止这种情况,我们要在这里指定一个列名,到时候如果发现将要插入的行为空行时,就会将你指定的这个列名的值设为null,然后再向数据库中插入。通过查看源码我们就明白了:
public long insertWithOnConflict(String table, String nullColumnHack,            ContentValues initialValues, int conflictAlgorithm) {        acquireReference();        try {            StringBuilder sql = new StringBuilder();            sql.append("INSERT");            sql.append(CONFLICT_VALUES[conflictAlgorithm]);            sql.append(" INTO ");            sql.append(table);            sql.append('(');            Object[] bindArgs = null;            int size = (initialValues != null && initialValues.size() > 0)                    ? initialValues.size() : 0;            if (size > 0) {                bindArgs = new Object[size];                int i = 0;                for (String colName : initialValues.keySet()) {                    sql.append((i > 0) ? "," : "");                    sql.append(colName);                    bindArgs[i++] = initialValues.get(colName);                }                sql.append(')');                sql.append(" VALUES (");                for (i = 0; i < size; i++) {                    sql.append((i > 0) ? ",?" : "?");                }            } else {                sql.append(nullColumnHack + ") VALUES (NULL");            }            sql.append(')');            SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);            try {                return statement.executeInsert();            } finally {                statement.close();            }        } finally {            releaseReference();        }    }

上面的源码中当我们的ContentValues类型的数据initialValues为null,或者size<=0时,就会再sql语句中添加nullColumnHack的设置。我们可以想象一下,如果我们不添加nullColumnHack的话,那么我们的sql语句最终的结果将会类似insert into tableName()values();这显然是不允许的。而如果我们添加上nullColumnHack呢,sql将会变成这样,insert into tableName (nullColumnHack)values(null);这样很显然就是可以的。


注:上面提到了插入时的冲突解决方案,在API中提供了0~5这几种:

  1. CONFLICT_NONE = 0
    Use the following when no conflict action is specified.
    规定没有冲突的动作时使用。
  2. CONFLICT_ROLLBACK = 1
    When a constraint violation occurs, an immediate ROLLBACK occurs, thus ending the current transaction, and the command aborts with a return code of SQLITE_CONSTRAINT. If no transaction is active (other than the implied transaction that is created on every command) then this algorithm works the same as ABORT.
    当限制冲突发生时,立即发生回滚,从而结束当前的事务,并命令SQLITE_CONSTRAINT的返回代码中止。如果没有事务是有效的(除了创建在每个命令的必需事务),那么这算法做法和ABORT一样。
  3. CONFLICT_ABORT = 2
    When a constraint violation occurs,no ROLLBACK is executed so changes from prior commands within the same transaction are preserved. This is the default behavior.
    当限制冲突发生时,不执行ROLLBACK所以在同一事务中从先前的命令更改将保留。这是默认的行为。
  4. CONFLICT_FAIL = 3
    When a constraint violation occurs, the command aborts with a return code SQLITE_CONSTRAINT. But any changes to the database that the command made prior to encountering the constraint violation are preserved and are not backed out.
    当限制冲突发生时,命令返回代码SQLITE_CONSTRAINT中止。但该命令之前遇到限制冲突得以保存任何更改数据库不备份出来。
  5. CONFLICT_IGNORE = 4
    When a constraint violation occurs, the one row that contains the constraint violation is not inserted or changed. But the command continues executing normally. Other rows before and after the row that contained the constraint violation continue to be inserted or updated normally. No error is returned.
    当限制冲突发生时,包含限制冲突的一行不会插入或更改。但命令继续正常执行。前后包含约束违反的行后,其他行继续正常插入或更新。不返回任何错误。
  6. CONFLICT_REPLACE = 5
    如果发生违反NOT NULL约束,NULL值被该列的默认值取代。如果列没有默认值,那么使用ABORT算法。如果则会发生CHECK约束违反IGNORE算法

  • 删除数据(删):
    database.delete(String table, String whereClause, String[] whereArgs);
    参数:1.表名 2.删除时的where语句,为null时删除整行 。例如:“_id=?”3.删除条件的数组,用来代替问号的。例如:new String[]{1,2};
  • 修改数据(改):修改数据时API中提供了两种方法:
    database.update(String table, ContentValues values, String whereClause, String[] whereArgs)
    参数:1.表名 2.键值对 3.需要修改的where条件 4.修改条件的数组,用来代替前面的问号
    database.updateWithOnConflict(String table, ContentValues values,
    String whereClause, String[] whereArgs, int conflictAlgorithm)
    该方法相比较于第一种方法多了解决冲突方案这个参数,这个参数的含义一插入数据的含义相同。第一种方法最终调用的是第二个方法,第四个参数默认为CONFLICT_NONE
  • 查询数据(查):查询方法提供的比较多,此处只介绍其中1个
    query(boolean distinct, String table, String[] columns,
    String selection, String[] selectionArgs, String groupBy,
    String having, String orderBy, String limit, CancellationSignal cancellationSignal)
    参数:
    1 distinct:是否合并,是:每行均唯一;否:….
    2 table:表名
    3 columns:要查询的列的数组
    4 selection:查询的where语句
    5 selectionArgs:where语句的查询条件,代替问号
    6 groupBy:分组列
    7 having:分组条件
    8 orderBy:排序列
    9 limit:分页查询限制
    10 cancellationSignal:信号取消操作正在进行中,如果没有则为null。如果操作被取消,那么{@link OperationCanceledException}将执行查询时抛出。(没用过)
    (CursorFactory cursorFactory,
    boolean distinct, String table, String[] columns,
    String selection, String[] selectionArgs, String groupBy,
    String having, String orderBy, String limit, CancellationSignal cancellationSignal)
0 0