Android系统中主要提供了4种方式用于简单地实现数据持久化功能,即文件存储、SharedPreferences存储、contentprovider存储以及数据库存储,真机测试是没有办法通过File Provider查看保存的数据的。
数据存储方式 | 文件保存目录 |
---|---|
File | /data/data/< 包名>/files/< 文件名> |
sharedPreferences | /data/data/< 包名>/shared_prefs/< 文件名> |
SQLite | /data/data/< 包名>/databases/< 数据库名> |
0.Android的4种数据存储方式概述
- File文件存储:写入和读取文件的方法和Java中实现l/O的程序一样,程序外本地数据存储,非结构化数据–文件存储
- SharedPreferences存储:一种轻型的数据存储方式,常用来存储一些简单的配置信息,本质是基于XML文件存储key-value键值对数据。应用内数据存储方式,程序外不可访问
- SQLlite数据库存储:一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,在存储大量复杂的关系型数据的时可以使用。结构化数据—SQLite数据库存储
- ContentProvider:四大组件之一,用于数据的存储和共享,不仅可以让不同应用程序之间进行数据共享,还可以选择只对哪一部分数据进行共享,可保证程序中的隐私数据不会有泄漏风险。
1. 文件存储
1.1将数据存储在文件中
Context类提供
OpenFileOutput()
方法将指定数据存储到指定的文件中
这个方法接收两个参数,第一个参数是文件名,不包含路径;第二个参数是文件Context的操作模式,一般来说是MODE_PRIVATE和MODE_APPEND,前者是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容;后者表示如果该文件已经存在,就向文件中追加内容,不存在创建新文件。方法返回一个FileOutputStream对象
-
MODE_ PRIVATE
为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容。 -
MODE_APPEND
模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。 -
MODE_WORLD_READABLE
表示当前文件可以被其他应用读取 -
MODE_WORLD_WRITEABLE
表示当前文件可以被其他应用写入
查看数据是否保存成功:
在cmd中进入SDK保存路径:cd /d D:\Android\Sdk\tools
然后调用ADB启动命令:monitor
1.2从文件中读取数据
onCreate
函数中调用
load()
方法来读取文件中存储的文本内容, 如果读到的内容不为null , 就调用EditText的
setText()
方法将内容填充到EditText里, 并调用
setSelection()
方法将输入光标移动到文本的末尾位置以便于继续输入
对字符串进行非空判断的时候使用了
TextUtils.isEmpty()
方法,可以一次性进行两种空值的判断。 当传入的字符串等于null 或者等于空字符串的时候, 这个方法都会返回true
2. SharePreferences
通过
键值对
的方式来进行数据的存储
2.1存储数据
基本步骤
- 调用getSharedPreferences()方法获取SharePreferences对象
- 得到了SharedPreferences 对象之后,调用SharedPreferences 对象的edit() 方法来获取一个SharedPreferences.Editor 对象。
- 向SharedPreferences.Editor 对象中添加数据,添加一个布尔型数据就使用putBoolean() 方法,添加一个字符串则使用putString() 方法
- 调用apply()方法将添加的数据提交,从而完成数据存储操作。
获取SharedPreferences对象
-
Context 类中的getSharedPreferences() 方法
此方法接收两个参数,
第一个参数用于指定SharedPreferences文件的名称, 如果指定的文件不存在则会创建一个
第二个参数用于指定操作模式
Context.MODE_PRIVATE:是默认的操作模式,和直接传入0效果是相同的,表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。
Context.MODE_WORLD_READABLE: 指定该SharedPreferences数据能被其他应用程序读,但不能写。
Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序读,写
2. Activity 类中的getPreferences() 方法只接收一个操作模式参数,使用这个方法时会自动将当前活动的类名作为SharedPreferences的文件名。
两种方式的区别:
- 调用Context对象的getSharedPreferences()方法获得的SharedPreferences对象可以被同一应用程序下的其他组件共享.
- 调用Activity对象的getPreferences()方法获得的SharedPreferences对象只能在该Activity中使用.
-
使用PreferenceManager 类中的getDefaultSharedPreferences() 方法。这是一个静态方法, 它接收一个Context 参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件。
2.2读取数据
SharedPreferences 对象中提供get 方法,用于对存储的数据进行读取,每种get 方法都对应了SharedPreferences.Editor 中的一种put 方法,读取一个字符串就使用getString() 方法。get 方法都接收两个参数,第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回。
3. SQLite数据库
3.1创建数据库
管理数据库的抽象类:
SQLiteOpenHelper
用于管理数据库的创建、升级等
需要实现的抽象方法:
onCreate() 和onUpgrade()
实例方法:
getReadableDatabase()和getWritableDatabase()
这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开, 否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,
当数据库不可写入的时候(如磁盘空间已满), getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase() 方法则将出现异常。
需要重写的构造方法:第一个参数是Context,必须要有它才能对数据库进行操作。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许我们在查询数据的时候返回一个自定义的Cursor, 一般是null 。第四个参数表示当前数据库的版本号, 可用于对数据库进行升级操作。
SQLite数据类型:
integer 表示整型,real 表示浮点型,text表示文本类型, blob表示二进制类型
3.2查看数据库
- 使用adb工具 配置其环境 将platform-tools目录配置到系统变量Path中
- 进入设备控制台 cmd adb shell su切换成超级管理员
- 查看目录文件 ls
- 打开数据库 sqlite3 数据库名称
-
其他命令
.table 查看数据库中的表有哪些
.schema 查看表的建表语句
.exit 或.quit 命令可以退出数据库的编辑
exit 命令可以退出设备控制台
3.3AS中查看模拟器或真机sqlite数据库
一、使用stetho方法
1. 科学上网
2. implementation 'com.facebook.stetho:stetho:1.5.1'
3. 主活动中添加 Stetho.initializeWithDefaults(this);
4. 运行App 在Chrome浏览器中地址栏中输入:chrome://inspect/#devices
二、
1. debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
2.启动程序
3. 在Logcat中输入D/DebugDB,即可查看IP与端口,在浏览器中打开访问即可
3.4数据库升级
如果想添加额外的表,只是在onCreate中再次调用数据库的执行操作是不会成功的,
因为onCreate方法不会被再次调用,所以进行如下修改:
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(Create_Book);
sqLiteDatabase.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
sqLiteDatabase.execSQL("drop table if exists Book");
sqLiteDatabase.execSQL("drop table if exists Category");
onCreate(sqLiteDatabase);
}
接着解决调用onUpgrade的问题
MyDatabaseHelper myDatabaseHelper=new MyDatabaseHelper(MainActivity.this,"database",null,2);
3.5增删改查
SQLiteDatabase 中提供了一个insert() 方法, 这个方法就是专门用于添加数据的。 它接收3个参数,第一个参数是表名,向哪张表里添加数据, 这里就传入该表的名字。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般传入null。第三个参数是一个ContentValues 对象,它提供了一系列的put() 方法重载,用于向ContentValues 中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可
ContentValues 和Hashtable类似都是一种存储的机制 但是两者最大的区别就在于,ContentValues只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西,而Hashtable却可以存储对象。
SQLiteDatabase 的update() 方法去执行具体的更新操作,第三个参数对应的是SQL语句的where 部分,表示更新所有name 等于?的行,?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。不指定的话就是默认所有行
SQLiteDatabase 中提供了一个delete() 方法,用于删除数据,这个方法接收3个参数, 第一个参数仍然是表名,第二、 第三个参数又是用于约束删除某一行或某几行的数据, 不指定的话默认就是删除所有行
查询方法quary()需要七个参数
(table, columns, selection, selectionArgs, groupBy, having, orderBy)
,含义是:(表名,要查询出的列名,查询条件子句,对应于selection语句中占位符的值,要分组的列名,分组后过滤条件,排序方式)。查询完之后就得到了一个Cursor 对象,接着我们调用它的
moveToFirst()
方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。在这个循环中可以通过Cursor 的
getColumnIndex()
方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中
3.6使用SQL操作数据库
3.7 特点
优点:
- 轻量级:只需要一个轻量级动态库
- 独立性:数据库的核心引擎不依赖第三方软件
- 隔离性:数据库所有信息都包含一个文件夹内
- 跨平台:支持多种操作系统,ios和android也可以
- 支持多语言编程接口
- 安全性:SQLite 数据库通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据,但只能有一个可以写入数据
缺点
- 并发性能差
3.8 SQL相关问题
-
SQLite的事务处理
SQLite在做CRDU操作时都默认开启了事务,然后把SQL语句翻译成对应的SQLiteStatement并调用其相应的CRUD方法,此时整个操作还是在rollback journal这个临时文件上进行,只有操作顺利完成才会更新.db数据库,否则会被回滚。 -
SQLite的批量处理
使用SQLiteDatabase的beginTransaction()方法开启一个事务,将批量操作SQL语句转化成SQLiteStatement并进行批量操作,结束后endTransaction() -
删除SQLite中表的一个字段如何做
SQLite数据库只允许增加表字段而不允许修改和删除表字段,只能采取复制表思想,即创建一个新表保留原表想要的字段,再将原表删除 -
使用SQLite会有哪些优化操作
- 使用事务做批量操作:具体操作见上
- 及时关闭Cursor,避免内存泄漏
- 耗时操作异步化:数据库的操作属于本地IO,通常比较耗时,建议将这些耗时操作放入异步线程中处理
- ContentValues的容量调整:ContentValues内部采用HashMap来存储Key-Value数据,ContentValues初始容量为8,扩容时翻倍。因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作
- 使用索引加快检索速度:对于查询操作量级较大、业务对要求查询要求较高的推荐使用索引
4. 数据库 使用LitePal开源项目
LitePal是一款开源的Android数据库框架,采用对象关系映射(ORM)模式,将常用的数据库功能进行封装,可以不用写一行SQL语句就可以完成创建表、增删改查的操作
1.添加依赖
implementation 'org.litepal.android:java:3.0.0'
2.配置litepal.xml文件
app/src/main目录→New→Directory, 创建一个assets目录, 然后在assets目录下再新建一个litepal.xml文件
3.配置LitePalApplication(两种方法)
LitePal配置链接
4.1表的管理操作
1.定义java类model 实现getter setter方法
2.模型添加到
litepal.xml
文件的< List>< /List>映射中
< mapping class="com.example.litepaldemo.Album">< /mapping>
3.创建数据库
SQLiteDatabase db=LitePal.getDatabase();
4.升级表
修改java的model 然后检查
litepal.xml
文件是否需要修改 数据库版本号数值增加
4.2数据的增删改查
现有的模型类没有继承结构,因为LitePal进行表管理操作时不需要模型类有任何的继承结构, 但是进行CRUD操作时模型类必须要继承LitePalSupport类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Stetho.initializeWithDefaults(this);
LitePal.initialize(this);
setContentView(R.layout.activity_main);
Button button=(Button)findViewById(R.id.initize);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db=LitePal.getDatabase();
}
});
Button button1=(Button)findViewById(R.id.add);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db=LitePal.getDatabase();
Album album=new Album();
album.setName("album");
album.setPrice(10.99f);
album.save();//save函数是从LitePalSupport继承而来
Song song1 = new Song();
song1.setName("song1");
song1.setDuration(320);
song1.setAlbum(album);
song1.save();
Song song2 = new Song();
song2.setName("song2");
song2.setDuration(356);
song2.setAlbum(album);
song2.save();
}
});
Button button2=(Button)findViewById(R.id.delete);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
LitePal.delete(Album.class,31);
LitePal.deleteAll(Song.class,"name=?","alibaba");
LitePal.deleteAll(Album.class,"name=?","album");
}
});
Button button3=(Button)findViewById(R.id.update);
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Album album=new Album();
album.setName("album3");
album.setPrice(2019);
album.save();//save函数是从LitePalSupport继承而来
Song song = new Song();
song.setName("song3");
song.setDuration(730);
song.setAlbum(album);
song.save();
song.setName("alibaba");
song.updateAll();//设置名字 然后对所有的song对象进行保存操作
song.setToDefault("name");
song.update(2);//将名字设置为默认值 对id为2的对象进行保存
}
});
Button button4=(Button)findViewById(R.id.query);
button4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//方法一 查询所有的Album对象
List<Album> all = LitePal.findAll(Album.class);
for (Album album : all) {
Log.d("MainActivity", " name is " + album.getName());
}
//方法二 查询第三个song对象
Song song = LitePal.find(Song.class, 88);
Log.d("MainActivity", " name is " + song.getName());
//方法三 查询API 查询第一条数据
Album album=LitePal.findFirst(Album.class);
//方法四 连缀查询
//order() 方法用于指定结果的排序方式,limit() 方法用于指定查询结果的数量,
//offset() 方法用于指定查询结果的偏移量
List<Song> songs = LitePal.select("name","id","album_id")
.where("name like ? and duration < ?", "song%", "200")
.order("duration dsc").limit(5).offset(3).find(Song.class);
//方法五 原生态查找
Cursor c = LitePal.findBySQL("select * from Album where price = ?", "2019");
}
});
}
}