什么是 architecture component?解决了什么问题?
它是一组库,能帮助你设计出一个具有健壮的,易测的,可维护的应用程序。
由哪些库组件的
- LifeCycle-Aware Components
- LiveData
- ViewModel
- Room
这些库都有什么作用
LifeCycle-Aware Components
-
什么是LifeCycle-Aware Components
一个 LifeCycle-Aware Components 是由 Lifecycle,LifecycleOwner组成的。
(1) 什么是 Lifecycle?
Lifecycle 是一个用来保存组件生命周期的状态信息的类,它允许其他对象观察这些状态信息。主要使用了Event 和 State 这两个枚举类来跟踪绑定的组件的生命周期状态信息。
-
Event
生命周期事件。这些事件与 activities 和 fragments 的事件形成映射关系。
-
State
组件的生命周期状态,由 Lifecycle 对象进行跟踪。
-
(2) 什么是 LifecycleOwner ?干啥用的呢?
LifecycleOwner 是只有一个方法的接口,表示这个类有一个 Lifecycle。只有一个 getLifecycle()
方法。
-
适用场景
如果一个库提供的类需要使用 Android 生命周期,建议使用 lifecycle-aware component。
a. 从粗糙的显示界面到精细的显示界面的切换。
b. 停止和开始视频缓冲。
c. 开始和停止网络连接。
d. 暂停和恢复动画效果。
示例
没有 LifeCycle 时,我们是这么实现定位功能的:
class MyLocationListener {
public MyLocationListener(Context context, Callback callback) {
// ...
}
void start() {
// connect to system location service
}
void stop() {
// disconnect from system location service
}
}
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
@Override
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, (location) -> {
// update UI
});
}
@Override
public void onStart() {
super.onStart();
myLocationListener.start();
// manage other components that need to respond
// to the activity lifecycle
}
@Override
public void onStop() {
super.onStop();
myLocationListener.stop();
// manage other components that need to respond
// to the activity lifecycle
}
}
使用 LifeCycle 来实现定位功能:
class MyLocationListener implements LifecycleObserver {
private boolean enabled = false;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
lifecycle.addObserver(this);
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
void start() {
if (enabled) {
// connect
}
}
public void enable() {
enabled = true;
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
// update UI
});
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.enable();
}
});
}
}
LiveData
-
什么是 LiveData
LiveData 是一种持有可被观察数据的类。和其他可被观察的类不同的是,LiveData 是有生命周期感知能力的,这意味着它可以在 activities, fragments 或者 services 生命周期是活跃状态时更新这些组件。那么什么是活跃状态呢?lifecycle 中提到的 STARTED 和 RESUMED就是活跃状态,只有在这两个状态下LiveData 是会通知数据变化的。
使用 LiveData,必须配合实现了 LifecycleOwner 的对象。当对应的生命周期对象 DESTORY 时,才能移除观察者。对于 activities 和 fragments 非常重要,因为他们可以在生命周期结束的时候立刻解除对数据的订阅,从而避免内存泄漏等问题。
-
LiveData 优点
- 界面与数据保持一致性。
- 避免内存泄漏。
- 不会再产生由于Activity处于stop状态而引起的崩溃。如果观察者的生命周期是不活跃的,例如 activity 处于后台,那么将不会收到任何 LiveData 事件。
- 不再需要手动的管理生命周期。
- 总是实时刷新数据。当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据
- 适当的configuration changes。如果 activity 或者 fragment 因为 configuration change 重新创建,例如设备旋转,立刻能收到最新的数据。
- 资源共享。使用单例模式来扩展 LiveData 包装系统服务,数据可以在你的应用程序间共享。
-
LiveData 示例
LiveData 其实是一个包装类,适用于任何数据类型,通常与 ViewModel 共同使用。如下示例演示了如何创建一个 LiveData 对象。
public class UserProfileViewModel extends ViewModel { ... // private User user; private LiveData<User> user; public LiveData<User> getUser() { return user; } }
ViewModel
-
什么是 ViewModel
ViewModel设计的目的就是存放和处理和UI相关的数据,并且这些数据不受配置变化(Configuration Changes,例如:旋转屏幕,组件被系统回收)的影响。
ViewModel 的作用
(1)避免由于(Activity/Fragment)被系统随时销毁或重新创建引起的数据丢失。对于简单的数据可以使用 onSaveInstanceState()
保存,在onCreate()
中恢复。对于少量的用户数据,比如UI状态是没有问题的。但是对于大量的数据,比如用户列表,这样做就会不合适。
(2)避免 UI 组件自己管理的维护成本以及容易产生的内存泄漏。由于 UI 组件频繁的异步请求,需要很多时间等待结果回调,UI 组件需要人为的管理这些回调。不仅浪费资源,还容易因为界面已经销毁而产生内存泄漏。
(3)避免职责过度集中。UI 组件需要对用户的操作作出响应,并且处理和操作系统的通信,同时还需要从数据库或者网络载入对应的数据。一个类所需要负责的事情太多了,不仅使代码臃肿,也造成了测试难度加大。
- ViewModel 示例
如下示例,定义一个 ViewModel:
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asyncronous operation to fetch users.
}
}
访问 ViewModel
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
当 Activity 旋转屏幕等重新创建时,oncreate 将收到与第一次创建 Activity 时相同的 MyViewModel 实例;当 Activity 销毁时,系统会调用 ViewModel 对象的 onCleared()
方法来清除数据。
注意:ViewModel 绝对不可以引用 view、Lifycycle 或者任何持有 context 的类。
-
ViewModel 的生命周期
ViewModel只有在Activity finish或者Fragment detach之后才会销毁。
Room
-
什么是 Room
Room 在 SQLite 上提供了一个方便访问的抽象层。最常使用的场景是缓存一些相关的数据,当设备无法连接上网络时,用户仍然可以浏览内容;当设备连上网络时,立刻更新用户数据。
-
Room 的组成
三个重要的组件:
Database:包含数据库访问入口
Entity:表示数据库中的表
DAO:包含访问数据库的方法
如何使用这些库
如何添加这些组件到工程
-
在根目录的 build.gradle 中,添加
google()
:allprojects { repositories { jcenter() google() } }
-
在 app 或者 要使用到的 module 的 build.gradle 中,添加类库依赖。
dependencies { // ViewModel and LiveData implementation "android.arch.lifecycle:extensions:1.1.0" // alternatively, just ViewModel implementation "android.arch.lifecycle:viewmodel:1.1.0" // alternatively, just LiveData implementation "android.arch.lifecycle:livedata:1.1.0" annotationProcessor "android.arch.lifecycle:compiler:1.1.0" // Room (use 1.1.0-alpha1 for latest alpha) implementation "android.arch.persistence.room:runtime:1.0.0" annotationProcessor "android.arch.persistence.room:compiler:1.0.0" // Paging implementation "android.arch.paging:runtime:1.0.0-alpha5" // Test helpers for LiveData testImplementation "android.arch.core:core-testing:1.1.0" // Test helpers for Room testImplementation "android.arch.persistence.room:testing:1.0.0" }
如果使用 Java 8,建议使用下面的类库取代 android.arch.lifecycle:compiler
dependencies { // Java8 support for Lifecycles implementation "android.arch.lifecycle:common-java8:1.1.0" }
单独使用 LifeCycles,不使用 LiveData 或 ViewModel,则建议使用下面的类库
dependencies { // Lifecycles only (no ViewModel or LiveData) implementation "android.arch.lifecycle:runtime:1.1.0" annotationProcessor "android.arch.lifecycle:compiler:1.1.0" }
如何应用
举个简单的例子:
以编写一个个人信息页面为例,由UserProfileFragment.java
和对应的布局文件user_profile_layout.xml
组成。
界面搭建
为了展示界面,我们需要两个数据:
- The User ID:用户的 ID,用于标识唯一的用户。
- The User object:个人信息的 POJO
由于个人信息不应该随着界面的销毁而重新初始化,并且通常有根据用户 ID 请求网络获取完整用户信息的需求,因此,使用 ViewModel 来存储用户数据。
public class UserProfileViewModel extends ViewModel {
private String userId;
private User user;
public void init(String userId) {
this.userId = userId;
}
public User getUser() {
return user;
}
}
因此,我们现在有三个文件:
- user_profile.xml: 个人信息布局文件
- UserProfileViewModel.java: 为界面提供数据的类
- UserProfileFragment.java: UI 界面展示类,负责数据展示和界面交互
public class UserProfileFragment extends Fragment {
private static final String UID_KEY = "uid";
private UserProfileViewModel viewModel;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String userId = getArguments().getString(UID_KEY);
viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
viewModel.init(userId);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.user_profile, container, false);
}
}
接下来,当 ViewModel 的 user 字段被设置,我们需要一个手段来通知 UI,这就用到了 LiveData。
LiveData的使用分为两个步骤:
- 用 LiveData<User> 来取代 User 字段,这样当数据更新时,fragment 可以收到通知。
public class UserProfileViewModel extends ViewModel {
...
// private User user;
private LiveData<User> user;
public LiveData<User> getUser() {
return user;
}
}
- 在 fragment 中观察数据并更新 UI
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel.getUser().observe(this, user -> {
// update UI
});
}
通过以上两个步骤的 LiveData 使用,每当数据更新时,onChanged 回调会被执行,UI可以在这里进行刷新。
tips:
(1)不需要在 fragment 的 onStop 方法中注销这个观察者,这就是 LiveData 的好处啦,除非 fragment 处于活跃状态,否则它不会执行回调函数;同时,当 fragment onDestroy 时,LiveData 会自动移除观察者。
(2)不需要处理 configuration changes(例如手机屏幕旋转)。ViewModel 会在屏幕旋转时,自动存储数据。当 fragment 重新创建时,会收到同一个 ViewModel 实例。
获取数据
前面已经把界面搭建好了,下面关心如何获取数据。
- 从网络获取数据
这里以 Retrofit 类库作为例子。
public interface Webservice {
/**
* @GET declares an HTTP GET request
* @Path("user") annotation on the userId parameter marks it as a
* replacement for the {user} placeholder in the @GET path
*/
@GET("/users/{user}")
Call<User> getUser(@Path("user") String userId);
}
可以直接在 ViewModel 中通过 Webservice 来获取数据,但是这样会使应用难以维护。ViewModel 承担了太多职责,代码太快臃肿。另外,ViewModel 与 Activity 或 Fragment 的生命周期相绑定,当生命周期结束以后,ViewModel 会释放掉相关的数据。所以把获取数据的功能,指派给 Repository 模块。
如下代码,UserRepository 使用 WebService 来获取数据。
public class UserRepository {
private Webservice webservice;
// ...
public LiveData<User> getUser(int userId) {
// This is not an optimal implementation, we'll fix it below
final MutableLiveData<User> data = new MutableLiveData<>();
webservice.getUser(userId).enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
// error case is left out for brevity
data.setValue(response.body());
}
});
return data;
}
}
然后修改 UserProfileViewModel 来使用 UserRepository:
public class UserProfileViewModel extends ViewModel {
private LiveData<User> user;
private UserRepository userRepo;
@Inject // UserRepository parameter is provided by Dagger 2
public UserProfileViewModel(UserRepository userRepo) {
this.userRepo = userRepo;
}
public void init(String userId) {
if (this.user != null) {
// ViewModel is created per Fragment so
// we know the userId won't change
return;
}
user = userRepo.getUser(userId);
}
public LiveData<User> getUser() {
return this.user;
}
}
现在,UserProfileViewModel 并不知道数据是从 Webservice 获取来的,也就是说,我们可以根据需要替换我们获取数据的实现方式。
- 从缓存获取数据
使用网络获取数据的问题是,如果用户离开了 fragment 然后再回来,应用需要重新通过网络获取数据,这样做有两个坏处:浪费宝贵的网络带宽和迫使用户等待新的查询完成。因此,增加从缓存获取数据的方法来避免这些问题。
@Singleton // informs Dagger that this class should be constructed once
public class UserRepository {
private Webservice webservice;
// simple in memory cache, details omitted for brevity
private UserCache userCache;
public LiveData<User> getUser(String userId) {
LiveData<User> cached = userCache.get(userId);
if (cached != null) {
return cached;
}
final MutableLiveData<User> data = new MutableLiveData<>();
userCache.put(userId, data);
// this is still suboptimal but better than before.
// a complete implementation must also handle the error cases.
webservice.getUser(userId).enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
data.setValue(response.body());
}
});
return data;
}
}
- 从数据库中获取数据
如果用户杀死这个 App,那么再回来,就无法从 cache中获取数据了,需要再次从网络拉取数据。隔了一段时间用户重新打开相同的页面仍然需要从网络获取相同的数据。Room 闪亮登场啦。
下面详细介绍如何使用 Room。
- 定义 User 类,并使用 @Entity 注解,标记这个类为数据库中的一张表
@Entity
class User {
@PrimaryKey
private int id;
private String name;
private String lastName;
// getters and setters for fields
}
- 创建一个 database 类继承 RoomDatabase。
@Database(entities = {User.class}, version = 1)
public abstract class MyDatabase extends RoomDatabase {
}
注意:MyDatabase 是一个抽象类,Room 会自动提供它的实现类。
- 创建一个 DAO ,将 user 数据插入到数据库中。
@Dao
public interface UserDao {
@Insert(onConflict = REPLACE)
void save(User user);
@Query("SELECT * FROM user WHERE id = :userId")
LiveData<User> load(String userId);
}
然后,在 MyDatabase 中引用 UserDao
@Database(entities = {User.class}, version = 1)
public abstract class MyDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
注意:
UserDao
中load()
方法返回的是LiveData<User>
。这就是为什么Room能够在数据库内容发生改变的时候通知对数据库内容感兴趣的类。
然后修改 UserRepository 包含 Room 数据源
@Singleton
public class UserRepository {
private final Webservice webservice;
private final UserDao userDao;
private final Executor executor;
@Inject
public UserRepository(Webservice webservice, UserDao userDao, Executor executor) {
this.webservice = webservice;
this.userDao = userDao;
this.executor = executor;
}
public LiveData<User> getUser(String userId) {
refreshUser(userId);
// return a LiveData directly from the database.
return userDao.load(userId);
}
private void refreshUser(final String userId) {
executor.execute(() -> {
// running in a background thread
// check if user was fetched recently
boolean userExists = userDao.hasUser(FRESH_TIMEOUT);
if (!userExists) {
// refresh the data
Response response = webservice.getUser(userId).execute();
// TODO check for error etc.
// Update the database.The LiveData will automatically refresh so
// we don't need to do anything else here besides updating the database
userDao.save(response.body());
}
});
}
}
从上面的代码可以看出,即使是改变了UserRepository
中的数据来源,也无需对UserProfileViewModel
或者UserProfileFragment
作任何更改。这就是抽象出了数据层的灵活性。
现在基本上就解决之前提到的一些问题。如果已经查看过某个用户的数据,那么再次打开这个用户的页面时,数据可以瞬间被展示。如果数据过期的话,还可以立刻去更新数据。当然,如果你规定了数据过期就不允许展示,也可以展示从网络获取的数据。
最终的架构
总结
- LifeCycle-Aware Components 是能够感知生命周期的组件。如果一个类库需要关心 Android 的生命周期,比如开始和停止视频的缓冲,用户通常需要自己管理 Android 的各个生命周期。使用 LifeCycle,这些繁琐的工作就可以不再操心,可以有效的避免内存泄漏,同时可以做到代码功能的解耦。
- LiveData 是能够感知生命周期的数据,不需要开发者自己关心组件的生命周期,只需要通过观察者,就可以在生命周期的激活状态下更新 UI 数据。通常与 ViewModel 共同使用。
- ViewModel 用来存储与管理 UI 数据。
(1)由于他的生命周期从组件 oncreate 创建时就存在,直到组件被彻底销毁才会消失。期间如果旋转屏幕,组件重新创建,会得到同一个 ViewModel 实例。所以 ViewModel 可以用于处理 UI 组件的相关数据。
(2)由于 ViewModel 在一个 Activity 中只有一个实例,并且贯穿整个 Activity的生命周期。所以 ViewModel 可用于 fragment 之间共享数据。减轻 Activity 的工作,同时使 fragment 之间解耦。 - Room 提供了一个 SQLite 之上的抽象层,使得在充分利用 SQLite 功能的前提下简单顺畅的访问数据库。