
今天的越写悦快乐之系列文章为大家带来Android项目如何集成Room的文章。做过Android开发的小伙伴都知道,Android Jetpack Components是谷歌官方推荐的构建高质量Android应用程序的利器,而Room作为访问SQLite数据库的框架,我们有必要了解和实践它是如何集成到Android应用中来的,希望大家喜欢。
Room是什么
The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.
Room库提供了一个基于SQLite的抽象层,便于利用SQLite的全部功能的同时实现更强大的数据库访问。
The library helps you create a cache of your app's data on a device that's running your app. This cache, which serves as your app's single source of truth, allows users to view a consistent copy of key information within your app, regardless of whether users have an internet connection.
该库可帮助您在运行应用程序的设备上创建应用程序数据的缓存。 此缓存作为应用程序的数据存储介质,允许用户在应用程序中查看数据的副本,无论用户是否具有Internet连接。
如何接入
添加依赖
我们在项目的根配置文件build.gradle
中新增阿里云和Jitpack仓库地址,以便加快依赖包的下载和缓存,其文件内容如下。
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
maven { url 'https://jitpack.io' }
maven { url 'https://maven.google.com' }
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
然后我们在app模块的配置文件添加Room的依赖,其文件内容如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "me.weitao.app.todoviewmodel"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dataBinding {
enabled true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
checkReleaseBuilds false
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:support-v4:28.0.0'
// Room
implementation 'android.arch.persistence.room:common:1.1.1'
implementation 'android.arch.persistence.room:runtime:1.1.1'
annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'
testImplementation 'android.arch.persistence.room:testing:1.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
创建实体
其次,我们创建产品实体,用于描述产品的属性,包含名称、描述和价格,其文件内容如下:
package me.weitao.app.todoviewmodel.repository.entity;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey;
import me.weitao.app.todoviewmodel.model.Product;
@Entity(tableName = "products")
public class ProductEntity implements Product {
@PrimaryKey
private int id;
private String name;
private String description;
private int price;
@Override
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public ProductEntity() {
}
@Ignore
public ProductEntity(int id, String name, String description, int price) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
}
public ProductEntity(Product product) {
this.id = product.getId();
this.name = product.getName();
this.description = product.getDescription();
this.price = product.getPrice();
}
}
创建DAO
再次,我们通过DAO来操作数据库,也就是利用面向对象的方式将数据表记录与产品实体一一对应,其文件内容如下:
package me.weitao.app.todoviewmodel.repository.dao;
import android.arch.lifecycle.LiveData;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import java.util.List;
import me.weitao.app.todoviewmodel.repository.entity.ProductEntity;
@Dao
public interface ProductDao {
@Query("SELECT * FROM products")
LiveData<List<ProductEntity>> loadAllProducts();
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<ProductEntity> products);
@Query("select * from products where id = :productId")
LiveData<ProductEntity> loadProduct(int productId);
@Query("select * from products where id = :productId")
ProductEntity loadProductSync(int productId);
}
创建数据库操作
然后,我们抽象出一个对象,继承自RoomDatabase
,声明数据库名称,并通过Builder的方式构造数据库实例,其文件内容如下:
package me.weitao.app.todoviewmodel.repository;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.room.Database;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.arch.persistence.room.TypeConverters;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import java.util.List;
import me.weitao.app.todoviewmodel.app.AppExecutors;
import me.weitao.app.todoviewmodel.repository.converter.DateConverter;
import me.weitao.app.todoviewmodel.repository.dao.CommentDao;
import me.weitao.app.todoviewmodel.repository.dao.ProductDao;
import me.weitao.app.todoviewmodel.repository.entity.CommentEntity;
import me.weitao.app.todoviewmodel.repository.entity.ProductEntity;
@Database(entities = {ProductEntity.class, CommentEntity.class}, version = 1)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase sInstance;
@VisibleForTesting
private static final String DATABASE_NAME = "todo-db";
public abstract ProductDao productDao();
public abstract CommentDao commentDao();
private final MutableLiveData<Boolean> mIsDatabaseCreated = new MutableLiveData<>();
public static AppDatabase getInstance(final Context context, final AppExecutors executors) {
if (sInstance == null) {
synchronized (AppDatabase.class) {
if (sInstance == null) {
sInstance = buildDatabase(context.getApplicationContext(), executors);
sInstance.updateDatabaseCreated(context.getApplicationContext());
}
}
}
return sInstance;
}
private static AppDatabase buildDatabase(final Context appContext,
final AppExecutors executors) {
return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
.addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
executors.diskIO().execute(() -> {
// Add a delay to simulate a long-running operation
addDelay();
// Generate the data for pre-population
AppDatabase database = AppDatabase.getInstance(appContext, executors);
// List<ProductEntity> products = DataGenerator.generateProducts();
//List<CommentEntity> comments = DataGenerator.generateCommentsForProducts(products);
//insertData(database, products, comments);
// notify that the database was created and it's ready to be used
database.setDatabaseCreated();
});
}
})
.build();
}
private void updateDatabaseCreated(final Context context) {
if (context.getDatabasePath(DATABASE_NAME).exists()) {
setDatabaseCreated();
}
}
private void setDatabaseCreated() {
mIsDatabaseCreated.postValue(true);
}
private static void insertData(final AppDatabase database, final List<ProductEntity> products,
final List<CommentEntity> comments) {
database.runInTransaction(() -> {
database.productDao().insertAll(products);
database.commentDao().insertAll(comments);
});
}
private static void addDelay() {
try {
Thread.sleep(4000);
} catch (InterruptedException ignored) {
}
}
public LiveData<Boolean> getDatabaseCreated() {
return mIsDatabaseCreated;
}
}
创建AppRepository
我们构建好AppDatabase之后,使用AppRepository
来操作AppDatabase
,将数据库的记录取出并赋值给某个变量,其文件内容如下:
package me.weitao.app.todoviewmodel.app;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MediatorLiveData;
import java.util.List;
import me.weitao.app.todoviewmodel.repository.AppDatabase;
import me.weitao.app.todoviewmodel.repository.entity.CommentEntity;
import me.weitao.app.todoviewmodel.repository.entity.ProductEntity;
public class AppRepository {
private static AppRepository sInstance;
private final AppDatabase mDatabase;
private MediatorLiveData<List<ProductEntity>> mObservableProducts;
private AppRepository(final AppDatabase database) {
mDatabase = database;
mObservableProducts = new MediatorLiveData<>();
mObservableProducts.addSource(mDatabase.productDao().loadAllProducts(),
productEntities -> {
if (mDatabase.getDatabaseCreated().getValue() != null) {
mObservableProducts.postValue(productEntities);
}
});
}
public static AppRepository getInstance(final AppDatabase database) {
if (sInstance == null) {
synchronized (AppRepository.class) {
if (sInstance == null) {
sInstance = new AppRepository(database);
}
}
}
return sInstance;
}
public LiveData<List<ProductEntity>> getProducts() {
return mObservableProducts;
}
public LiveData<ProductEntity> loadProduct(final int productId) {
return mDatabase.productDao().loadProduct(productId);
}
public LiveData<List<CommentEntity>> loadComments(final int productId) {
return mDatabase.commentDao().loadComments(productId);
}
}
将AppRepository注入Application
最后,将对象注入到Application中,我们的应用在启动之后会通过AppDatabase读取数据库的记录,然后在View层通过LiveData监听页面的变化,其文件内容如下:
package me.weitao.app.todoviewmodel.app;
import android.app.Application;
import com.socks.library.KLog;
import me.weitao.app.todoviewmodel.BuildConfig;
import me.weitao.app.todoviewmodel.repository.AppDatabase;
public class TodoApplication extends Application {
private AppExecutors mAppExecutors;
@Override
public void onCreate() {
super.onCreate();
KLog.init(BuildConfig.DEBUG, "Kai");
mAppExecutors = new AppExecutors();
}
public AppDatabase getDatabase() {
return AppDatabase.getInstance(this, mAppExecutors);
}
public AppRepository getRepository() {
return AppRepository.getInstance(getDatabase());
}
}
在这里,我们使用了AppExecutors,通过它可以感知我们的程序运行在主线程或者UI线程,其文件内容如下:
package me.weitao.app.todoviewmodel.app;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class AppExecutors {
private final Executor mDiskIO;
private final Executor mNetworkIO;
private final Executor mMainThread;
private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
this.mDiskIO = diskIO;
this.mNetworkIO = networkIO;
this.mMainThread = mainThread;
}
public AppExecutors() {
this(Executors.newSingleThreadExecutor(), Executors.newFixedThreadPool(3),
new MainThreadExecutor());
}
public Executor diskIO() {
return mDiskIO;
}
public Executor networkIO() {
return mNetworkIO;
}
public Executor mainThread() {
return mMainThread;
}
private static class MainThreadExecutor implements Executor {
private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
mainThreadHandler.post(command);
}
}
}
我们使用的AppDatabase、AppRepository使用了单例模式,如何把这些对象方便地注入,也是目前我们需要关注的问题。
要将我们的应用启动起来,需要配合Activity的生命周期、LiveData或者其它技术才能更好地发挥Room操作数据库的强大能力。
参考
个人收获和感想
我们的文章学习了Android应用如何接入Room,也就是如何利用Room的注解帮助我们快速声明和使用业务实体并通过版本迁移的方式有效存储我们应用的数据,在无网络的情况下也可以使用我们的应用。当然,我们可以针对数据库操作的多样性深入探索Room的使用和原理,构建更加快速、稳定和高质量的App。Jetpack作为谷歌官方推荐使用的组件套件,我们有理由相信他为我们提供了构建Android应用丰富的场景和解决方案,从Fragment到Layout,从DataBinding到LiveData,从Navigation到WorkManger,无不体现了Android系统广泛的场景化方案,从手机到平板,从物联网到TV,我相信构建这些场景的核心依然是计算机语言的普及和发展,让我们有幸成为其中的一员,是我们的幸运。希望大家继续保持学习的热情,构建更加稳定安全的软件产品或者服务。若是我的文章对你有所启发,那将是我莫大的荣幸。
网友评论