以前,Android团队没有提供有关如何构建Android应用程序的建议。在大多数情况下,这意味着任何第一次学习Android的人最终都会将所有代码放入Activity文件中,如果应用程序因NetworkOnMainThreadException崩溃,偶尔会将内容移动到AsyncTask中。只有在尝试添加单元测试和检测测试之后,您才能真正理解您刚刚开发的代码,不易读取,更改或编写测试。
然后每个人都开始讨论不同的模式 - MVP,MVVM,MVI模式,文章和库。 Android框架团队已经意识到强烈需要增加他们为Android应用程序的最佳实践架构提供的指导。 Android团队发布的这些新架构组件旨在改变这一点。
什么是新的架构组件?
架构组件框架是一组库和指南,可作为编写Android应用程序的基础。它们解决了开发人员在各种应用程序中面临的常见情况。该框架旨在减少样板和重复代码的数量,使您专注于应用程序的核心功能。
架构组件的基本块包括以下内容:
- Room - 一个SQLite对象映射器。与ORMlite或greenDAO等其他库非常相似。它使用SQL,同时仍允许对查询进行编译时保证。
- LiveData - 生命周期感知的可观察核心组件。
- ViewModel - 与活动/片段的应用程序的其余部分的通信点。比活动或片段更长久。
- Lifecycle - 体系结构组件的核心部分,它包含有关组件生命周期状态的信息(例如,活动)。
- LifecycleOwner - 具有生命周期(活动,碎片,进程,自定义组件)的组件的核心接口。
- LifecycleObserver - 指定触发某些Lifecycle方法时应发生的情况。创建LifecycleObserver允许组件自包含。
在应用程序中使用新的体系结构组件
我们将构建一个应用程序,它是您添加到应用程序的不同事件的倒计时。我们将使用MVVM模式。
下面的图表说明了我们将使用新的Architecture组件构建的Android应用程序。 此图详细说明了我们将在本系列中构建的Date Countdown应用程序的最终结果。 它还指示在应用程序的哪个部分中使用了哪些体系结构组件。
MVP和MVVM之间的主要区别在于MVVM ViewModels暴露数据并且感兴趣的各方可以监听该数据(或忽略它),而MVP在View和Presenter之间存在严格的契约。使用MVP,重用Presenters更加困难,因为它们与View紧密耦合。使用MVVM,Views可以从ViewModel订阅他们感兴趣的数据。
什么是Room?
Room是在Android应用中创建数据库的新方法。 Room消除了您之前必须编写的许多样板代码,以便在您的应用中存储数据。 Room是Java类和SQLite之间的ORM。使用Room,您不再需要使用Cursors和Loaders。Room不是完全成熟的ORM,例如,您不能像其他ORM解决方案那样提供复杂的对象嵌套。
使用Room,您可以通过几种不同的方式查询数据:
- 使用LiveData,它是一个公开可以订阅以接收更新的事件流的类。这可以在主线程上使用,因为它是异步的。
- 使用RxJava2 Flowable抽象类。
- 将同步调用放在后台线程中,例如AsyncTask。(Room不允许您在主线程上发出数据库查询(因为这会产生ANR))。
开始使用Room
在这个例子中,我们将创建一个允许我们保存重要日期的应用程序,它将显示每个日期的倒计时。。
1.在Android Studio中使用默认的空活动创建新项目。
2.将Google Maven存储库添加到顶级build.gradle:
allprojects {
repositories {
maven { url 'https://maven.google.com' }
jcenter()
}
}
3. 将Room依赖项添加到app / build.gradle:
compile "android.arch.lifecycle:extensions:1.1.0"
compile "android.arch.persistence.room:runtime:1.1.0"
annotationProcessor "android.arch.lifecycle:compiler:1.1.0"
annotationProcessor "android.arch.persistence.room:compiler:1.1.0-alpha1"
4. 创建一个名为Event的实体。 这个表将存储用户在应用中创建的倒计时事件列表。 我们使用@Entity注释和表的名称(在这种情况下为事件)注释该类。 使用@PrimaryKey注释注释id字段,并选择autoGenerate我们可以在这种情况下设置为true的字段。 然后,Room将使用对象中定义的字段自动创建表格。
@Entity(tableName = TABLE_NAME)
public class Event {
public static final String TABLE_NAME = "events";
public static final String DATE_FIELD = "date";
@PrimaryKey(autoGenerate = true)
private int id;
private String name;
private String description;
@ColumnInfo(name = DATE_FIELD)
private LocalDateTime date;
public Event(int id, String name, String description, LocalDateTime date) {
this.id = id;
this.name = name;
this.description = description;
this.date = date;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public LocalDateTime getDate() {
return date;
}
@Override
public String toString() {
return "Event{" +
"id=" + id +
", name='" + name + ''' +
", description='" + description + ''' +
", date=" + date +
'}';
}
public long getDaysUntil() {
return ChronoUnit.DAYS.between(LocalDateTime.now(), getDate());
}
}
5. 通过创建名为EventDao的接口来创建数据访问对象(或DAO)。 使用@Dao注释注释类。 然后Room将生成一个类实现,它实现接口中定义的方法(非常类似于Retrofit的工作方式)。 我们可以在这里使用不同的注释,注释如@ Query,@ Delete,@ Insert,@ Update。 @Query注释可以采用SQL结构化查询。 关于这一点的重要部分是在这些脚本上发生的编译时间检查。 例如:如果您错误地键入了表格,则在您更正此选项之前,Room不允许您编译应用程序。
@Dao
public interface EventDao {
@Query("SELECT * FROM " + Event.TABLE_NAME + " WHERE " + Event.DATE_FIELD + " > :minDate")
LiveData<List<Event>> getEvents(LocalDateTime minDate);
@Insert(onConflict = REPLACE)
void addEvent(Event event);
@Delete
void deleteEvent(Event event);
@Update(onConflict = REPLACE)
void updateEvent(Event event);
}
6. 创建一个名为EventDatabase的抽象类,这将是连接实体(或表)的地方。该类应继承RoomDatabase。
@Database(entities = {Event.class}, version = 1)
@TypeConverters(DateTypeConverter.class)
public abstract class EventDatabase extends RoomDatabase {
public abstract EventDao eventDao();
}
您将注意到返回我们刚刚创建的EventDao的抽象方法eventDao()。 Room在运行时返回此类的实例。
值得注意的是,@ TypeConverters(DateTypeConverter.class)注释会自动将LocalDateTime对象日期序列化为其String格式,并在从存储中读取时将其反序列化为LocalDateTime对象。 下面是DateTypeConverter类的示例类定义:
public class DateTypeConverter {
@TypeConverter
public static LocalDateTime toDate(Long timestamp) {
//.. convert
}
@TypeConverter
public static Long toTimestamp(LocalDateTime date) {
//.. convert
}
}
7.使用Room.databaseBuilder(...)创建单例EventDatabase对象。我们还可以使用Room.inMemoryDatabaseBuilder(..)方法创建内存数据库。我们可以使用Dagger轻松完成此操作或手动创建单例。使用Dagger,我们的模块如下所示
@Module
public class CountdownModule {
private CountdownApplication countdownApplication;
public CountdownModule(CountdownApplication countdownApplication) {
this.countdownApplication = countdownApplication;
}
@Provides
Context applicationContext() {
return countdownApplication;
}
@Provides
@Singleton
EventRepository providesEventRepository(EventDatabase eventDatabase) {
return new EventRepositoryImpl(eventDatabase);
}
@Provides
@Singleton
EventDatabase providesEventDatabase(Context context) {
return Room.databaseBuilder(context.getApplicationContext(), EventDatabase.class, "event_db").build();
}
}
现在我们有了添加项目,查询和删除的结构,我们可以使用它们并讨论我们在上面定义的EventDao类中使用的LiveData类。
什么是LiveData?
LiveData允许您观察应用程序的多个组件之间的数据更改,而无需在它们之间创建明确且严格的依赖关系路径。 LiveData将遵守活动和片段的不同生命周期。将LiveData与Room结合使用时,它使您能够接收自动数据库更新,这些更新很难通过使用标准SQLiteDatabase来实现
- 1创建一个名为EventListActivity的活动和一个名为EventListFragment的片段。 在活动内部,inflate片段。 确保您的片段扩展Fragment(支持库版本)。
- 在片段中,添加一个RecyclerView,EventAdapter和EventViewHolder,它们将用于显示我们的事件列表。
- 在片段中,我们可以轻松获取对EventDatabase的引用,并在添加新项目时观察数据库中的事件。 在可观察的回调中,我们可以在适配器上设置项目。 我们可以使用Dagger注入EventDatabase对象。
eventDao = eventDatabase.eventDao();
eventDao.getEvents().observe(this, events -> {
Log.d(TAG, "Events Changed:" + events);
adapter.setItems(events);
});
通过将this作为第一个参数传递,将自动为您管理LiveData observable。这意味着当片段不再使用时,片段将负责处理可观察量。 LiveData类是LifecycleObserver的一个示例。当Lifecycle处于Lifecycle.State.DESTROYED状态时,它会自动停止发送更新,并在Lifecycle处于Lifecycle.State.STARTED状态时重新启动发送更新。
使用Room添加新事件
- 1创建一个包含两个EditText字段,日期选择器和保存按钮的新片段。
- 保存按钮的onClickListener只需调用eventDatabase.eventDao()。addEvent()即可轻松写入事件数据库。(注意:这应该从后台线程调用,在本例中我使用了RxJava Completable,但如果你愿意,可以使用AsyncTasks)。
String eventTitle = editTextTitle.getText().toString();
String eventDescription = editTextDescription.getText().toString();
Event event = new Event(0, eventTitle, eventDescription, eventDateTime);
eventDatabase.eventDao().addEvent(event); //Run this in a background thread.
我们现在可以访问数据库,我们可以从UI轻松插入或查询数据库。
综上所述
Room是一个易于使用的库,它包含Android上的SQLite实现。 它还提供了一个直观的界面来处理Objects而不是Cursors或ContentProviders。 使用带有LiveData的房间是真正的魔法发生的地方。 它允许通知视图有关数据更改的信息,这可能很难通过标准SQLiteDatabase实现。
在活动或片段中直接加载数据有一些缺陷。 主要问题是您的活动或片段与数据库紧密耦合。 如果您想在其他地方添加测试或重用逻辑,这不是一个好方法。 从View逻辑中分离数据库逻辑是一种更好的方法。 ViewModel架构组件旨在解决此问题。