pAdTy_-5 保存数据

来源:互联网 发布:微信公众号 java demo 编辑:程序博客网 时间:2024/05/24 03:51

2015.10.28-10.30
个人英文阅读练习笔记(低水准)。原文地址:http://developer.android.com/training/basics/data-storage/index.html。

2015.10.28
大多数的Android应用程序都需要保存数据,即使是为了保持用户使用进度也需要在onPause()中保存应用程序的状态信息。大多数非平凡(简单)的应用程序还需要保存用户设置,有的应用程序还必须管理文件及数据库中的信息。此笔记将介绍在android中保存数据的方法,包括:

  • 在共享文件中为简单的数据类型保存键-值对
  • 将任意的文件保存到android的文件系统中
  • 通过SQLite管理来使用数据库

2015.10.29

1. 保存键-值集

学习用共享文件以键-值对的方式保存小数量的信息。

如果您有一些相关的较小的键-值对要保存,您可以用SharePreferences APIs。一个SharePreferences 对象指向包含键-值的文件并提供方法来读或写它们。每个SharePreferences 文件被框架管理,它可以是私有的也可以是共享的。

此笔记记录怎么使用SharePreferences APIs来存储或检索简单的值。

注:SharePreferences APIs只是用来读或写键-值对的,不要将它与Preference APIs混淆,后者能够帮助您的应用程序设置构建用户接口(尽管它们使用了SharePreferences来实现的保存应用程序设置)。更多关于使用Preference APIs请参考Settings指导手册。

(1). 获取SharedPreferences的句柄

您可以通过调用以下其中一种方法来创建一个新的、或者访问一个已经存在的共享偏好文件(shared preference file):

  • getSharedPreferences() —- 如果您需要多个用名字(此函数的第一个参数用来指定文件的名字)来标识的共享偏好文件,就用这个函数。您可以在应用程序的任何Context中调用这个函数。
  • getPreferences() —- 如果您只需要在活动中使用一个共享偏好文件则调用这个方法。此方法会检索属于活动的默认的共享偏好文件,所以您不必提供文件名给此函数。

例如,以下代码在Fragment中执行。它访问了被资源字符串R.string.preference_file_key标识的文件,并以私有的方式打开此文件,这样就只有此应用程序能够访问此文件。

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

当为共享偏好文件命名时,您应该用一个唯一的标识符来标识您应用程序中的文件,例如
“com.example.myapp.PREFERENCE_FILE_KEY”。

可选的,如果您的活动只需要一个共享偏好文件,您可以使用getPreferences()方法。

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

警告:如果您使用了MODE_WORLD_READBLE或者MODE_WORLD_WRITEABLE来创建共享偏好文件,那么只要知道这些文件的标识符,其它的应用程序也可以访问这些文件。

(2). 写共享的文件(Shared Preferences)

往共享偏好文件中写数据时,需要在SharedPreferences上调用edit()方法来创建SharedPreferences.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();

(3).读共享文件(Shared Preferences)

从共享偏好文件中检索值时,调用诸如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);

2. 保存文件

学习保存基本文件,如保存通常会按照顺序读的一长序列的数据。

安卓使用的文件系统类似于其它平台上的磁盘文件系统。此笔记描述如何使用安卓的文件系统的File APIs来读写文件。

一个File对象适合用于读写数据量较大且是以按照顺序不随意挑转的从头到尾的读写的场合。例如,File对象适合读写图像文件或者其它网络文件。

此笔记记录如何在应用程序中执行基本的文件相关的任务。此笔记假设您熟悉Linux文件系统以及标准的java.io中的输入/输出APIs。

(1). 选择内部存储器还是外部存储器

所有的android设备都有两个文件存储区域:“内部”和“外部”存储器。这两个名字来自早期的安卓,那时大部分的安卓设备提供内置非易失内存(内部存储器),外加诸如micro SD卡的可移除存储器介质的存储器(外部存储器)。一些设备将永久存储器空间划分为“内部”和“外部”两部分,所以即使没有可移除存储器介质,设备也有两块存储空间,所以不管有没有可移除的存储介质,那些API的表现行为也会一样。以下总结了两种存储器特点。

内部存储器:
- 总是可用。
- 默认情况下,由您的应用程序保存在内部存储器的文件只有您的应用程序可以访问。
- 当卸载应用程序时,跟应用程序相关的保存在内部存储器中的文件也会被移除。
当您不想让用户或者其他应用程序访问您为应用程序创建的文件时,将文件保存在内部存储器中是最好的选择。

外部存储器:
- 不总是可用,因为用户可以用USB连接外部存储器到设备也可以从设备上移除该外部存储器。
- 它是共享的,其它可以读此外部存储器的设备都可以访问其中的文件。
- 当卸载应用程序时,系统只会移除通过getExternalFileDir()得到的目录下的文件。
当不需要限制文件的访问属性时,当要和其它应用程序共享文件时,当用户也可以访问文件时,将文件保存在外部存储器是一种最好的选择。

