【Android training】保存数据

来源:互联网 发布:宁化老鼠干 知乎 编辑:程序博客网 时间:2024/06/07 03:23

原文链接:https://developer.android.google.cn/training/basics/data-storage/index.html (部分翻译加一些buchong)

一:保存键值集

         如果您希望保存的键值相对较小,则应使用SharedPreferencesAPI。 SharedPreferences对象指向包含键值对的文件,并提供读取和写入它们的简单方法。每个SharedPreferences文件由框架管理,可以是私有的或共享的。

注意:android7.0给SharedPreferences设置Context.MODE_WORLD_READABLE或Context.MODE_WORLD_WRITEABLE,会触发SecurityException异常,已经不能跨应用访问。

 

获取SharedPreference的句柄。

你可以使用如下两种方法来创建一个preference文件。

方法一:getSharedPreferences()如果你需要使用名字定义多个preference文件,你可以使用这个方法。第一个参数为文件名,第二个参数为访问模式。

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

方法二:getPreferences() 如果你只需要一个preference文件,则可以使用这个方法,这个发方法提供一个属于当前Activity的preference文件,不需要再提供名字。

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


向Preference写值

向一个preference中写值,你需要使用你的SharedPreference对象调用 edit方法获取一个SharedPreference.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();


读取Preference

调用 getInt 和 getString()方法,提供你想要获得的key值,并且指定一个默认返回值。

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

preference文件路径为:data/data/<your package name>/shared_prefs


二:保存文件

选择内部或者外部存储

所有Android设备都有两个文件存储区:“内部”和“外部”存储。 这些名字来自Android的早期阶段,大多数设备提供内置的非易失性存储器(内部存储器)以及可移动存储介质(如micro SD卡(外部存储)))。一些设备将永久存储空间划分为“内部”和“外部”分区,因此即使没有可移动存储介质,总是有两个存储空间,而且外部存储器是否可移动,API行为是相同的。以下列表总结了有关每个存储空间的特点。

internal storage

External storage

总是可用的

不总是可以使用,因为它可能被移除。

只有你的应用程序可以访问这里保存的文件。

这里是 world-readable,保存在这里的文件会不受控制的被外部读取。

当卸载你的应用程序时,Android系统会移除内部存储中所有你的app的文件。

当你卸载你的应用程序的时候,系统只会移除你使用getExternalFilesDir()方法保存的文件。

如果你不想其他应用程序访问你的文件,使用内部存储是最好的。

如果你想和其他应用共享文件或者在电脑上访问这些文件,那么使用外部存储是最好的。

注意:在Android 7.0(API级别24)之前,可以通过放宽文件系统权限使其他应用程序可以访问内部文件。7.0后不再是这样。如果您希望使私人文件的内容可以被其他应用程序访问,您的应用程序可能需要使用FileProvider。

提示:默认情况下,应用程序安装在内部存储上,您可以在AndroidManifest中指定android:installLocation属性,以便将应用程序安装在外部存储上。当APK的大小非常大,并且它们的外部存储空间大于内部存储空间时,用户倾向于使用这个选项。

 

获得外部存储读写权限

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

保存文件在内部存储的时候你不需要任何权限,你的应用程序有权限访问它的内部存储目录。

 

保存文件到内部存储

将文件保存到内部存储器时,可以通过调用以下两种方法之一获取相应的目录作为文件:

 

getFilesDir()  返回一个表示程序内部目录的文件。

文件路径为: /data/user/0/包名/files/

getCacheDir() 

返回代表应用程序的临时缓存文件的内部目录的文件。 确保在不再需要时删除每个文件,并对在任何给定时间(如1MB)使用的内存量实施合理的大小限制。如果系统开始运行,在存储空间不足时,则可能会在没有警告的情况下删除缓存文件。

文件路径为: /data/user/0/包名/cache/

 

在这些文件目录下创建一个新的文件,你可以使用File()构造方法,使用上面两个方法之一传递内部存储文件目录,例:

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

或者,您可以调用openFileOutput()获取写入内部目录中的文件的FileOutputStream。例如,以下是将文本写入文件的方法:

写入的路径为: /data/user/0/包名/files

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中提取文件名,并在应用程序的内部缓存目录中创建一个名称为该名称的文件:

