数据存储
数据存储简介和文件存储
Android常见的三类四种数据存储方式︰
Internal Storage (内部存储)
External Storage (外部存储)
使用SharedPreferences存储数据
文件存储数据
SQLite数据库存储数据
Android提供的几种存储方式简单介绍如下∶
SharedPreference:提供了一套完整的框架用来存储基本类型数据的键值对,例如︰Long . int、float、String等。
Internal Storage :手机的内部存储设备是以文件的形式保存数据的,因此只能在本程序使用,外部的应用程序无法访问该文件,同时文件数据也是临时性的,当卸载本程序时,该文件也会自动被删除。
External Storage :手机的外接存储设备也是以文件的形式存储数据,可以存储数据量较大的公共数据。外接的存储设备可以是移动式的(比如SD卡),应该注意的是,对于保存在外接设备的数据是不安全的,因为该设备中的数据对外部公开,用户可以进行读写操作,甚至删除数据。
SQLite Database : SQLite是标准的关系型数据库,用户可以使用标准的SQL语句进行数据的增删改查,该数据库具有小巧、独立性高、隔离性好、安全性高、跨平台和多语言接口等优点,它被内置在Android系统中,用于存储一些较复杂类型的数据,且系统中比较大的数据一般也都是用SQLite数据库存储(这也是Google工程师为什么在Android中内置该数据库的原因)。
内部存储
内部存储路径中的文件是分类存储的,无法干涉,除了cache目录,其它目录系统不会自动创建
除了files目录,别的目录几乎都是无法手动操作的
其它app几乎无法访问内部存储中的数据,除了用非法手段或者主动暴露
内部存储目录下的文件夹及文件会随着app的卸载而被系统自动删除
Build.VERSION_CODES.LOLLIPOP及以上版本新增的API
Build.VERSION_CODES.N及以上版本新增的API
getDir( “test", MODE_PRIVATE) /data/data/0/包名/app_test
getNoBackupFilesDir() /data/data/0/包名/no_backup
getCodeCacheDir() /data/data/0/包名/code_cache
getDataDir() /data/data
通过Environment
通过上下文Context
Environment.getDataDirectory() /data
Environment.getRootDirectory() /system
Environment.getDownloadCacheDirectory() /cache
getCacheDir() /data/data/0/包名/cache
getFilesDir() /data/data/0/包名/files
获取内部存储路径和API对应关系
获取内部存储路径和API对应关系
特点
外部存储
私有目录不需要访问权限
Android在外部存储空间中也提供了特殊目录供app存放私有文件,该路径为︰/storage/emulated/0/Android/data/包名/((注意:应用安装之后此路径是没有对应的应用文件夹的,需要手动调用对应的API创建)
获取私有目录路径
getObbDir() /storage/emulated/0/Android/obb/com.xxx.autorepair
getExternalCacheDir() /storage/emulated/0/Android/data/com.xxx.autorepair/cache
getExternalFilesDir(null) /storage/emulated/0/Android/data/com.xxx.autorepair/files
私有目录
在API级别29中不赞成使用此方法。为了提高用户隐私,不建议直接访问共享/外部存储设备。当应用定位到时Build.VERSION_CODES.Q,此方法返回的路径不再可供应用直接访问。应用程序可以继续访问内容通过迁移到替代品,如存储在共享/外部存储Context#getExternalFilesDir(String),MediaStore或Intent#ACTION_OPEN_DOCUMENT。
为什么需要分区存储
乱占空间
随意读取用户的数据
随意读取应用的数据
私有存储(Private Storage):每个应用在内部存储中都拥有自己的私有目录 "/data/data/packageName”,其它应用看不到,彼此也无法访问到该目录
共享存储(Shared Storage):除了私有存储以外,其它的一切都被认定是共享 存储,如︰媒体集(Media Collection)和SD卡外部应用存储目录
Android中存储可以分为两大类∶私有存储和共享存储
如此宽泛的存储权限,这就使得Android中某些应用程序大量的存在如下问题∶
Android 10中的分区存储的出现,就是为了解决上述的问题,即主要就是为了限制应用程序过于宽泛的存储权限!
Android10中分区存储设计遵循的三个原则
更好的文件属性:系统应用知道什么文件属于哪一个app,让用户更加容易管理他们自己的文件。当app 被卸载了,被应用创建的内容,除非用户希望保留,否则不应该保留下来。
用户的数据安全:当用户下载文件,比如敏感的电子邮件附件,这些文件对大多数应用程序都不应该可见。
应用的数据安全∶当app将特定于应用程序的文件写入外部存储时,其他应用程序不应该可见这些文件。
Android10中分区存储设计的七个注意事项
应用访问自己的应用目录不受限制(包括内部和外部)无需任何权限
应用向媒体集和下载目录提供文件,如果您要想保存图片、视频、音频、文档,无需任何权限
不再提供宽泛的共享存储(Share Storage ),读写存储权限只能访问提供的媒体集(图片集、视频集、音频集)
位置元数据限制,获取图片上的位置等信息需要请求权限,如果不请求权限,读取图片的信息的时候,位置元数据将会被删除
读取PDF或其他类型的文件,需要调用系统的文件选择器(Storage Access Framerwork API )
在媒体集或应用目录之外,写任何文件都需要系统的文件选择器,这样用户能选择并确认将文件存在哪里
requestLegacyExternalStorage开关值,在清单文件配置,如果开启了,存储权限就会像之前版本中的Android一样运作
具体分区存储权限的介绍
应用外部特定目录中的文件(使用getExternalFilesDir()访问)。
应用自己创建的照片、视频和音频(通过MediaStore访问)。
默认情况下,对于targetSdkVersion大于等于29的应用,其访问权限范围限定为分区存储。此应用无需请求与存储相关的用户权限,即可以查看外部存储中以下类型的文件:
分区存储将影响在Android10系统首次安装启动、且targetSdkVersion>=29的应用。需要访问和共享外部存储文件的应用会受到影响,需要进行兼容性适配。
影响范围
如果应用最先安装在Android 10以下的系统,
然后系统通过Fota升级到Android 10
应用通过更新升级到targetSdkVersion >= 29
targetSdkVersion <= 28,不受影响。
如果targetSdkVersion >= 29,默认情况应用外部存储可见性将被过滤,应用需要对分区存储进行适配。
在Android 10上运行的应用∶
还有值得注意的是以下两种情况比较特殊,不会受到分区存储的影响︰
分区存储权限体现在以下五个方面:
媒体文件需要媒体目录进行创建,不能在图片目录创建音频文件,如果您想访问由其他应用创建的媒体文件,您就要请求外部存储读取权限。如果您没有获得媒体位置权限,就仍然会在读取由其他应用创建的媒体文件时,被系统拿掉位置信息。
非媒体文件必须在 Downloads下创建,所有Dowonloads目录下也只能创建像PDF 或其它的非媒体文件
读取由其他应用创建的非媒体文件,也将需要Storage Access Framerwork API
所有的应用都尽量继续使用MediaStore,因为后台文件路径 ”/sdcard/DCIM/xxx.JPG”请求或使用文件路径"/sdcard/DCIM/xxx.JPG"进行I/O流请求都是代理给MediaStore类。
不建议直接使用媒体文件访问路径“/sdcard/DCIM/xxx.JPG]”,应用的性能会略有下降。
可以在应用获取那些其他应用创建添加的媒体文件的时候,直接删除这些元数据
如果想要获取图片的位置信息,需要声明请求权限 ACCESS_MEDIA_LOCATION,用户在Settings UI里看不到这个权限,但是它属于运行时权限,所以必须要在Manifest里声明该权限,并在运行时同时请求该权限和读取外部存储权限
MediaStore.setRequireOriginal ()∶获取当前磁盘的体积容量
下载文件集,和媒体文件集一样,无需请求任何权限就能在这个集中添加、编辑、删除非媒体文件。
与媒体文件不同的是,即使有读取外部存储权限,也是不允许访问由其他应用创建添加的非媒体文件。想要取得权限,需要通过调用Storage Access FramerworkAPI,启动系统文件选择器,让用户可以进行选择可以访问哪些文件和目录。
如果用户允许应用访问一个文件,那么这个权限将是完整的权限,应用无需其他额外的任何权限,就可以任意的读取、编辑删除媒体文件和非媒体文件。
如上做法,使得用户就能获得完整的控制权,更好的管理应用在何时访问敏感的非媒体文件
Android 10中,自己的app无需任何权限就能向媒体集添加文件,同时也可以编辑和删除自己添加的媒体文件。
如果要读取并操作并非自已的应用所创建的媒体文件,就需要读取外部存储权限,如果用户没有同意,则应用将无法编辑和删除并非自己的应用所创建的媒体文件。
以上做法使得应用想编辑和删除媒体文件时,用户就能获得完整的控制权
媒体文件集∶用于和其它应用分享媒体文件(图片、音频、视频文件)
下载文件集︰用于和其它应用分享非媒体文件(非图片、非音频、非视频文件)
限制位置元数据∶对媒体文件中敏感的元数据进行了访问权限的限制,主要限制位置元数据
媒体文件访问路径:Android 10锁定了公共目录文件路径的权限。
MediaStore的强制性:文件需要在适当的目录进行创建
Andriod10中的分区存储小结
特定于应用的目录->无需权限->访问方法getExternalFilesDir () ->卸载应用时移除文件
媒体集合(照片、视频、音频)->需要权限READ_EXTERNAL_STORAGE(仅当访问其他应用的文件时)->访问方法MediaStore ->卸载应用时不移除文件
下载内容(文档和电子书籍)->无需权限->存储访问框架(加载系统的文件选择器)->卸载应用时不移除文件
I/O流操作文件
FileInputStream openFileInput(String name)
FileOutputStream openFileOuput(String name,int mode)
其中参数mode用于指定输出流的模式
Android支持通过I/O流方式来访问移动设备中的存储文件
Context对象提供了openFileInput()和openFileOuput()方法分别来得文件的输入流和输出流︰
读写SD卡文件
SD卡 ( Secure Digital Memory Card )是一种基于半导体快闪记忆器的多功能存储卡,具有大容量、高性能、安全高等多种特点,被广泛地用于便携式移动设备,例如手机、数码相机、PDA等。SD卡极大地扩充了手机的存储能力。
使用Environment.getExternalStorageState()方法判断是否插入SD卡,且应用程序具有读写SD卡的权限
使用Environment.getExternalStorageDirectory()方法获取SD卡的目录
使用文件输入流(FileInputStream、FileReader )或输出流(FileOutputStream、FileWriter )来读写SD卡中的文件
读写SD卡文件时,需要以下步骤︰
Android权限动态申请
Google在Android 6.0开始引入了权限申请机制,将所有权限分成了正常权限和危险权限。应用的相关功能每次在使用危险权限时需要动态的申请并得到用户的授权才能使用。
正常权限︰不会直接给用户隐私权带来风险。如果你的应用在其清单中列出了正常权限,系统将自动授予该权限。
危险权限︰会授予应用访问用户机密数据的权限。如果你的应用在其清单中列出了正常权限,系统将自动授予该权限。如果你列出了危险权限,则用户必须明确批准您的应用使用这些权限。
Android系统权限分为两类∶
重要方法:
ContextCompat.checkSelfPermission:检查应用是否具有某个危险权限。如果应用具有此权限,方法将返回PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回PackageManager.PERMISSION_DENIED,且应用必须明确向用户要求权限。
ActivityCompat.requestPermissions :应用可以通过这个方法动态申请权限,调用后会弹出一个对话框提示用户授权所申请的权限。
ActivityCompat.shouldShowRequestPermissionRationale :如果应用之前请求过此权限但用户拒绝了请求,此方法将返回true。如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了Don't ask again选项,此方法将返回false。如果设备规范禁止应用具有该权限,此方法也会返回false。
onRequestPermissionsResult :当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的onRequestPermissionsResult()方法,向其传递用户响应,处理对应的场景。
Android XML数据解析
Android中常见的三种XML解析方法︰
DOM DOM是基于树形结构的的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树、检索所需数据。分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息。
SAX SAX(Simple API for XML)解析器是一种基于事件的解析器,它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理。
PULL PULL解析器的运行方式和SAX类似,都是基于事件的模式。不同的是,在PULL解析过程中,我们需要自己获取产生的事件然后做相应的操作,而不像SAX那样由处理器触发一种事件的方法,执行我们的代码。
三种XML解析方法的优缺点及适用场景∶
DOM 由于DOM在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。
SAX SAX解析器的优点是解析速度快,占用内存少。非常适合在Android移动设备中使用。
PULL PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种XML时也是用PULL解析器。
Pull解析器提供的事件类型总共有5种,分别如下:
START_DOCUMENT 文档开始
START_TAG 开始元素
TEXT 文本
END_TAG 结束元素
END_DOCUMENT 文档结束
使用PulI解析器XmIPullParser解析XML文档时,常用方法∶
int getAttributeCount(); 获取当前元素的属性个数
String getAttributeValue(int index); 获取属性值
int getEventType); 获取事件类型
string getName(); 用于START_TAG和END_TAG事件中,获取当前元素的名字
int next(); 处理下一个元素
int nextText(); 用于START_TAG事件中,获得下一个TEXT类型的元素
可以通过以下两个步骤创建一个Pull解析器∶
通过调用XmIPullParserFactory工厂类的newInstance()方法,创建一个Pull解析器工厂对象。
通过调用Pull解析器工厂对象的newPullParser()方法,创建一个Pull解析器对象。
Pull解析器对象创建之后,可以通过其setInput()方法传入要解析的XML文档。setInput()方法提供了两种重载的形式:
void setInput(Reader in);
void setInput(InputStream inputStream, String inputEncoding);
SharePreferences接口
SharedPreferences能够保存简单格式的数据,主要用于类似配置信息格式的数据,这些数据以key-value形式存储在XML文件中。
使用SharedPreferences方式存储数据时需要用到SharedPreferences和SharedPreferences.Editor接口
SharedPreferences常用方法
Context对象的getSharedPreferences(String name,int mode)方法获取SharedPreference对象参数mode用于设定文件的操作模式,取值可以是三种︰
Context.MODE_WORLD_READABLE(可读)
Context.MODE_WORLD_WRITEABLE(可写)
Context.MODE_PRIVATE(私有)
使用SharedPreferences进行数据操作时,操作步骤︰
使用getSharedPreferences()方法获取一个SharedPreferences实例对象
使用SharedPreferences实例对象的edit()方法,获取SharedPreferences.Editor编辑对象
使用SharedPreferences.Editorr编辑对象的putXxx()方法来保存数据
使用SharedPreferences.Editor编辑对象的commit()方法将数据提交到XML文件中
使用SharedPreferences对象的getXxx()方法来读取数据
SQLite数据库
sQLite是一种免费、开源的轻量级数据库,Android系统集成了sQLite
sQLite数据库具有以下几个特征:
轻量级
独立
操作简单
便于管理和维护
可移植性
语言无关
事务性
sQLiteDatabase对象打开数据库的方法∶
openDatabase(String path, SQLiteDatabase.CursorFactory factory, intflags)∶打开path所指定的SQLite数据库
openOrCreateDatabase(String path,SQLiteDatabase.CursorFactoryfactory)∶打开或创建(如果文件不存在) path所指定的SQLite数据库
openOrCreateDatabase(File file,SQLiteDatabase.CursorFactoryfactory)∶打开或创建(如果文件不存在) file所指定的SQLite数据库
创建或打开数据库
path用于指定数据库的路径,若指定的数据库不存在,则抛出FileNotFoundException异常
factory用于构造查询时的游标,若factory为null,则表示使用默认的factory构造游标
flags指定了数据库打开的模式,SQLite定义了4种数据库打开模式:
OPEN_READONLY(只读)
OPEN_READWRITE(可读可写)
CREATE_IF_NECESSARY(若数据库不存在先创建数据库)
NO_LOCALIZED_COLLATORS(不按照本地化语言对数据进行排序)
使用openDatabase()方法打开指定的数据库时,需要三个参数∶
使用openDatabase()方法打开指定的数据库
sQLiteDatabase sqliteDatabase = sQLiteDatabase.openDatabase ( "qst_student. db", null,NO_LOCALIZED_COLLATORS);
使用openOrCreateDatabase()方法打开或创建指定的数据库
sQLiteDatabase sqliteDatabase = sQLiteDatabase.openorCreateDatabase ("qst_student.db", null);
使用deleteDatabase()方法删除数据库
deleteDatabase ("qst_student.db" );//删除数据库qst_student. db
使用close()方法关闭数据库
sqliteDatabase.close();//关闭数据库,sqliteDatabase是一个实例对象
使用execSQL()方法创建表
//创建表的sQL语句
string sql="CREATE TABLE student(ID INTEGER PRIMARY KEY,age INTEGER,name TEXT) ";
//执行该sQL语句创建表
sqliteDatabase.execSQL ( sql) ;
使用execSQL()方法删除表
l//创建表的sQL语句
string sql= "CREATETABLE student(ID INTEGER PRIMARY KEY,age INTEGER,name TEXT)";
//执行该sQL语句创建表
sqliteDatabase.execsQL (sql) ;
使用insert()方法插入记录
contentValues contentValues = new Contentvalues ( ) ;contentValues.put ("ID",1);
contentValues.put ( "age",26);
contentValues.put ( "name" , "studentA" ) ;
//调用insert()方法将contentvalues对象封装的数据插入到student表中sqliteDatabase.insert ("student" , null,contentvalues) ;
使用execSQL()方法插入记录
//定义插入sQL语句
string sql= "INSERT INTo student (ID,age, name) values (1,26,'studentA ' ) ";//调用execsQL ()方法执行sQL语句,将数据插入到student表中
sqliteDatabase.execSQL(sql);
使用execSQL()方法删除记录
//定义更新sQL语句
string sql= "DELETE FORM student where name='StudentA' ";//调用execsQL()方法执行sQL语句更新student表中的记录
sqliteDatabase.execsQL(sql);
事务处理
beginTransaction()方法用于开始事务
endTransaction()方法用于结束事务
inTransaction()方法用于判断当前上下文是否处于事务环境中
setTransactionSuccessful()方法来设置事务成功标志
sQLiteDatabase提供以下几个方法来控制事务∶
SQLite0penHelper类
sQLiteOpenHelper是SQLiteDatabase的帮助类,用来管理数据库的创建和版本更新
通过继承SQLiteOpenHelper类可以隐藏开发过程中不需要直接调用的方法