Android Architecture Componets B

2017-10-06  本文已影响28人  WangGavin

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

需求分析,效果展示

这个应用就两个界面,打开应用进入首页,刚开始会加载产品数据,数据是从本地数据库查询而来,然后点击任意一项可以进入产品的详情页,详情页包含对产品的评论

效果展示

主目录

src主目录

db

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)。

  1. 比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句
  2. 把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目录

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;
    }
}
上一篇下一篇

猜你喜欢

热点阅读