面对愈来愈复杂的 App 需求,Google 官方发布了Android 框架组件库(Android Architecture Components )。为开发者更好的开发 App 提供了很是好的样本。这个框架里的组件是配合 Android 组件生命周期的,因此它可以很好的规避组件生命周期管理的问题。今天咱们就来看看这个库的使用。html
官方建议在架构 App 的时候遵循如下两个准则:java
关注分离android
其中早期开发 App 最多见的作法是在 Activity 或者 Fragment 中写了大量的逻辑代码,致使 Activity 或 Fragment 中的代码很臃肿,十分不易维护。如今不少 App 开发者都注意到了这个问题,因此前两年 MVP 结构就很是有市场,目前普及率也很高。git
模型驱动UIgithub
模型持久化的好处就是:即便系统回收了 App 的资源用户也不会丢失数据,并且在网络不稳定的状况下 App 依然能够正常地运行。从而保证了 App 的用户体验。web
框架提供了如下几个核心组件,咱们将经过一个实例来讲明这几个组件的使用。数据库
假设要实现一个用户信息展现页面。这个用户信息是经过REST API 从后台获取的。缓存
咱们使用 fragment (UserProfileFragment.java) 来实现用户信息的展现页面。为了驱动 UI,咱们的数据模型须要持有如下两个数据元素微信
咱们要建立 ViewModel 对象用于保存以上数据。网络
那什么是 ViewModel 呢?
A ViewModel provides the data for a specific UI component, such as a fragment or activity, and handles the communication with the business part of data handling, such as calling other components to load the data or forwarding user modifications. The ViewModel does not know about the View and is not affected by configuration changes such as recreating an activity due to rotation.
ViewModel 是一个框架组件。它为 UI 组件 (fragment或activity) 提供数据,而且能够调用其它组件加载数据或者转发用户指令。ViewModel 不会关心 UI 长什么样,也不会受到 UI 组件配置改变的影响,例如不会受旋转屏幕后 activity 从新启动的影响。所以它是一个与 UI 组件无关的。
public class UserProfileViewModel extends ViewModel { private String userId; private User user; public void init(String userId) { this.userId = userId; } public User getUser() { return user; } }
public class UserProfileFragment extends LifecycleFragment { 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); } }
须要的是:因为框架组件目前还处于预览版本,这里 UserProfileFragment
是继承于 LifecycleFragment
而不是 Fragment
。待正式发布版本以后 Android Support 包中的 Fragment
就会默认实现 LifecycleOwner
接口。而 LifecycleFragment
也是实现了 LifecycleOwner
接口的。即正式版本发布时 Support 包中的 UI 组件类就是支持框架组件的。
如今已经有了 UI 组件和 ViewModel,那么咱们如何将它们进行链接呢?这时候就须要用到 LiveData 组件了。
LiveData is an observable data holder. It lets the components in your app observe
LiveData
objects for changes without creating explicit and rigid dependency paths between them. LiveData also respects the lifecycle state of your app components (activities, fragments, services) and does the right thing to prevent object leaking so that your app does not consume more memory.
LiveData 的使用有点像 RxJava。所以彻底可使用 RxJava 来替代 LiveData 组件。
如今咱们修改一下 UserProfileViewModel
类
public class UserProfileViewModel extends ViewModel { ... private LiveData<User> user; public LiveData<User> getUser() { return user; } }
将 Useruser
替换成 LiveData<User>user
而后再修改 UserProfileFragment
类中
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); viewModel.getUser().observe(this, user -> { // update UI }); }
当用户数据发生改变时,就会通知 UI 进行更新。ViewModel 与 UI 组件的交互就是这么简单。
但细心的朋友可能发现了:fragment 在 onActivityCreated
方法中添加了相应的监听,可是没有在其它对应的生命周期中移除监听。有经验的朋友就会以为这是否是有可能会发生引用泄露问题呢?其实否则,LiveData 组件内部已经为开发者作了这些事情。即 LiveData 会再正确的生命周期进行回调。
如今已经成功的把 ViewModel 与 UI 组件(fragment)进行了通讯。那么 ViewModel 又是如何获取数据的呢?
假设咱们的数据是经过REST API 从后天获取的。咱们使用 Retrofit 库实现网络请求。
如下是请求网络接口 Webservice
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
接口,可是这样作违背了咱们在上文提到的关注分离准则。由于咱们推荐使用 Repository
模型对 Webservice
进行封装。
Repository modules are responsible for handling data operations. They provide a clean API to the rest of the app. They know where to get the data from and what API calls to make when data is updated. You can consider them as mediators between different data sources (persistent model, web service, cache, etc.).
关于 Repository 模式能够参考个人上一篇《App 组件化/模块化之路——Repository模式》
如下是使用 Repository 封装 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; } }
使用 Respository 模式抽象数据源接口,也能够很方便地替换其它数据。这样 ViewModel 也不用知道数据源究竟是来自哪里。
从上文咱们知道 UserRepository
类须要有一个 WebService
实例才能工做。咱们能够直接建立它,但这么作咱们就必须知道它的依赖,并且会由不少重复的建立对象的代码。这时候咱们可使用依赖注入。本例中咱们将使用 Dagger 2 来管理依赖。
修改 UserProfileViewModel
类,引用 Repository 而且经过 Dagger 2 对 Repository 的依赖进行管理。
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; } }
前面咱们实现的 Repository 是只有一个网络数据源的。这样作每次进入用户信息页面都须要去查询网络,用户须要等待,体验很差。所以在 Repository 中加一个缓存数据。
@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; } }
Android 框架提供了 Room 组件,为 App 数据持久化提供了解决方案。
Room is an object mapping library that provides local data persistence with minimal boilerplate code. At compile time, it validates each query against the schema, so that broken SQL queries result in compile time errors instead of runtime failures. Room abstracts away some of the underlying implementation details of working with raw SQL tables and queries. It also allows observing changes to the database data (including collections and join queries), exposing such changes via LiveData objects. In addition, it explicitly defines thread constraints that address common issues such as accessing storage on the main thread.
Room 组件提供了数据库操做,配合 LiveData 使用能够监听数据库的变化,进而更新 UI 组件。
要使用 Room 组件,须要如下步骤:
@Entity
定义实体RoomDatabase
子类RoomDatabase
中引用 DAO 1. 用注解 @Entity
定义实体类
@Entity class User { @PrimaryKey private int id; private String name; private String lastName; // getters and setters for fields }
2. 建立 RoomDatabase
子类
@Database(entities = {User.class}, version = 1) public abstract class MyDatabase extends RoomDatabase { }
须要注意的是 MyDatabase
是抽象类,Room 组件为咱们提供具体的实现。
3. 建立 DAO
@Dao public interface UserDao { @Insert(onConflict = REPLACE) void save(User user); @Query("SELECT * FROM user WHERE id = :userId") LiveData<User> load(String userId); }
4. 在 RoomDatabase
中引用 DAO
@Database(entities = {User.class}, version = 1) public abstract class MyDatabase extends RoomDatabase { public abstract UserDao userDao(); }
如今有了 Room 组件,那么咱们能够修改 UserRepository
类
@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()); } }); } }
目前为止咱们的代码就基本完成了。UI 组件经过 ViewModel 访问数据,而 ViewModel 经过 LiveData 监听数据的变化,而且使用 Repository 模式封装数据源。这些数据源能够是网络数据,缓存以及持久化数据。
参考文档:
https://developer.android.com/topic/libraries/architecture/guide.html#recommendedapparchitecture
https://github.com/googlesamples/android-architecture-components
微信关注咱们,能够获取更多