1.创建工程
所有模块依赖于Google,jencenter,maven仓库
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
mavenCentral()
}
}
gradle版本:gradle-4.1-milestone-1-all
gradle 的Android插件版本在3.0.0-beta7
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-milestone-1-all.zip
classpath 'com.android.tools.build:gradle:3.0.0-alpha7'
工程根目录的gradle设置一些变量,表示模块依赖的框架的版本号,方便以后改动,例如
ext {
buildToolsVersion = "26.0.1"
supportLibVersion = "26.0.2"
runnerVersion = "1.0.1"
rulesVersion = "1.0.1"
espressoVersion = "3.0.1"
archLifecycleVersion = "1.0.0-alpha9"
archRoomVersion = "1.0.0-alpha9"
constrantLayoutVersion="1.0.2"
}
Module的gradle里
compile 'com.android.support:design:' + rootProject.supportLibVersion
compile 'com.android.support:cardview-v7:' + rootProject.supportLibVersion
compile 'com.android.support:recyclerview-v7:' + rootProject.supportLibVersion
compile 'com.android.support.constraint:constraint-layout:' + rootProject.constrantLayoutVersion
需求分析,效果展示
这个应用就两个界面,打开应用进入首页,刚开始会加载产品数据,数据是从本地数据库查询而来,然后点击任意一项可以进入产品的详情页,详情页包含对产品的评论
主目录
db
converter
里面放着一个日期转换器,用了TypeConverter注解
/**
* 时间转换
* @param timestamp
* @return Date
*/
@TypeConverter
public static Date toDate(Long timestamp){
return timestamp==null?null:new Date(timestamp);
}
/**
* 时间转换
* @param date
* @return long
*/
@TypeConverter
public static Long toTimestamp(Date date) {
return date == null ? null : date.getTime();
}
model
模型,里面都是接口,包含的都是获取数据实体成员的方法,它们都是抽象的。
例:一个Comment的,一个Product的
public interface Comment {
int getId();//获取ID
int getProductId();//获取产品ID
String getText();//获取评论内容
Date getPostedAt();//获取发布时间
}
public interface Product {
int getId();
String getName();
String getDescription();
int getPrice();
}
entity
这个数据库就两张表,一个使产品表,一个使评论表,所以分别对应着各自的实体类
例如一个产品的实体类,它是实现了model里的抽象方法
//Entity注解,设置表名为products
@Entity(tableName = "products")
public class ProductEntity implements Product{//实现了模型的抽象方法
@PrimaryKey
private int id;//id 为主键
private String name;//产品名
private String description;//产品描述
private int price;//产品价格
@Override
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@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 int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public ProductEntity() {
}
public ProductEntity(Product product){
this.id=product.getId();
this.name=product.getName();
this.description=product.getDescription();
this.price=product.getPrice();
}
}
@Entity(tableName = "comments" ,foreignKeys = {
@ForeignKey(entity = ProductEntity.class,
parentColumns = "id",
childColumns = "productId",
onDelete = ForeignKey.CASCADE)
},indices = { @Index(value = "productId")
})
public class CommentEntity implements Comment{
@PrimaryKey(autoGenerate = true)
private int id;
private int productId;
private String text;
private Date postedAt;
@Override
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
@Override
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public Date getPostedAt() {
return postedAt;
}
public void setPostedAt(Date postedAt) {
this.postedAt = postedAt;
}
public CommentEntity() {
}
public CommentEntity(Comment comment) {
this.id = comment.getId();
this.productId = comment.getProductId();
this.text = comment.getText();
this.postedAt = comment.getPostedAt();
}
}
dao
里面是各个表的操作接口,都是抽象的,很像Retrofit那个带注解的接口,只不过这里是操作数据库
@Dao
public interface CommentDao {
@Query("SELECT * FROM comments where productId = :productId")
LiveData<List<CommentEntity>> loadComments(int productId);
@Query("SELECT * FROM comments where productId = :productId")
List<CommentEntity> loadCommentsSync(int productId);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<CommentEntity> products);
}
@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);
}
AppDatabase.java
AppDatabase继承RoomDatabase,这个类的作用就是数据库类,通过这个对象可以获取各个表的Dao和数据库设置,@Database注解设置了实体类和数据库版本,@TypeConverters指定了类型转换,就是上面的日期转换,因为数据库没有Data这种类型,但可以用long表示
@Database(entities = {ProductEntity.class, CommentEntity.class},version = 1)
@TypeConverters(DateConverter.class)
public abstract class AppDataBase extends RoomDatabase {
public static final String DATABASENAME="basesample-db";//数据库名
public abstract ProductDao productDao();//获取Dao
public abstract CommentDao commentDao();//获取Dao
}
DatabaseCreator.java
这个是一个辅助类,也是一个单例,用于获取AppDatabase实例,因为很多地方都要用AppDatabase实例嘛,而且数据库还没创建啊,这个类设计到一些知识
实现单例
这里应该用的是双重检查锁,保证创建实例是也是在一个线程里创建
// For Singleton instantiation
private static DatabaseCreator sInstance;
private static final Object LOCK = new Object();
public synchronized static DatabaseCreator getInstance(Context context) {
if (sInstance == null) {
synchronized (LOCK) {
if (sInstance == null) {
sInstance = new DatabaseCreator();
}
}
}
return sInstance;
}
原子操作
这里用到了AtomicBoolean,原来看到这里我都很懵逼,后来查查资料,才知道这个叫原子操作,就是compareAndSet(boolean expect, boolean update)。
- 比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句
- 把AtomicBoolean的值设成update
这连个操作一气呵成,中间没有人能够阻止,这样的话可以进行多线程控制,比如这里的创建数据库,因为后期可能多个地方会用到creatDb方法,所以要保证数据库只能创建一次且只能在一个线程中创建,当然这里没用SharePreferences,所以这里的"数据库只能创建一次"是指在应用不被杀死的情况下。
private final MutableLiveData<Boolean> mIsDatabaseCreated = new MutableLiveData<>();
private AppDataBase mDb;
private final AtomicBoolean mInitializing = new AtomicBoolean(true);
public void createDb(Context context) {
Log.d("DatabaseCreator", "Creating DB from " + Thread.currentThread().getName());
if (mInitializing.compareAndSet(false, true)) {
return; // Already initializing 已经创建了数据库
}
mIsDatabaseCreated.setValue(false);//开始创建数据库,观察这个数据可以显示loading
new AsyncTask<Context,Void,Void>(){
@Override
protected Void doInBackground(Context... contexts) {
Log.d("DatabaseCreator",
"Starting bg job " + Thread.currentThread().getName());
Context context = contexts[0].getApplicationContext();
// Reset the database to have new data on every run.
context.deleteDatabase(DATABASENAME);
AppDataBase db= Room.databaseBuilder(context.getApplicationContext(),AppDataBase.class,
DATABASENAME).build();
addDelay(); // Add a delay to simulate a long-running operation模拟耗时操作
// Add some data to the database 加一些数据进去
DatabaseInitUtil.initializeDb(db);
Log.d("DatabaseCreator",
"DB was populated in thread " + Thread.currentThread().getName());
mDb=db;
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
mIsDatabaseCreated.setValue(true);//创建数据库完毕
Log.d(TAG, "onPostExecute:");
}
}.execute(context.getApplicationContext());
}
viewmodel
viewmodel就是提供一个连接model和view的桥梁,只不过提供的是可被观察的数据对象,而且是liveData,可以判断观察者的状态进行通知.如下面的ProductListViewModel,mObservableProducts是一个LiveData,viewmodel获取它是通过database得到,但如果database未初始化的情况也要考虑,所以用了一个Transformations.switchMap(),就是在databaseCreator.isDatabaseCreated()这个liveData为FALSE时返回值为空的liveData,为True时返回database查询到的liveData,这里可能有点绕,但是慢慢想又觉得很妙,因为这个观察模式在一定的生命周期内一直生效,完全是响应式的。
public class ProductListViewModel extends AndroidViewModel{
private static final String TAG = "ProductListViewModel>>>";
private static final MutableLiveData ABSENT=new MutableLiveData();
{
ABSENT.setValue(null);
}
private final LiveData<List<ProductEntity>> mObservableProducts;
public ProductListViewModel(@Nullable Application application) {
super(application);
final DatabaseCreator databaseCreator=DatabaseCreator.getInstance(application);
mObservableProducts= Transformations.switchMap(databaseCreator.isDatabaseCreated(), new Function<Boolean, LiveData<List<ProductEntity>>>() {
@Override
public LiveData<List<ProductEntity>> apply(Boolean input) {
if (!Boolean.TRUE.equals(input)){
Log.d(TAG, "apply: 空数据!");
return ABSENT;
}else {
return databaseCreator.getDatabase().productDao().loadAllProducts();
}
}
});
databaseCreator.createDb(this.getApplication());
}
public LiveData<List<ProductEntity>> getmObservableProducts() {
return mObservableProducts;
}
}