提示:尽管应用程序在默认时都是被安装到内部存储中,您也可以设置清单文件中的android:installLocation属性,这样您的应用程序就有可能被安装到外部存储器中。当应用程序(APK)很大、内部存储器又比较小时,用户会比较欣赏这样的设置。更多信息请参考App Install Location。

(2). 获取外外部存储器权限

往外部存储器中写时,您必须在清单文件中获取WRITE_EXTERNAL_STORAGE权限:

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

警告:目前,所有的应用程序不需要获取特殊权限就拥有读外部存储器的能力。然而,在将来发布的版本中会改变这个模式。如果应用程序是需要读外部存储器(不需要写它),您需要声明READ_EXTERNAL_STORAGE权限。为了确保应用程序按照期望执行,在那个改变到来之前(现在)就应该声明这个权限。

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

然而,如果应用程序使用了WRITE_EXTERNAL_STORAGE权限,那就暗示着应用程序同样有读外部存储的权限。

在内部存储器中保存文件,您不需要任何的权限。应用程序有读写内部存储器中文件的权限。

(3). 将文件保存到内部存储器

当保存文件到内部存储器中时,可以通过调用以下两个方法来像文件(File)那样获取适当的目录:
getFilesDir():返回一个File,代表应用程序内部存储器中的某个目录。
getCacheDir():返回一个File,代表应用程序暂时缓存文件的内部存储器的某个目录。要及时删除不再需要的文件,在任何给定的时间内也要为使用内存做一个合适的限制,如限制为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();}

或者,若需要缓存一些文件时,应该用createTemFile()方法。例如,以下方法从一个URL获取到文件名,并用这个名字在内部缓存目录下为应用程序创建了一个文件:

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

注:应用程序的内部存储目录在安卓文件系统的一个特殊位置,这个目录含应用程序的包名。从技术上讲,只要内部文件的模式是可读的,其它的应用程序可以访问内部文件。但是,其它的应用程序至少应该知道这些文件所对应应用程序的报名和其文件名。除非明确的设置了内部文件是可读或者可写的,否则其它文件根本不能浏览其它应用程序的内部目录。只要用MODE_PRIVATE设置了内部存储器中的文件,其它的应用程序时不能访问它们的。

(4). 将文件保存到外部存储器

因为外部存储器有可能不可用 —- 如用户将存储器链接到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;}

尽管外部存储器能够被用户和其它应用程序修改,但这里仍有两个类别的文件应该保存在这里:
公共文件(public files)
指对其它应用程序和用户公开的文件。当用户卸载了应用程序后,这些文件仍然能够被用户访问。
例如,被您的应用程序捕获的照片或者其它下载的文件。

私有文件(private files)
指的是应该属于您的应用程序以及当用户卸载应用程序时要删除的文件。因为这些文件都在外部存储器上,所以从技术上讲用户和其它的应用程序可以访问,但实际上它们并不为除了它们所属的应用程序用户提供数据值。当用户卸载应用时,系统会删除所有属于这个应用程序的在外部存储器上的私有目录。
例如,被您的应用程序下载的资源或者暂时的媒体文件。

想要保存公共文件到外部存储器中,要使用getExternalStoragePublicDirectory()方法来获取一个代表外部存储器上合适目录的File。这个方法带一个指定所需保存文件的类型,这样此文件和其它文件就能够被逻辑的组织,如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;}

如果要保存应用程序的私有文件,可以传递文件名给getExternalFileDir()方法来获取一个适当的在外部存储器上的目录。以这种方式创建的目录都被封装了其下的所有文件且被增加到父目录下,当用户卸载应用程序时可将这些目录一举卸载。

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

如果预定义子目录名没有适合您的应用程序的,您可以传递NULL给getExternalFileDir()。如此,此方法就会为返回在外部存储器上私有文件的根目录。

记住getExternalFilesDir()方法在目录下创建的子目录会在用户卸载程序时被删除。如果在程序卸载后您仍旧想保存某些文件—- 如照相机下的照片,那么您应该使用getExternalStoragePublicDirectory()。

无论是用getExternalStoragePublicDirectory()创建的共享文件还是用getExternalFilesDir()创建的私有文件,它们都是用API提供的诸如DIRECTORY_PICTURES名字。这些目录的名字能够被安卓系统识别。例如,DIRECTORY_RINGTONGS是系统用来组织媒体浏览铃声的。

(5). 查询自由空间

如果您提前知道了要存储数据的大小,您就可以通过调用getFreeSpace()或者getTotalSpace()函数来找到足够可用的空间而不会引起IOException。这些方法分别提供存储器当前的可用空间以及总空间。

然而,系统不会保证您能够写通过getFreeSpace()返回内存的大小的那么多内存。如返回的内存大小比要写的数据的大小小几MB,或者文件系统小于90%。否则,就不适合往存储器中写东西。

注:在保存文件之前不需要检查可用内存空间。可以通过try写文件,然后catch IOException。若您不知道数据到底有多大时,您可以使用这种方式。例如改变一个文件的编码:将PNG文件转换为JPEG文件,您可能并不知道这种转换需要多大尺寸改变。