文件路径为:/data/user/0/包名/cache/

public File getTempFile(Context context, String url) {    File file;    try {        String fileName = Uri.parse(url).getLastPathSegment();        /*参数1:前缀          参数2: 后缀          参数3:在指定目录下创建*/        file = File.createTempFile(fileName, null, context.getCacheDir());    } catch (IOException e) {        // Error while creating file    }    return file;}

保存文件在外部存储

因为外部存储可能不可用,例如当用户将存储器安装到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;}

虽然外部存储可由用户和其他应用修改,但您可以在这里保存两种类型的文件:

公共文件:

提供给其他应用程序和用户的文件。 当用户卸载您的应用程序时,这些文件应该仍然可供用户使用。

例如,您的应用程序或其他下载的文件捕获的照片。

 

私有文件:

文件属于您的应用程序,并应在用户卸载您的应用程序时被删除。 虽然这些文件在技术上可由用户和其他应用程序访问,因为它们在外部存储上,但它们是实际上不为应用程序外的用户提供价值的文件。当用户卸载您的应用程序时,系统将删除应用程序的外部专用目录中的所有文件。

例如,您的应用程序或临时媒体文件下载的其他资源。

 

如果要将公共文件保存在外部存储器上,请使用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;}

如果要保存对您的应用程序是私有的文件,您可以通过调用getExternalFilesDir()并传递一个名称来指示您想要的目录的类型来获取相应的目录。以这种方式创建的每个目录都被添加到父目录中,该目录封装了您的所有应用程序的外部存储文件,当用户卸载您的应用程序时系统会将其删除。

例如,这里有一种方法可用于为单个相册创建一个目录:

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(),请务必使用API​​常量(如DIRECTORY_PICTURES)提供的目录名称。这些目录名称确保文件被系统正确处理。例如,保存在DIRECTORY_RINGTONES中的文件被系统媒体扫描器分类为铃声而不是音乐

 

查询可用空间

如果您提前知道要保存的数据量,可以通过调用getFreeSpace()或getTotalSpace()来确定是否有足够的空间可用而不会导致IOException。这些方法分别提供存储卷中的当前可用空间和总空间。此信息也可用于避免将存储空间填充到某一阈值以上。

 

但是,系统不保证您可以编写与getFreeSpace()指示的字节数一样多的字节。 如果返回的数字比您要保存的数据大小几MB,或者如果文件系统小于90%已满,则可能进行安全。否则,你可能不应该写入存储。

 

删除文件

您应该始终删除不再需要的文件。 删除文件最简单的方式是打开文件引用调用delete();

myFile.delete();

如果文件保存在内部存储器上,还可以通过调用deleteFile()来使用Context定位和删除文件:

myContext.deleteFile(fileName);

注意:当用户卸载您的应用程序时,Android系统将删除以下内容:

保存在内部存储上的所有文件

使用getExternalFilesDir()保存在外部存储上的所有文件。

但是,您应该定期手动删除使用getCacheDir()创建的所有缓存文件,并定期删除不再需要的其他文件。

 

保存数据到数据库

将数据保存到数据库是重复或结构化数据(如联系信息)的理想选择。Android上使用数据库的API可以在android.database.sqlite包中使用。

 

定义模式和合同

SQL数据库的主要原则之一是模式:数据库如何组织的正式声明。 该模式反映在用于创建数据库的SQL语句中。您可能会发现创建一个称为合同类的协同类,它以系统和自我记录的方式显式指定了模式的布局。

 

合同类是用于定义URI,表和列的名称的常量的容器。 合同类允许您在同一个包中的所有其他类中使用相同的常量。 这可以让您在一个地方更改列名称,并在整个代码中传播。

 

组织合同类的一个好方法是将整个数据库的全局定义放在类的根级别中。 然后为枚举其列的每个表创建一个内部类。

 

注意:通过实现BaseColumns接口,您的内部类可以继承一个称为_ID的主键字段,一些Android类(如光标适配器)将期望它具有。这不是必需的,但这可以帮助您的数据库与Android框架协调一致。

 

例如,此代码段定义单个表的表名称和列名称:

