作为一个完整的应用程序,数据的存储与操作是必不可少的。Android系统为我们提供了4种数据存储方式,分别是:SharedPreference、SQlite、ContentProvider和文件。
(1)SharedPreference存储:一种常用的数据存储方法,其本质就是基于xml文件存储键值对(key-value)数据,通常用来存储一些简单的配置信息。
(2)SQLite存储:一个轻量级的数据库,支持基本SQL语法,是Android系统中常被采用的一种数据存储方式。Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一些操作数据库的API。
(3)ContentProvider存储:它是Android系统中能实现应用程序之间数据共享的一种存储方式。由于Android系统中的数据基本都是私有的,存放于"data/data/程序包名(package name)"目录下,所以要实现数据共享,正确的方式是使用ContentProvider。由于数据在各应用间通常是私密的,虽然此存储方式较少使用,但又是必不可少的一种存储方式。如果应用程序有数据需要共享时,就需要使用ContentProvider为这些数据定义一个URI(包装成Uri对象),其他的应用程序就通过ContentProvider传入这个URI来对数据进行操作。
(4)文件存储:即常说的文件存储方式,常用于存储数量较大的数据,但缺点是更新数据是一件困难的事情。
1.SharedPreference存储
SharedPreference将数据以键值对(key-value)的形式保存至xml文件中,而生成的xml文件保存于"data/data/程序包名(package name)/shared_prefs"目录下。SharedPreference使用非常简单,能够轻松地存放数据和读取数据,但只能保存基本数据类型的值。
@SuppressLint("WrongConstant") SharedPreferences sharedPreferences = getSharedPreferences("type",Context.MODE_APPEND);
SharedPreferences.Editor editor = sharedPreferences.edit();
//String字符类型
editor.putString("String","words");
//Boolean布尔型
editor.putBoolean("Boolean",true);
//Integer整型
editor.putInt("Integer",1);
//长整型
editor.putLong("Long",100000);
//Float浮点数型
editor.putFloat("Float",3.15f);
editor.commit();
生成的SharedPreference文件名为type.xml,保存在应用程序文件夹下的shared_prefs文件内,程序包名为"com.XXX"。
<map>
<float name="Float" value = "3.5"/>
<Long name="Long" value = "100000"/>
<boolean name="Boolean" value = "true"/>
<String name="String" >words</String>
<int name="Integer" value = "1"/>
</map>
SharedPreferences对象本身只能获取数据,而不支持存储和修改。通过SharedPreference中的edit()方法可以获得响应的Editor对象,由Editor对象完成SharedPreferences中数据的存储和修改。
1.使用SharedPreferences保存键值对(key-value)
(1)使用Activity类的getSharedPreferences(String name,int mode)方法获得SharedPreferences对象,其中存储键值对(key-value)的文件的名称由getSharedPreferences方法的name指定,打开方式由mode指定。
(2)使用SharedPreferences的edit()方法获得SharedPreferences.Editor对象
(3)通过SharedPreferences.Editor的putXxx(String key,Xxx value)方法写入键值对(key-value)。
(4)通过SharedPreferences.Editor的commit()方法提交并保存键值对(key-value)
2.使用SharedPreferences读取键值对(key-value)
(1)使用Activity类的getSharedPreferences(String name,int mode)方法获得所要读取的SharedPreferences对象,其方法与上述保存键值对(key-value)的第(1)步相同
(2)使用SharedPreferences接口的getXxx(String key,Xxx value)方法,可以方便地获得对应键(key)的值(value)
2.SQLite存储
SQLite是一种轻量级数据库系统,以嵌入式操作系统为设计目标,占用的资源低,因此常被作为手机操作系统的本地数据库。它还是开源的,任何人可以使用。
2.1SQLite简介
SQLite由以下几个组件组成:SQL编译器、内核、后端以及附件。SQLite利用虚拟机和虚拟数据库引擎(VDBE),使调试、修改和扩展SQLite的内核变得更加方便。
2.2SQLite使用
它创建一个表时,可以在CREATETABLE语句中指定某列的数据类型,具体有以下5种类型的列。
(1)TEXT:使用NULL、TEXT或者BLOB存储任何插入此列的数据,如果数据是数字类型,则会转换为TEXT类型。
(2)NUMERIC:可以使用任何存储类型,它首先视图将插入的数据转换为REAL或INTEGER类型,如果成功则存储为REAL和INTEGER类型,否则不加改变地存入。
(3)INTEGER:和NUMERIC类型类似,只是它将可以转换为INTEGER类型的值都转换为INTEGER类,如果是REAL类型,且没有小数部分,也转换为INTEGER类型。
(4)REAL:和NUMERIC类型类似,只是它将可以转换为REAL和INTEGER类型的值都转换为REAL类型。
(5)NONE:不做任何改变的尝试。
实际上,SQLite内部仅有下列5种存储值的类型。
(1)NULL:空值
(2)INTEGER:整型,根据大小可以使用1、2、3、4、6、8位来存储
(3)REAL:浮点数
(4)TEXT:字符串
(5)BLOB:二进制大数据
当某个值插入数据库时,SQLite将检查这个值的类型。如果该值的类型与关联列的类型不匹配,SQLite就尝试将该值的类型转换成该列的类型;如果不能转换,就将该值作为其本身具有的类型存储。
在Android中,SQLite的使用涉及两个重要的类:SQLiteDatabase和SQLiteOpenHelper。
1.SQLiteOpenHelper类
SQLiteOpenHelper是SQLite的数据库辅助类,且是一个抽象类,用来管理数据库的创建和版本的管理,使用时必须实现它的onCreate(SQLiteDatabase db)方法和onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)方法。SQLiteOpenHelper的常用方法如下。
DatabaseHelper继承自SQLiteOpenHelper类,重写了onCreate()与onUpgrade()方法,并将这两个方法作为TableCreateInterface接口方法开放,供各个数据表进行实现。onCreate(SQLiteDatabase db)方法用于各个表的创建,具体表的创建由TableCreateInterface接口实现,在各自的onCreate方法中进行创建。onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)方法用于各个表的更新,具体表的更新由TableCreateInterface接口实现,在各自的onUpgrade方法中进行更新。注意,只有在真正操作数据库,如调用getReadableDatabase()或者getWritableDatabase()方法时,数据库才会被创建。创建的数据库文件位于"data/data/<package name>/databases/<数据库名>.db"。
2.SQLiteDatabase类
SQLiteDatabase类作为SQLite的数据库实体类,用于对数据库进行增删改查等操作,常用方法有insert()、delete()、update()、query()等。
方法名称 | 含义 |
public long insert(String table,String nullColumnHack,ContentValues values) | table指定要插入数据表的名称,values类似一个map通过键值对的形式存储,其中的键必须与表中的字段名相同。当插入空行时,我们要在这里指定一个列名nullColumnHack,如果发现将要插入的行为空行时,就会将指定的这个列名的值设为null,然后再向数据库中插入。 |
public int delete(String table,String whereClause,String[] whereArgs) | 用于删除表中的一条记录。table为要删除的表名,whereClause指定要根据哪个列字段参数进行删除,whereArgs是删除的具体依据参数。 |
public int update(String table,ContentValues values,String whereClause,String[] whereArgs) | 用于修改数据表中的一条数据。table指定要删除的数据表,whereClause指定根据哪列字段参数进行删除,whereArgs是删除的具体依据参数。values用来保存需要修改的值 |
public Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy,String limit) | 用于查询数据表中的信息,获得指向对应数据的游标。table指定要删除数据的表的名称;columns指定需要查询的列,selection与selectionArgs参数的用法同wgereClause和whereArgs的用法相同;groupBy与having是与合计函数(Aggregate Functions),orderBy指定查询数据顺序,可以以其中一个字段的升降序进行查询,如以"ID"字段的降序排列,则orderBy为"ID DESC",而ASC代表升序;limit则指定限制查询数据的个数。 |
3.ContentProvider存储
Android平台中,ContentProvider是在不同应用程序之间实现数据共享的唯一机制。如果需要让别的应用程序能够操作一个应用程序自己的数据,就可采用这种机制。比如,我们操作手机里面的联系人、多媒体等一些信息,都可以用ContentProvider来实现。
一个程序可以通过一个ContentProvider的抽象接口将自己的数据完全暴露出去。ContentProvider是类似数据库中表的方式将数据暴露,就像一个“数据库”。外界获取其提供的数据,与从数据库中获取数据的操作基本一样,这时就用到另一个类(ContentResolver),并通过Uri来操作数据从而实现对数据的处理。
Uri是一个通用资源标识符,被分为A、B、C、D4个部分,如图
(1)A:无法改变的标准前缀,如"content://"和"tel://"等。当前缀是"content://"时,说明可通过ContentProvider控制这些数据。
(2)B:Uri的标识,它通过authorities属性声明,用于限制是哪个ContentProvider能够有权提供这些数据。对于第三方应用程序,为了保证Uri标识的唯一性,它必须是一个完整的类名。
(3)C:路径,可以近似理解为需要操作的数据库中表的名字。如"content://com.androidbook.client.contentprovider.DataProvider/Topic"中的Topic。
(4)D:如果Uri中包含表示需要获取的记录的ID,就返回该ID对于的数据;如果没有ID,就表示返回全部。
因为Uri代表了要操作的数据,所以经常需要解析,并从中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris。
1.UriMatcher的常用方法
UriMatcher的常用方法如下表。
方法名称 | 含义 |
public void addURI(String authority,String path,int code) | 往UriMatcher类里添加一个拼凑的Uri,在此我们可以将UriMatcher理解为一个Uri的容器,这个容器包含着即将操作的Uri,它用于我们业务逻辑的处理。path相当于Uri中的路径,也就是想要操作的数据表名。如果通过下面的match()方法匹配成功,就返回code值。 |
public int match(Uri uri) | 与传入的Uri匹配,它会首先找到之前通过addURI方法添加的Uri匹配,如果匹配成功,就返回之前设置的code值,否则返回一个值为-1的常量UriMatcher.NO_MATCH |
//定义两个Uri匹配的返回值code
private static final int topic = 1;
private static final int privateLetter = 2;
//定义AUTHORITY
public static String AUTHORITY = "com.androidbook.client.contentprovider.DataProvider";
...
//当没有匹配成功是,返回NO_MATCH的值
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//匹配Topic表的Uri,匹配成功后返回topic整数值
uriMatcher.addURI(AUTHORITY,"Topic",topic);
//匹配PrivateLetter表的Uri,匹配成功后返回privateLetter整数值
uriMatcher.addURI(AUTHORITY,"PrivateLetter",privateLetter);
//使用match方法
Uri uri = Uri.parse("content://com.androidbook.client.contentprovider.DataProvider/Topic");
//进行匹配,获得返回的code值,可以根据code值判别并进行不同的操作
int code = uriMatcher.match(uri);
2.ContentUris的常用方法
方法名称 | 含义 |
public static Uri withAppendedId(Uri contentUri,long id) | 用于为路径加上ID |
public static long parseId(Uri contentUri) | 从路径中获取ID |
//首先定义一个Uri
Uri uri = Uri.parse("Content://com.example.demo_for_exercise.contentprovider.DataProvider/Topic");
//为Uri添加ID
Uri newUri = ContentUris.withAppendedId(uri,3);
//从newUri中提取出ID,若为空,返回-1
long id = ContentUris.parseId(newUri);
3.ContentProvider的常用方法
方法名称 | 含义 |
public abstract boolean onCreate() | 在ContentProvider创建后被调用 |
public abstract Uri insert(Uri uri,Content Values values) | 根据Uri插入values对应的数据 |
public abstract int delete(Uri uri,String selection,String[] selectionArgs) | 根据Uri删除selection指定条件匹配的全部记录 |
public abstract int update(Uri uri,ContentValues values,String selection,String[] selectionArgs) | 根据Uri修改selection指定条件匹配的全部记录 |
public abstract Cursor query(Uri uri,ContentValues values,String selection,String[] selectionArgs,String sortOder) | 根据Uri查询出selection指定条件匹配的全部记录,并可以指定查询哪些列,并以什么方式(sortOder)排序 |
public abstract String getType(Uri uri) | 返回当前Uri所指向数据的MIME类型(Multipurpose Internet Mail Extensions多用途互联网邮件扩展类型,即指定用什么东西打开该文件),MIME类型由自己定义。 |
那么具体该如何公开数据并且在外部对其操作呢?
(1)在当前应用程序中定义一个ContentProvider类,继承ContentProvider并重写它的几个抽象方法
(2)首先要定义UriMatcher类,分别加入对应的Uri以及要返回的code值,以便另一个程序进行调用时对Uri进行判别,并通过匹配不同的Uri来分别操作不同的表。
(3)需要在当前应用程序的AndroidManifest.xml文件中注册ContentProvider。android:authorities属性定义了是哪个ContentProvider提供这些数据,格式为:provider所在的包的名称+provider本身定义的名称。而android:name指定具体的ContentProvider类
<provider
android:authorities="com.example.demo_for_exercise.contentprovider.DataProvider"
android:name="com.example.demo_for_exercise.contentprovider.DataProvider">
</provider>
(4)其他应用程序通过ContentResolver和Uri来获取ContentProvider的数据。
4.文件存储
之前我们介绍了Android利用SharedPreferences与SQLite进行数据存储。使用SharedPreferences存储的内容是一些键值对(key-value),使用SQLite数据库来操作存储的数据表。如果我们要存储的是一些文件,就可以采用文件存储的方式。
文件存储可以分为两类。
(1)将文件存储在应用程序内。在Android系统中,这些文件保存在"data/data/<packagename>/files/"目录下,称为文件存储。
(2)将文件存储在外接的存储设备中,也就是存储在SDcard存储卡中,称为SDCard存储。
- 首先我们研究第一种文件存储→File存储。Android中读取/写入文件的方法与java中的I/O是一样的,利用openFileInput()方法与FileInputStream对象来读取设备上的文件,并利用openFileOutput()方法与FileOutputStream对象来创建文件。但是在File存储状态下,不同的程序之间不能共享。以上两个方法只支持保存、读取该应用程序目录下的文件。创建的文件保存在"/data/data/<package name>/files"目录下,读取非自身目录下的文件将会抛出FileNotFoundException异常。
方法名称 | 含义 |
openFileOutput(String name,int mode) | 保存文件内容,打开指定的私有文件输出流,返回类型FileOutputStream。name为要打开的文件名,不能包含路径分隔符。mode为操作模式,有以下4种保存模式。 (1)Environment.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问。在该模式下写入的内容会覆盖源文件的内容。 (2)Environment.MODE_APPEND:检查文件是否存在,存在就追加内容,否则就创建新文件 (3)Environment.MODE_WORLD_READABLE:表示当前文件可以被其他应用读取 (4)Environment.MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。 在使用模式时,可以用“+”来选择多种模式,比如openFileOutput(FileName,Environment.MODE_PRIVATE+Environment.MODE_WORLD_READABLE) |
openFileInput(String name) | 读取文件内容,打开指定的私有文件输出流,返回类型为FileInputStream。name为要打开的文件名,不能包含路径分隔符。File存储所创建的文件只能被创建该文件的应用访问,如果希望文件能被其他应用读和写,可以在创建文件时,指定Environment.MODE_WORLD_READABLE和Environment.MODE_WORLD_WRITEABLE |
deleteFile(String name) | 删除指定的文件,返回值类型为boolean。name为要删除的文件名,不能包含路径。 |
getDir(String name,int mode) | 在应用程序的数据文件下获取或创建name对应的子目录,返回值类型为File |
getFilesDir() | 得到该应用程序数据文件夹的绝对路径,返回值类型为File |
fileList() | 得到该应用程序数据文件夹下的全部文件的文件名,返回值类型为String[] |
//File存储,创建并保存文件
public void saveFileInApplication(){
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = this.openFileOutput("file.txt", Context.MODE_APPEND);
fileOutputStream.write("保存文件到应用程序下".getBytes());
fileOutputStream.close();
Toast.makeText(MainActivity.this,"保存文件成功",Toast.LENGTH_SHORT).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,"保存文件失败",Toast.LENGTH_SHORT).show();
} catch (IOException e) {
Toast.makeText(MainActivity.this,"保存文件失败",Toast.LENGTH_SHORT).show();
}
}
//File文件存储--读取文件
public void readFileInApplication(){
FileInputStream fileInputStream = null;
try {
fileInputStream = this.openFileInput("file.txt");
byte[] buffer = new byte[1024];
ByteArrayInputStream stream = null;
int length = -1;
while((length=fileInputStream.read(buffer))!= -1){
stream = new ByteArrayInputStream(buffer);
}
stream.close();
fileInputStream.close();
textView.setText(stream.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
- 然后我们来学习SDCard存储。Android也为我们提供了一些关于SDCard的相关方法,这些方法方便开发者进行操作。Environment这个类为我们提供了操作的便利。
方法名称 | 含义 |
getDataDirectory() | 获得Android下的data文件夹的目录,返回值类型为File |
getDownloadCacheDirectiory() | 获得AndroidDownload/Cache内容的目录,返回值类型为File |
getExternalStorageDirectory() | 获得Android外部存储器也就是SDCard的目录,返回值类型为File |
getExternalStorageState() | 获得Android外部存储器的当前状态,返回值类型为String,有以下9中保存模式。 (1)Environment.MEDIA_BAD_REMOVAL:在没有正确卸载SDCard之前就移除。 (2) (3) (4) (5) (6) (7) (8) (9) |
getRootDirectoty() | 获取Anroid下的root文件夹的目录,返回值类型为File |
以上5个方法都是静态方法,可以直接用Environment的类名调用。
下面通过详细的步骤来说明对SDCard的读取操作。
(1)要在AndroidManifest.xml文件中添加权限
<!-- 在SDCard中创建与删除文件权限-->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard写入数据权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
(2)用Environment.getExternalStorageStata().equals(Environment.MEDIA_MOUNTED)来判断这台手机设备上是否有SDCard且具有读写SDCard的权限。
(3)调用Environment.getExternalStorageDirectory()获得外部存储器的目录
(4)使用I/O流对外部存储器File类进行文件的读、写等操作。
具体操作可以参考:https://blog.csdn.net/yoryky/article/details/78675373