(6). 删除文件

当文件不再需要时应及时将其删除。删除文件的最直接的方式是用已经被打开文件的delete()方法直接将其自身删掉。

myFile.delete();

对于保存在内部存储器上的文件,可以用Context的deleteFile()来定位且删掉文件。

注:当用户卸载应用程序时,安卓系统还会删除以下文件
- 所有保存在内部存储器上的文件。
- 所有用getExternalFilesDir()保存在外部存储器上的文件。
然而,您需要手动删除这些文件:基于基本步骤getCacheDir()创建的文件(对应着要用对应的步骤来删除不需要的文件)

2015.10.30

3. 在SQL数据库中保存数据

学习使用SQLite数据库来读写结构数据。

理论上,保存数据到数据库是为重复的或者结构化的数据而设计的,比如联系人信息。此笔记假设读者熟悉基本的SQL 数据库,并帮助读者在安卓平台上使用SQLite数据库。在安卓上操作数据库设计的APIs在android.database.sqlite包中。

(1) 定义模式和协议

SQL数据主要的原则之一就是模式:怎么组织数据库的正式声明。模式在创建数据库的SQL语句中体现。您可能会发现创建一个同伴类即协议类会非常有用,此类以系统和自记录的方式精确的指定模式的布局。

协议类中包含一些常量的定义,如URIs、表格以及列的名字。协议类中的常量可以被同一个包的其它所有类引用。这就使得在某处改变一个列名之后,所有引用此列名的代码都会受到影响。

组织协议类中的全局定义的一个好的方法是将它们保存在类的根级数据库中。再为每个表格创建一个可以列举表格列的内部类。

注:通过实现BaseColums接口,您的内部类能够继承一个原始的名为_ID的东西,_ID是诸如光标适配器类的安卓类系统拥有的一个东西。这不是必需的,但这能够让您和安卓框架更加和谐的相处。

例如,以下代码定义了一个表格和其列名:

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

(2) 用SQL帮手创建数据库

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

private static final String TEXT_TYPE = " TEXT";private static final String COMMA_SEP = ",";private static final String SQL_CREATE_ENTRIES =    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +    FeedEntry._ID + " INTEGER PRIMARY KEY," +    FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +    FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +    ... // Any other options for the CREATE command    " )";private static final String SQL_DELETE_ENTRIES =    "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;

就像保存文件到设备的内部存储器,安卓将数据库保存在与应用程序相关联的私有磁盘空间上。数据库中的数据时安全的,因为默认情况下保存数据库的区域是不能被其它应用程序所访问的。

在SQLiteOpenHelper类中有一套非常有用的APIs。当用此类去获取数据库的关联时,系统会执行创建或更新数据库的潜在的长期运行的操作(只在需要时且不在应用程序启动时)。您所需要做的就是调用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);    }    public void onCreate(SQLiteDatabase db) {        db.execSQL(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        db.execSQL(SQL_DELETE_ENTRIES);        onCreate(db);    }    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {        onUpgrade(db, oldVersion, newVersion);    }}

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

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

(3) 将数据放到数据库中

通过传递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_ENTRY_ID, id);values.put(FeedEntry.COLUMN_NAME_TITLE, title);values.put(FeedEntry.COLUMN_NAME_CONTENT, content);// Insert the new row, returning the primary key value of the new rowlong newRowId;newRowId = db.insert(         FeedEntry.TABLE_NAME,         FeedEntry.COLUMN_NAME_NULLABLE,         values);

insert()方法的第一个参数时表格名,第二个参数为列名。当ContentValues为空时往此列中写NULL(如果用“null”代替,当没有值时框架不会插入列)。

(4) 从数据库中读数据

用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_UPDATED,    ...    };// How you want the results sorted in the resulting CursorString sortOrder =    FeedEntry.COLUMN_NAME_UPDATED + " DESC";Cursor c = 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中的移动方法,在开始读数据之前必须要要调用移动方法。通常来讲,最开始需要调用moveToFirst(),次方法将读位置置于第一个入口。对于每一行,通过调用Cursor的获取方法(如getString()或getLong())可以读到任意一列。对于每个获取数据的方法,必须将要读取的每一列的索引位置传递给它们,可以通过getColumnIndex()或getColumnIndexOrThrow()方法来获取列索引。例如:

cursor.moveToFirst();long itemId = cursor.getLong(    cursor.getColumnIndexOrThrow(FeedEntry._ID));

(5) 删除数据库中的数据

欲删除表格中的某行,需要提供标识行的选择准则。数据库API提供了创建保户SQL注入(injection)的选择准则的机制。此机制将选择划分为选择条目和选择参数。条目定义了查看列的方式,且允许联合列测试。参数是跟条目关联的值。因为结果没有被规则的SQL语句操作,所以它对SQL耦合是免疫的。

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

(6) 更新数据库

当需要修改数据库中的某部分值时,使用update()方法来完成。

在有数据插入(insert())和删除数据(delete())后更新表格:

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

[2015.11.11-09:47]

0 0
原创粉丝点击