Android 开发-----数据存储
数据一般有以下保存方式实现:
- SharedPreferences
- 采用java.io.* 库所提供的I/O 接口,读写文件。
- SQLite 数据库
- ContentProvider
一.SharedPreferences
SharedPreferences 是一种轻量级的数据保存方式,比较类似于我们常用的ini文件,用来保存运用程序的一些属性设置,较简单的参数设置。SharedPreferences将NVP(Name/Value Pair,键值对)保存在Android的文件系统,为XML文件。SharedPreferences 将对文件系统的操作过程封装起来,开发人员仅通过SharedPreferences 的API 即可完成读写过程。
1.三种数据访问模式
- 私有(MODE_PRIVATE):仅创建程序可读写。
- 全局读(MODE_WORLD_READABLE): 创建程序可读写,其他程序可读不可写。
- 全局写(MODE_WORLD_WRITEABLE):创建程序可读写,其他程序都可写不可读。
注意:根据官网文档,后两种全局模式在API 17 之后已被弃用(deprecated),强烈不推荐使用SharedPreferences 来实现数据共享。开发者应该选择ContentProvider, 广播和服务等数据共享方式。Android N 中使用这两种模式会抛出异常。
2.SharedPreferences的使用
a.获取SharedPreferences
- getSharedPreferences() — 如果需要多个通过名称参数来区分的shared preference文件, 名称可以通过第一个参数来指定。可在app中通过任何一个Context 执行该方法。
Context context = getActivity(); SharedPreferences sharedPref = context.getSharedPreferences( "SharedPreferences_file_name", Context.MODE_PRIVATE);
- getPreferences() — 当activity仅需要一个shared preference文件时。因为该方法会检索activity下默认的shared preference文件,并不需要提供文件名称。
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
b.写SharedPreferences
执行写操作需要同edit()创建一个SharedPreferences.Editor,然后通过类似putInt和putString的方法传递key-value,接着调用Editor 对象的commit() 或apply() 方法保存修改内容。两者的区别在于:commit() 函数会立即将修改结果同步地写入文件中。而apply() 则采用异步的方式写入文件。在主线程(UI线程)中,建议使用apply() 而非commit() 以防阻塞。
支持数据类型:boolean, float, int, long, String 和Set<String>.
SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean("RunInBackGround", true); editor.putFloat("Height", 163.5f); editor.putInt("Age", 20); editor.putLong("TimeStamp", 1476931843L); editor.putString("Name", "Tom"); Set<String> tags = new HashSet<>(); tags.add("Android"); editor.putStringSet("Tags", tags); editor.apply();
c.读SharedPreferences
为了从shared preference中读取数据,可以通过类似于 getInt() 及 getString()等方法来读取。在那些方法里面传递我们想要获取的value对应的key,并提供一个默认的value作为查找的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), default);
二.文件存储
所有的Android设备均有两个文件存储区域:"internal" 与 "external" 。 这两个名称来自于早先的Android系统,当时大多设备都内置了不可变的内存(internal storage)及一个类似于SD card(external storage)这样的可卸载的存储部件。之后有一些设备将"internal" 与 "external" 都做成了不可卸载的内置存储,虽然如此,但是这一整块还是从逻辑上有被划为"internal"与"external"的。只是现在不再以是否可卸载进行区分了。 下面列出了两者的区别:
-
Internal storage:
- 总是可用的
- 这里的文件默认只能被我们的app所访问。
- 当用户卸载app的时候,系统会把internal内该app相关的文件都清除干净。
- Internal是我们在想确保不被用户与其他app所访问的最佳存储区域。
-
External storage:
- 并不总是可用的,因为用户有时会通过USB存储模式挂载外部存储器,当取下挂载的这部分后,就无法对其进行访问了。
- 是大家都可以访问的,因此保存在这里的文件可能被其他程序访问。
- 当用户卸载我们的app时,系统仅仅会删除external根目录(getExternalFilesDir())下的相关文件。
- External是在不需要严格的访问权限并且希望这些文件能够被其他app所共享或者是允许用户通过电脑访问时的最佳存储区域。
- 无论是否支持SD 卡等外置存储设备,所有Android 手机都将存储空间划分为「内部存储」与「外部存储」两部分。
一般默认安装到internal storage的,可以通过在manifest文件中声明android:installLocation来指定安装到external storage中。
1.Internal storage:
Android 系统允许应用程序创建仅能够自身访问的私有文件,文件保存在设备的内部存储器上,路径为:/data/data/<package name>/files/。
Android系统支持四种文件操作模式:
- MODE_PRIVATE私有模式,文件仅能够被文件创建程序访问,或具有相同UID的程序访问。
- MODE_APPEND追加模式,如果文件已经存在,则在文件的结尾处添加新数据。
- MODE_WORLD_READABLE全局读模式,允许任何程序读取私有文件。
- MODE_WORLD_WRITEABLE全局写模式,允许任何程序写入私有文件
获取目录:
- getFilesDir() : 返回一个File,代表了我们app的internal目录。
- getCacheDir() : 返回一个File,代表了我们app的internal缓存目录。请确保这个目录下的文件能够在一旦不再需要的时候马上被删除,并对其大小进行合理限制,例如1MB 。系统的内部存储空间不够时,会自行选择删除缓存文件。
可以使用File构造器在目录下创建新的文件,如下:
File file = new File(context.getFilesDir(), filename);
获取/新建文件:
openFileOutput():
用于写入数据,如果指定文件不存在,则创建一个新的文件
函数签名:public FileOutputStreamopenFileOutput(String name, intmode),返回FileOuputStream对象
Stringfilename ="myfile"; Stringstring="Helloworld!"; FileOutputStreamoutputStream; try{ outputStream=openFileOutput(filename,Context.MODE_PRIVATE); outputStream.write(string.getBytes()); outputStream.close(); }catch(Exceptione){ e.printStackTrace(); }
openFileInput():
用于打开一个与应用程序关联的私有文件输入流
函数签名:public FileInputStreamopenFileInput(String name)
当指定文件名对应的文件不存在时,会抛出FileNotFound异常
FileInputStreamfileInputStream; final String FILE_NAME = "file.txt", try { fileInputStream= openFileInput(FILE_NAME);
byte[] contents = new byte[fileInputStream.available()];
fileInputStream.read(contents);
} catch (IOExceptionex) {
Log.e("TAG", "Fail to read file.");
}
如果需要缓存一些文件,可以使用createTempFile()。例如:下面的方法从URL中抽取了一个文件名,然后再在程序的internal缓存目录下创建了一个以这个文件名命名的文件。
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; }
2.external storage:
获取External存储的权限:
要往外部存储控件中写入文件,必须在AndroidManifest.xml 中声明权限。
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
检查External存储是否可用:
因为外部存储可能是不可用的,比如遇到SD卡被拔出等情况时。因此在访问之前应对其可用性进行检查。我们可以通过执行 getExternalStorageState()来查询external storage的状态。若返回状态为MEDIA_MOUNTED, 则为可用。
/* 检查外部存储可读写*/ public boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false; } /* 检查外部存储至少可读*/ 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 :这些文件对与用户与其他app来说是public的,当用户卸载我们的app时,这些文件应该保留。例如,那些被我们的app拍摄的图片或者下载的文件。
- Private files: 这些文件完全被我们的app所私有,它们应该在app被卸载时删除。尽管由于存储在external storage,那些文件从技术上而言可以被用户与其他app所访问,但实际上那些文件对于其他app没有任何意义。因此,当用户卸载我们的app时,系统会删除其下的private目录。例如,那些被我们的app下载的缓存文件。
想文件已public形式保存在外部存储中,getExternalStoragePublicDirectory(String))来获取目录,其中的String 参数指的是目录类型,可以为DIRECTORY_MUSIC, DIRECTORY_PICTURES 等。若无指定类型,可以传入null,函数则会返回files 目录本身。
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; }
想文件已private形式保存在外部存储中,getExternalFilesDir
()来获取目录。
检查剩余空间:
可以通过执行getFreeSpace() or getTotalSpace() 来判断是否有足够的空间来保存文件,从而避免发生异常。
删除文件:
myFile.delete();
如果文件是保存在internal storage,我们可以通过Context
来访问并通过执行deleteFile()
进行删除
myContext.deleteFile(fileName);
Note:
当用户卸载我们的app时,android系统会删除以下文件:
- 所有保存到internal storage的文件。
- 所有使用getExternalFilesDir()方式保存在external storage的文件。
通常来说,我们应该手动删除所有通过 getCacheDir() 方式创建的缓存文件,以及那些不会再用到的文件。
3.资源文件:
如何读取原始格式文件:
- 首先调用getResource() 函数获得资源对象
- 然后通过调用资源对象的openRawResource() 函数,以二进制流的形式打开文件
- 在读取文件结束后,调用close()函数关闭文件流
InputStreaminput = this.getResources().openRawResource(R.raw.filename); byte[] reader = new byte[inputStream.available()]; while (inputStream.read(reader) != -1) {} txt_text.setText(new String(reader,“utf-8”));//获得Context资源 input.close();//关闭输入流
读取XML格式文件:
首先通过调用资源对象的getXml() 函数,获取到XML解析器XmlPullParser。
XmlPullParser parser = resources.getXml(R.xml.toys); //通过资源对象的getXml()函数获取到XML解析器 while (parser.next() != XmlPullParser.END_DOCUMENT) { String toys = parser.getName(); // getName()函数获得元素的名称 ...... }
获取元素个数:
int count = parser.getAttributeCount();
获得属性名和属性值:
String attrName= parser.getAttributeName(i); / /获得属性名 String attrValue= parser.getAttributeValue(i); // 获得属性值
XmlPullParser的XML事件类型:
- START_TAG:读取到标签开始标志
- TEXT:读取文本内容
- END_TAG:读取到标签结束标志
- END_DOCUMENT:文档末尾
读取文件过程中遇到END_DOCUMENT 时停止分析
三.SQLite数据库
定义Schema与Contract:
SQL中一个重要的概念是schema:一种DB结构的正式声明,用于表示database的组成结构。schema是从创建DB的SQL语句中生成的。我们会发现创建一个伴随类(companion class)是很有益的,这个类称为合约类(contract class),它用一种系统化并且自动生成文档的方式,显示指定了schema样式。
Contract Clsss是一些常量的容器。它定义了例如URIs,表名,列名等。这个contract类允许在同一个包下与其他类使用同样的常量。 它让我们只需要在一个地方修改列名,然后这个列名就可以自动传递给整个code。
组织contract类的一个好方法是在类的根层级定义一些全局变量,然后为每一个table来创建内部类。
Note:通过实现 BaseColumns 的接口,内部类可以继承到一个名为_ID的主键,这个对于Android里面的一些类似cursor adaptor类是很有必要的。这么做不是必须的,但这样能够使得我们的DB与Android的framework能够很好的相容。
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"; ... } }
更具定义好的结构,然后定义对应的创建,增删改查等语句。
private static final String TEXT_TYPE = " TEXT"; private static final String COMMA_SEP = ","; private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" + FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," + FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + FeedReaderContract.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 " + TABLE_NAME_ENTRIES;
使用SQL Helper创建DB:
在SQLiteOpenHelper 类中有一些很有用的APIs。当使用这个类来做一些与db有关的操作时,系统会对那些有可能比较耗时的操作(例如创建与更新等)在真正需要的时候才去执行,而不是在app刚启动的时候就去做那些动作。我们所需要做的仅仅是执行getWritableDatabase()或者getReadableDatabase().
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());
然后通过getWritableDatabase()或
getReadableDatabase()来获取DB对象:
SQLiteDatabase db = mDbHelper.getWritableDatabase();
添加数据:
我们利用ContentValues对象来添加数据:
首先构造一个ContentValues对象,然后调用ContentValues对象的put()方法,将每个属性的值写入到ContentValues对象中,最后使用SQLiteDatabase对象的insert()函数,将ContentValues对象中的数据写入指定的数据库表中。
SQLiteDatabase db = mDbHelper.getWritableDatabase(); // Create a new map of values, where column names are the keys ContentValues values = new ContentValues(); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content); // Insert the new row, returning the primary key value of the new row long newRowId;//insert()函数的返回值是新数据插入的位置,即ID值。 newRowId = db.insert( FeedReaderContract.FeedEntry.TABLE_NAME, FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE, values);
put方法的参数是一个键值对,第1个参数是名称(列名称),第2个参数是值.
insert()函数中,第1个参数是数据表的名称,第2个参数是在NULL时的替换数据,第3个参数是需要向数据库添加的数据。
修改数据:
更新数据使用到 update() 方法,其也是通过ContentValues对象实现。
update(String table,ContentValuesvalues,String whereClause,String[] whereArgs);
- table是表名;
- values是要更新的数据;
- whereClause:满足whereClause子句的记录将会被更新;
whereArgs:为whereClause子句传入的参数
删除数据:
使用delete方法delete(String table,String whereClause,String[] whereArgs);
- table是表名
- whereClause指的是删除的条件
- whereArgs用于为whereClause子句传入参数
查询数据:
从DB中查询数据使用到query()方法,查询结构返回一个 Cursor 对象。
query()方法:Cursor android.database.sqlite.SQLiteDatabase.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
Cursor函数说明:
例如:
Cursor cursor = mSQLiteDatabase.query(TABLE_NAME, new String[] { TABLE_ID, TABLE_NUM, TABLE_DATA }, null, null, null, null, null);
要查询在cursor中的行,使用cursor的其中一个move方法,但必须在读取值之前调用。一般来说应该先调用moveToFirst()
函数,将读取位置置于结果集最开始的位置。对每一行,我们可以使用cursor的其中一个get方法如getString()
或getLong()
获取列的值。对于每一个get方法必须传递想要获取的列的索引位置(index position),索引位置可以通过调用getColumnIndex()
或getColumnIndexOrThrow()
获得。
cursor.moveToFirst(); long itemId = cursor.getLong( cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID) );