public final class FeedReaderContract {    // To prevent someone from accidentally instantiating the contract class,    // make the constructor private.    private FeedReaderContract() {}    /* Inner class that defines the table contents */    public static class FeedEntry implements BaseColumns {        public static final String TABLE_NAME = "entry";        public static final String COLUMN_NAME_TITLE = "title";        public static final String COLUMN_NAME_SUBTITLE = "subtitle";    }}

使用SQL Helper 创建数据库

一旦定义了数据库的外观,您应该实现创建和维护数据库和表的方法。 以下是创建和删除表的一些典型语句

private static final String SQL_CREATE_ENTRIES =    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +    FeedEntry._ID + " INTEGER PRIMARY KEY," +    FeedEntry.COLUMN_NAME_TITLE + " TEXT," +    FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)";private static final String SQL_DELETE_ENTRIES =    "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;

就像您保存在设备内部存储上的文件一样,Android将数据库存储在相关应用程序的专用磁盘空间中。 您的数据是安全的,因为默认情况下,其他应用程序无法访问该区域。

 

SQLiteOpenHelper类中提供了一组有用的API。 当您使用此类来获取对数据库的引用时,系统会在需要时执行潜在的长时间运行的操作,而不是在应用程序启动期间创建和更新数据库。所有你需要做的是调用getWritableDatabase()或getReadableDatabase()。

 

注意:由于它们可以长时间运行,请确保在后台线程中调用getWritableDatabase()或getReadableDatabase(),例如使用AsyncTask或IntentService。

 

要使用SQLiteOpenHelper,创建一个覆盖onCreate(),onUpgrade()和onOpen()回调方法的子类。 您可能还需要实现onDowngrade(),但不是必需的。

例如,以下是SQLiteOpenHelper的实现:

public class FeedReaderDbHelper extends SQLiteOpenHelper {    // If you change the database schema, you must increment the database version.    public static final int DATABASE_VERSION = 1;    public static final String DATABASE_NAME = "FeedReader.db";    public FeedReaderDbHelper(Context context) {        super(context, DATABASE_NAME, null, DATABASE_VERSION);    }    /*onCreate 方法会在调用getReadableDatabase或者WriteableDatabase时并且数据库不存在时才会被调用,    数据库如果已经存在则不会调用。*/    public void onCreate(SQLiteDatabase db) {        db.execSQL(FeedReaderContract.SQL_CREATE_ENTRIES);    }    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {        // This database is only a cache for online data, so its upgrade policy is        // to simply to discard the data and start over        /*比较粗暴的方法,删除数据表然后调用oncreate重新创建,不建议使用*/        db.execSQL(FeedReaderContract.SQL_DELETE_ENTRIES);        onCreate(db);        /*         * 升级数据库的最佳实践,每一个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前的版本         * 号的时候,就会进入到onUpgrade()方法中去执行更新操作。这里需要为每一个版本号赋予它各自改变的内容,然后在         * onUpgrade 方法中执行更新操作         *         */        /*注意一个细节,这里我们没有写break,为了跨版本升级时都可以执行*/        /*switch (oldVersion) {            case 1:                //执行版本1的数据库操作                db.execSQL(sql);            case 2:                //执行版本2的数据库操作                db.execSQL(sql);            default:        }*/    }    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {        onUpgrade(db, oldVersion, newVersion);    }}

要访问您的数据库,请实例化您的子类SQLiteOpenHelper:

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

向数据库存入信息

通过将ContentValues对象传递给insert()方法将数据插入到数据库中:

// Gets the data repository in write modeSQLiteDatabase db = mDbHelper.getWritableDatabase();// Create a new map of values, where column names are the keysContentValues values = new ContentValues();values.put(FeedEntry.COLUMN_NAME_TITLE, title);values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);// Insert the new row, returning the primary key value of the new rowlong newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);

insert()的第一个参数只是表名。

第二个参数告诉框架在ContentValues为空的情况下(即,您没有放置任何值)会做什么。 如果指定列的名称,框架将插入一行并将该列的值设置为null。如果指定null,就像在此代码示例中一样,当没有值时,框架不会插入一行。

 

读取数据库信息

要从数据库中读取,请使用query()方法,传递您的选择条件和所需的列。 该方法组合了insert()和update()的元素,除了列列表定义要获取的数据,而不是要插入的数据。查询的结果将在Cursor对象中返回给您。

SQLiteDatabase db = mDbHelper.getReadableDatabase();// Define a projection that specifies which columns from the database// you will actually use after this query.String[] projection = {    FeedEntry._ID,    FeedEntry.COLUMN_NAME_TITLE,    FeedEntry.COLUMN_NAME_SUBTITLE    };// Filter results WHERE "title" = 'My Title'String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";String[] selectionArgs = { "My Title" };// How you want the results sorted in the resulting CursorString sortOrder =    FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";Cursor cursor = db.query(    FeedEntry.TABLE_NAME,                     // The table to query    projection,                               // The columns to return    selection,                                // The columns for the WHERE clause    selectionArgs,                            // The values for the WHERE clause    null,                                     // don't group the rows    null,                                     // don't filter by row groups    sortOrder                                 // The sort order    );

要查看光标中的一行,请使用其中一个Cursor移动方法,您必须始终在开始读取值之前调用该方法。 由于光标从位置-1开始,所以调用moveToNext()将“读取位置”放在结果的第一个条目上,并返回光标是否已经超过结果集中的最后一个条目。对于每一行,您可以通过调用一个Cursor get方法来读取列的值,例如getString()或getLong()。对于每个get方法,您必须传递所需列的索引位置,您可以通过调用getColumnIndex()或getColumnIndexOrThrow()获取该索引位置。在完成迭代结果之后,调用光标上的close()来释放其资源。例如

List itemIds = new ArrayList<>();while(cursor.moveToNext()) {  long itemId = cursor.getLong(      cursor.getColumnIndexOrThrow(FeedEntry._ID));  itemIds.add(itemId);}cursor.close();

删除数据库信息

要从表中删除行,您需要提供标识行的选择条件。 数据库API提供了一种创建可以防止SQL注入的选择标准的机制。 该机制将选择规范分为选择条款和选择参数。 该子句定义要查看的列,还允许您组合列测试。 参数是要绑定到子句中的值进行测试的值。 因为结果不像常规SQL语句那样处理,所以它不受SQL注入的影响

// Define 'where' part of query.String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";// Specify arguments in placeholder order.String[] selectionArgs = { "MyTitle" };// Issue SQL statement.db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs);

更新一个数据库

当您需要修改数据库值的一个子集时,请使用update()方法。

