文章目录
- 一、SharedPreferences
- 二、SQLite
- 三、Room
- 使用Room进行增删改查
- Room数据库升级
一、SharedPreferences
要想使用SharePreferences来存储数据,首先需要获取到SharedPreferences对象。Android中提供了三种方法用于得到SharedPreferences对象
1.Context类中的getSharedPreferences()方法
第一个参数用于指定SharedPreferences文件名称,如果指定的文件不存在则会创建一个。第二个参数用于指定操作模式
MODE_PRIVATE表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。
例如:
//获取对象
SharePreferences.Editor editor=getSharedPreferences("data",MODE_PRIVATE).edit();
//进行存储
editor.putString("name","Tom);
editor.putInt("age",28);
editor.apply();
//读取数据
SharedPreference pref=getSharedPreferences("data",MODE_PRIVATE);
/*
通过getSharedPreferences()方法得到了SharedPreferences对象,然后再调用getString(),getInt()获取前面所存储的姓名和年龄,如果没有找到相应的值,就会使用方法中传入的默认值来代替
*/
String name=pref.getString("name","");
int age=pref.getInt("age",0);
如果存储的key值一样,后存储的会覆盖前存储的,比如:
pref.getString(“sex”,“女”);
pref.getString(“sex”,“男”);
最后shared_prefs文件夹文件只会显示
<map>
<string name="sex">男</string>
</map>
2.Activity类中的getPreferences()方法
这个方法和Context类中的getSharedPreferences()方法很相似,不过它只接受一个操作模式,这个方法会自动将当前活动的类名作为SharedPreferences的文件名
3.PreferenceManager类中的getDefaultSharedPreferences()方法
这是一个静态方法,他接收一个Context参数,并自动使用当前应用程序的包作为前缀来命名SharedPreferences文件
例如:
//获取对象
SharedPreferences.Editor editor = PreferenceManager.
getDefaultSharedPreferences(WeatherActivity.this)
.edit();
//添加数据
editor.putString("weather", responseText);
//将添加的数据提交,从而完成数据存储操作
editor.apply();
//得到SharedPreferences对象
SharedPreferences prefs = PreferenceManager.
getDefaultSharedPreferences(this);
//读取数据
String weatherString = prefs.getString("weather", null);
String bingPic=prefs.getString("bing_pic",null);
向SharedPreferences文件存储数据分为三步:
1.调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象
2.向SharedPreferences.Editor对象添加数据,比如添加一个布尔型数据就使用putBoolean()方法,添加一个字符串则使用putString()方法,以此类推
3.调用apply()方法将添加的数据提交,从而完成数据存储操作
二、SQLite
1.建立数据库
Android提供了一个SQLiteOpenHelper帮助类,借助这个类我们可以对数据库进行创建和升级。SQLiteOpenHelper是一个抽象类,我们要想使用他的话就需要创建一个自己的帮助类去继承他。SQLiteOpenHelper中有两个抽象方法,分别是onCreate和onUpgrade,我们必须在自己的帮助类中重写这两个方法,分别在这两个方法去实现创建、升级数据库。
SQLiteOpenHelper中有两个构造方法重写,我们一般使用参数少的那个,这个构造方法中接受4个参数,第一个是Context,第二个是数据库名,第三个是查询数据库时返回的一个自定义的Cursor,一般都是传入null。第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。构件出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase()或getWrittableDatabase()方法就能创建数据库了。
SQLiteDatabase的execSQL()方法去执行建表语句
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book(" + "id integer primary key autoincrement,"
+ "author text, " + "price real, " + "pages integer, " + "name text)";
private Context mContext;
public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
//表如果已经存在就不会在执行onCreate()方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);//这是创建表的语句,又因为在onCreate方法中,所以会
//在创建数据库链接getWritableDatabase的时候调用,这样的话就能创建表
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_LONG).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public class MainActivity extends AppCompatActivity {
//implements View.OnClickListener{
private MyDatabaseHelper dbHelper;
private Button createDatabase,addData,updateData,delete,query;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建数据库
dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,1);
//当版本从1变到2的时候就会让onUpgrade()方法执行
// init();
createDatabase=(Button) findViewById(R.id.ceate_database);
addData=(Button) findViewById(R.id.Add_data);
updateData=(Button) findViewById(R.id.update_data);
delete=(Button) findViewById(R.id.delete_data);
query=(Button) findViewById(R.id.query_data);
createDatabase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
2.升级数据库
onUpgrade()方法是用于对数据库进行升级的。目前就已经有一张Book表用于存放书的各种详细数据,如果我们想再添加一张Category表用于记录图书的分类,该怎么做呢?在之前的代码中添加以下语句
public static final String CREATE_CATEGORY = "create table Category(" + "id integer primary key autoincrement,"
+ "category_name text, " + "category_code integer)";
//表如果已经存在就不会在执行onCreate()方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);//这是创建表的语句,又因为在onCreate方法中,所以会
//在创建数据库的时候调用,这样的话创建的数据库的同时也能创建表
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_LONG).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//如果存在两张表中的一个就都删除掉,,然后再调用onCreate方法重新创建
db.execSQL("drop table if exists Book");//如果存在Book表删掉
db.execSQL("drop table if exists Category");//如果存在Category表删掉
onCreate(db);
}
因为之前的BookStore.db数据库已经存在了,之后不管怎么点击createDatabase按钮,MyDatabaseHelper的onCreate()方法都不会再执行,因此填加的新表也无法创建了。
此时我们可以在onUpgrade方法里执行两条DROP语句,如果发现创建了Book表或者Category表。就将这张表删除,然后再调用onCreate方法重新创建。
==如何让onUpgrade方法得到执行?==直接将SQLiteOpenHelper的构造方法里接受的第四个参数。他表示当前数据库的版本号,之前我们传入的是1,现在传入一个比1大的数(一般要比原来的大),就可以让onUpgrade()方法得到执行。如dbHelper=new MyDatabaseHelper(this,“BookStore.db”,null,2);
3.添加数据库
SQLiteDatabase中提供了一个insert()方法,这个方法就是专门用于添加数据。他接收3个参数,第一个参数是表名,第二个参数是用于未指定添加数据的情况下给某些可为空的列自动赋值为NULL,一般用不到,直接传入null即可。。第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向contentValues中添加数据。
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db1= dbHelper.getWritableDatabase();
ContentValues values=new ContentValues();
//开始组装第一天数据
values.put("name","The Da Vinci Code");
values.put("author","Dan Brown");
values.put("pages",454);
values.put("price",16.69);
db1.insert("Book",null,values);
values.clear();
//开始组装第二条数据
values.put("name","The Lost Symbol");
values.put("author","Dan Brown");
values.put("pages",510);
values.put("price",19.95);
db1.insert("Book",null,values);
}
});
4.更新数据库
update()方法用于対数据进行更新。这个方法接收4个参数,第一个是表名,在这里是指定去更新哪张表里的数据。第二个参数是ContentValues对象,用于把更新数据在这里组装进去。第三、第四个参数用于约束更新某一行或某几行中的数据,不知道的话默认是更新所有行
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db2 = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price",10.99);
db2.update("Book",values,"name = ?",new String[]{"The Da Vinci Code"});
}
});
5.删除数据库
delete()方法专门用来删除数据,这个方法接收三个参数,第一个参数是表名,第二,第三个参数用于约束删除某一行或某几行数据,不指定的话就是默认删除所有行。
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db3=dbHelper.getWritableDatabase();
db3.delete("Book","pages>?",new String[]{"500"});
db3.delete("Book","author=?",new String[]{"Dan Brown"});
}
});
6.查询数据库
query()方法用于对数据库进行查询。最短的一个方法重载需要传入7个参数。第一个参数表名,第二个参数指定去查询哪几列,如果不指定则默认查询所有列。第三,四个参数用于约束查询某一行或某几行的数据,不指定则默认查询所有行,第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作。第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于对查询结果的排序方式,不指定则表示使用默认的排序方式。
cursor.moveToFirst()将指针指向第一个数据的第一行数据。然后进入循环去遍历
query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db=dbHelper.getWritableDatabase();
//查询表中所有的数据
Cursor cursor= db.query("Book", null, null, null, null, null, null);
if(cursor.moveToFirst())
{
do {
//遍历Cursor对象,取出数据并打印
String name=cursor.getString(cursor.getColumnIndex("name"));
String author=cursor.getString(cursor.getColumnIndex("author"));
int pages=cursor.getInt(cursor.getColumnIndex("pages"));
double price=cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("MainActivity","book name is"+name);
Log.d("MainActivity","book author is"+author);
Log.d("MainActivity","book pages is"+pages);
Log.d("MainActivity","book price is"+price);
}while (cursor.moveToNext());
}
cursor.close();
}
});
//遍历方法1:
if (cursor.moveToFirst()) {
do{
//取出数据,调用cursor.getInt/getString等方法
}while(cusor.moveToNext());
}
//遍历方法2:
if(cusor != null){
while(cursor.moveToNext()) {
//读取数据
}
}
三、Room
使用Room进行增删改查
通过下面这三个就可以完成对数据库的基本操作:
Room的整体结构,它主要由Entity、Dao和Database这3部分组成。
- Entity。用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。
- Dao。Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互。
- Database。用户定义数据库中的关键信息,包括数据库的版本号,包括哪些实体类以及提供Dao层的访问实例。
新建一个User实体类
@Entity
data class User(var firstName:String,var lastName:String,var age:Int){
@PrimaryKey(autoGenerate = true)
var id:Long=0
}
这里我们在User的类名上使用@Entity注解,将它声明成了一个实体类,然后在User类中添加了一个id字段,并使用@PrimaryKey注解将它设为了主键,再把autoGenerate参数指定为true,使得主键的值是自动生成的。
接下来定义Dao,因为所有访问数据库的操作都在这里封装的。
访问数据库的操作无非就是增删改查这4种,但是业务需求千变万化。而Dao要做的事情就是覆盖所有的业务需求,使得业务永远只需要与Dao层进行交互,而不必和底层的数据库打交道。
新建一个UserDao接口
@Dao
interface UserDao {
@Insert
fun insertUser(user: User):Long
@Update
fun updateUser(newUser: User)
@Query("select * from User")
fun loadAllUsers():List<User>
@Query("select * from User where age > :age")
fun loadUsersOlderThan(age:Int):List<User>
@Delete
fun deleteUser(user:User)
@Query("delete from User where lastName=:lastName")
fun deleteUserByLastName(lastName:String):Int
}
UserDao 接口的上面使用了一个@Dao注解,这样Room才能将它识别成一个Dao。UserDao的内部就是根据业务需求对各种数据库操作进行的封装。数据库操作通常有增删改查4种,因此Room也提供了@Insert,@Delete, @Update ,@Query这4种相应的注解。
insertUser()方法上面使用了@Insert注解,表示会将参数中传入的User对象插入数据库中,插入完成后还会将自动生成的主键id值返回。updateUser()方法上面使用了@Update注解,表示会将参数中传入的User对象更新到数据库当中。deleteUser()方法上面使用了@Delete注解,表示会将参数传入的User对象从数据库中删除。以上几种数据库操作都是直接使用注解标识,不需要编写SQL语句。
如果想要从数据库查询数据,或者使用非实体类参数来增删改查数据,就必须编写SQL语句了。比如loadAllUsers()方法,如果只使用一个@Query注解,Room将无法知道我们想要查询哪些数据。我们还可以将方法中传入的参数指定到SQL语句当中,比如loadUsersOlderThan()方法就可以查询所有年龄大于指定参数的用户。而如果使用非实体类的参数来增删改数据,也必须编写SQL语句,只能使用@Query注解,参考deleteUserByLastName()方法的写法。
定义Database。只需定义好3个部分的内容:数据库版本号、包含哪些实体类,以及提供Dao层的访问实例。新建一个AppDatabase.kt文件,代码如下:
@Database(version = 1,entities = [User::class])
abstract class AppDatabase: RoomDatabase() {
abstract fun userDao():UserDao
companion object{
private var instance:AppDatabase?=null
fun getDatabase(context: Context):AppDatabase{
//instance不为空直接执行let函数返回instance,为空则不执行let函数
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database"
).build().apply { instance=this }
//apply函数中是构建AppDatabase的实例的上下文,会自动返回调用对象本身
}
}
}
我们在AppDatabase类的头部使用了@Database注解,并在注解中声明了数据库的版本号以及包含哪些实体类,多个实体类之间用逗号隔开。
AppDatabase类必须继承自RoomDatabase类,并且一定要使用abstract关键字将它声明成抽象类,然后提供相应的抽象方法,用于获取之前编写的Dao的实例,比如这里提供的userDao()方法。这里只需声明,具体的实现由Room在底层自动完成的(不用自己去实现这个抽象方法)。
接着在companion object结构体中编写了一个单例模式,因为原则上全局应该只存在一份AppDatabase实例。这里使用了instance变量来缓存AppDatabase的实例,然后再getDatabase()方法中判断:如果instance变量不为空就直接返回,否则就调用Room.databaseBuilder()方法来构建一个AppDatabase实例。databaseBuilder()方法接收3个参数,第一个参数是applicationContext,避免出现内存泄露。第二个参数是AppDatabase的Class类型,第三个参数是数据库名。最后调用build()方法完成构建,并将创建出来的实例赋值给instance变量,然后返回当前实例。
activity_main.xml代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/addDataBtn"
android:layout_gravity="center_horizontal"
android:text="Add Data"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/updateDataBtn"
android:layout_gravity="center_horizontal"
android:text="Update Data"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/deleteDataBtn"
android:layout_gravity="center_horizontal"
android:text="Delete Data"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/queryDataBtn"
android:layout_gravity="center_horizontal"
android:text="Query Data"
/>
</LinearLayout>
MainActivity代码:
class MainActivity : AppCompatActivity() {
private val TAG:String="MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var userDao=AppDatabase.getDatabase(this).userDao()
val user1=User("Tom","Brandy",40)
val user2=User("Tom","Hanks",63)
addDataBtn.setOnClickListener {
thread {
user1.id=userDao.insertUser(user1)
user2.id=userDao.insertUser(user2)
}
}
updateDataBtn.setOnClickListener {
thread {
user1.age=2
userDao.updateUser(user1)
}
}
deleteDataBtn.setOnClickListener {
thread {
userDao.deleteUserByLastName("Hanks")
}
}
queryDataBtn.setOnClickListener {
thread {
for(user in userDao.loadAllUsers()){
Log.d(TAG, user.toString())
}
}
}
}
}
点击"Add Data"按钮,再点击"Query Data"按钮
可以看到两条用户数据添加进来了
点击"Update Data"按钮,再重新点击"Query Data"按钮
可以看到,第一条数据中用户的年龄被成功修改成了2岁。
点击"Delete Data"按钮,再重新点击"Query Data"按钮
只剩下一条数据了
Room数据库升级
如果你处于开发测试阶段,Room提供了一个简单粗暴的方法,如下所示:
Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database"
).fallbackToDestructiveMigration().build()
在构建AppDatabase实例的时候,加入一个fallbackToDestructiveMigration()方法。这样只要数据库进行了升级,Room就会将之前的数据库销毁,然后再重新创建,随之而来的副作用就是将之前数据库中的所有数据就全部丢失了。
接下来是Room数据库升级的正规写法:
随着业务升级,现在我们打算在数据库添加一张Book表,那么首先就是创建一个Book的实体类,如下所示:
@Entity
data class Book (var name:String,var pages:Int){
@PrimaryKey(autoGenerate = true)
var id:Long=0
}
然后创建一个BookDao接口
@Dao
interface BookDao {
@Insert
fun insertBook(book:Book):Long
@Query("select * from Book")
fun loadAllBooks():List<Book>
}
接下来修改AppDatabase中的代码,在里面编写数据库升级的逻辑
@Database(version = 2,entities = [User::class,Book::class])
abstract class AppDatabase: RoomDatabase() {
abstract fun userDao():UserDao
abstract fun bookDao():BookDao
companion object{
val MIGRATION_1_2=object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book(id integer primary key autoincrement not null,name text not null,pages integer not null)")
}
}
private var instance:AppDatabase?=null
fun getDatabase(context: Context):AppDatabase{
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database"
).addMigrations(MIGRATION_1_2).build().apply { instance=this }//apply函数中是构建AppDatabase的实例的上下问,
// 会自动返回调用对象本身
}
}
}
首先在@Database注解中,我们将版本号升级成了2,并将Book类添加到了实体类声明中,然后又提供了一个bookDao()方法用于获取BookDao的实例。
在 companion object结构体中,我们实现了一个Migration的匿名类,并传入了1和2这两个参数,表示当数据库从1升级到2的时候就执行这个匿名类的时候就执行这个匿名类的升级逻辑。由于我们新增一张Book表,所以需要在migrate()方法中编写相应的建表语句。Book表的建表语句和Book实体类中声明的结构要完全一致。
最后在构建AppDatabase实例的时候,加入一个addMigrations()方法,并把MIGRATION_1_2传入。
现在当我们进行任何数据库操作时,Room就会自动根据当前数据库的版本号执行这些升级逻辑,从而让数据库始终保证是最新的版本。
每次数据库升级并不一定都要新增一张表,也有可能是向现有的表中添加新的列。这个时候只需要使用alter语句修改表结构就可以了。
比如添加一个作者字段
@Entity
data class Book (var name:String,var pages:Int,var author:String){
@PrimaryKey(autoGenerate = true)
var id:Long=0
}
修改AppDatabase中的代码
@Database(version = 3,entities = [User::class,Book::class])
abstract class AppDatabase: RoomDatabase() {
abstract fun userDao():UserDao
abstract fun bookDao():BookDao
companion object{
val MIGRATION_1_2=object :Migration(1,2){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book(id integer primary key autoincrement not null,name text not null,pages integer not null)")
}
}
val MIGRATION_2_3=object :Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Book add column author text not null default 'unknown'")
}
}
private var instance:AppDatabase?=null
fun getDatabase(context: Context):AppDatabase{
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database"
).addMigrations(MIGRATION_1_2, MIGRATION_2_3).build().apply { instance=this }//apply函数中是构建AppDatabase的实例的上下问,
// 会自动返回调用对象本身
}
}
}