 更新表将insert()的内容值语法与delete()的语法相结合。

SQLiteDatabase db = mDbHelper.getWritableDatabase();// New value for one columnContentValues values = new ContentValues();values.put(FeedEntry.COLUMN_NAME_TITLE, title);// Which row to update, based on the titleString selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";String[] selectionArgs = { "MyTitle" };int count = db.update(    FeedReaderDbHelper.FeedEntry.TABLE_NAME,    values,    selection,    selectionArgs);

数据库事务操作

SQLiteDatabase db = fdHelper.getWritableDatabase();        db.beginTransaction();//开启事务        try{            if(true){                //手动抛出一个异常,让事务失败,注释掉这一句则事务执行成功                      throw new NullPointerException();            }            ContentValues values = new ContentValues();            values.put(FeedEntry.COLUMN_NAME_TITLE, title.getText().toString());            values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle.getText().toString());            db.insert(FeedEntry.TABLE_NAME, null, values);            db.setTransactionSuccessful(); //事务执行成功        }catch(Exception e){            e.printStackTrace();        }finally{            db.endTransaction();//结束事务        }

持久数据库连接

由于getWritableDatabase()和getReadableDatabase()在数据库关闭时调用成本高昂,您应该将数据库连接打开,只要您可能需要访问它即可。通常,关闭调用Activity的onDestroy()中的数据库是最佳的。

@Overrideprotected void onDestroy() {    mDbHelper.close();    super.onDestroy();}

使用SQL语句操作数据库,我们可以直接使用SQL语句来操作数据库,如下:

SQLiteDatabase db = openOrCreateDatabase(DdName, MODE_PRIVATE, null); db.execSQL( sql)db.rawQuery(sql);


Demo 地址
















原创粉丝点击