之前一直都用 MVC 架构开发 android, 所有的业务逻辑都写在 Activity 中,感觉一般情况下 MVC 就够用了。现在来看一个官方推荐的 AAC 架构。
主要参考: https://developer.android.google.cn/topic/libraries/architecture/guide.html
AAC 中主要多了一个 ViewModel, 业务代码不再堆在 Activity (或 Fragment ) 中而是写在 ViewModel 中。ViewModel 负责取数据、更新数据、处理业务逻辑。
下面以一个请求 Github 用户信息显示用户id和用户名的 demo 来看一下 AAC
用户数据: https://api.github.com/users/lesliebeijing
首先引入依赖(基于 Android Studio 3.0 + Java8)
// Java8 support for Lifecycles
implementation "android.arch.lifecycle:common-java8:1.1.0"
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:1.1.0"
// Room
implementation "android.arch.persistence.room:runtime:1.0.0"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
// Retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
AS3.0 开始支持 Java8
这里使用 Retrofit2 做网络请求
为了支持 Java8 , gradle 中要加入下面的配置
android {
// ....
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
定义 User 对象
public class User {
private int id;
private String name;
// 省略
}
下面我们要定义一个显示用户信息的 ViewModel
public class UserProfileViewModel extends ViewModel {
private User user;
public User getUser() {
return user;
}
}
ViewModel 中定义了要显示的用户信息 user
下面要在 Activity (或 Fragment)中实例化 ViewModel
public class MainActivity extends BaseActivity {
private TextView mUserInfo;
private UserProfileViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initProcessBar();
mUserInfo = findViewById(R.id.userInfo);
viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
}
}
现在 Activity、View、ViewModel 连接在一起了,当 ViewModel 中获取到用户数据之后怎么更新 UI 呢????这就用到了 LiveData
LiveData 是一个数据容器,实现了观察者模式,对数据进行监听,当数据发生变化时会通知所有的观察者
LiveData 还能感知 Lifecycle ,只有当 LifecycleOwner (Activity 、Fragment、Service) active (OnStart 之后,OnStop 之前) 时才会通知
所以我们把 User 变成 LiveData
public class UserProfileViewModel extends ViewModel {
private LiveData<User> user;
public LiveData<User> getUser() {
return user;
}
}
这样在 Activity 中就能监听 User 的变化了
viewModel.getUser().observe(this, user -> {
if (user != null) {
mUserInfo.setText(user.getId() + "\n" + user.getName());
}
});
下面请求网络数据
public interface UserService {
@GET("users/lesliebeijing")
Call<User> getUser();
}
当然可以直接在 ViewModel 进行网络请求更新数据,但为了更好的隔离这里增加一层 Repository, Repository 负责底层所有的数据交互,提供单一接口,向上层屏蔽底层数据访问细节
public class UserRepository {
private UserService userService;
public UserRepository() {
userService = RetrofitClient.retrofit().create(UserService.class);
}
public LiveData<User> getUser() {
final MutableLiveData<User> user = new MutableLiveData<>();
userService.getUser().enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
user.setValue(response.body());
}
@Override
public void onFailure(Call<User> call, Throwable t) {
}
});
return user;
}
}
网络请求成功 setValue 设置新的数据后 activity 中的监听就会执行更新 UI 。
setValue 只能在主线程中执行, 在普通线程中可调用 postValue
下面我们就可以在 ViewModel 中使用 UserRepository
public class UserProfileViewModel extends ViewModel {
private LiveData<User> user;
private UserRepository userRepository;
public UserProfileViewModel() {
userRepository = new UserRepository();
}
public LiveData<User> getUser() {
user = userRepository.getUser();
return user;
}
}
第一阶段结束, 查看源码: https://github.com/lesliebeijing/aac-demo/tree/step1
官方 Demo 中用了 Dagger2 做依赖注入, DI 方便测试,但过多的使用 DI 也会导致代码不清晰,我自己不是很喜欢用 DI, 所有 Demo 中没有使用。
第二阶段看看 Room 持久化
实现一个简单的逻辑:如果数据库中有缓存的用户信息直接显示,否则请求网络获取数据写入数据库
Room 是一个 ORM ,可以返回 LiveData 对数据库的变化进行监听,也可以返回普通的对象。目前见过的最好用的 ORM 了。
首先为 User 添加注解
@Entity
public class User {
@PrimaryKey
private int id;
private String name;
// 省略...
}
接着创建 Dao
@Dao
public interface UserDao {
@Insert(onConflict = REPLACE)
void save(User user);
@Query("SELECT * FROM user LIMIT 1")
LiveData<User> load();
@Query("SELECT COUNT(*) FROM user")
int getUserCount();
default boolean userExists() {
return getUserCount() > 0;
}
}
最后创建 Database
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase appDb;
public static final String DB_NAME = "mydb";
public abstract UserDao userDao();
public static AppDatabase getInstance(Context context) {
if (appDb == null) {
synchronized (AppDatabase.class) {
if (appDb == null) {
appDb = Room.databaseBuilder(context, AppDatabase.class, DB_NAME).build();
}
}
}
return appDb;
}
}
单例模式
下面改造 Repository
public class UserRepository {
private UserService userService;
private UserDao userDao;
public UserRepository(Context context) {
userService = RetrofitClient.retrofit().create(UserService.class);
userDao = AppDatabase.getInstance(context).userDao();
}
public LiveData<User> getUser() {
Executors.newSingleThreadExecutor().execute(() -> {
if (!userDao.userExists()) {
Log.d("leslie", "not exists fetch from network");
try {
Response<User> response = userService.getUser().execute();
userDao.save(response.body());
} catch (IOException e) {
e.printStackTrace();
}
}
});
return userDao.load();
}
}
getUser 直接返回 userDao.load(),如果用户信息不存在会请求网络获取数据后写入数据库,Room 会监听数据库的变化通过 LiveData 回调到 UI层
Database 的实例化需要 Context 对象,让 UserProfileViewModel 改为继承 AndroidViewModel 就可以获取到 Context 对象
public class UserProfileViewModel extends AndroidViewModel {
private UserRepository userRepository;
public UserProfileViewModel(@NonNull Application application) {
super(application);
userRepository = new UserRepository(getApplication());
}
}
第二阶段结束,查看源码 https://github.com/lesliebeijing/aac-demo/tree/step2
第三阶段,看一下 Loading 状态和错误信息处理
要监听请求状态和错误可以把用户数据和请求状态包装在一起返回
定义状态
public enum Status {
SUCCESS,
ERROR,
LOADING
}
定义一个 Resource 类
public class Resource<T> {
public final Status status;
public final T data;
public final String message;
private Resource(Status status, T data, String message) {
this.status = status;
this.data = data;
this.message = message;
}
public static <T> Resource<T> success(T data) {
return new Resource<>(Status.SUCCESS, data, null);
}
public static <T> Resource<T> error(String message, T data) {
return new Resource<>(Status.ERROR, data, message);
}
public static <T> Resource<T> loading(T data) {
return new Resource<>(Status.LOADING, data, null);
}
}
下面我们实现一种比较常见的场景,如果数据库有数据先显示,然后根据情况决定是否请求数据,请求数据成功后把新数据写入数据库
封装一个通用的类来处理这种场景
public abstract class NetworkBoundResource<T> {
private Executor executor;
private MediatorLiveData<Resource<T>> result = new MediatorLiveData<>();
public NetworkBoundResource() {
executor = Executors.newSingleThreadExecutor();
result.setValue(Resource.loading(null));
final LiveData<T> dbSource = loadFromDb();
result.addSource(dbSource, data -> {
result.removeSource(dbSource);
if (shouldFetch()) {
fetchFromNetwork(dbSource);
} else {
result.setValue(Resource.success(data));
}
});
}
private void fetchFromNetwork(final LiveData<T> dbSource) {
result.addSource(dbSource, data -> result.setValue(Resource.loading(data)));
loadData().enqueue(new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) {
result.removeSource(dbSource);
if (response.isSuccessful()) {
executor.execute(() -> {
saveToDb(response.body());
result.addSource(loadFromDb(), data -> result.setValue(Resource.success(data)));
});
} else {
String message = null;
if (response.errorBody() != null) {
try {
message = response.errorBody().string();
} catch (IOException e) {
e.printStackTrace();
}
}
final String errorMessage = message;
result.addSource(dbSource, data -> result.setValue(Resource.error(errorMessage, data)));
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
result.removeSource(dbSource);
result.addSource(dbSource, data -> result.setValue(Resource.error(t.getMessage(), data)));
}
});
}
protected abstract Call<T> loadData();
// 默认 true ,子类可复写
protected boolean shouldFetch() {
return true;
}
protected abstract void saveToDb(T data);
protected abstract LiveData<T> loadFromDb();
public LiveData<Resource<T>> getResult() {
return result;
}
}
MediatorLiveData 可以监听多个数据源,并把多个数据源的事件合并在一起发送
改造 UserRepository
public LiveData<Resource<User>> getUser() {
return new NetworkBoundResource<User>() {
@Override
protected Call<User> loadData() {
return userService.getUser();
}
@Override
protected void saveToDb(User user) {
userDao.save(user);
}
@Override
protected LiveData<User> loadFromDb() {
return userDao.load();
}
}.getResult();
}
注意现在返回 Resource<User> 了
最后前台就可以根据状态显示 loading 和错误信息了
viewModel.getUser().observe(this, resource -> {
Log.d("leslie", "observe " + resource.status);
Status status = resource.status;
if (status == LOADING) {
showProcessBar();
setData(resource.data);
} else {
hideProcessBar();
if (status == SUCCESS) {
setData(resource.data);
} else {
showToast(resource.message);
}
}
});
官方 Demo 添加了 Retrofit adapter 让请求可以返回 LiveData, 这里没有这么做
查看源码 https://github.com/lesliebeijing/aac-demo
官方 sample: https://github.com/googlesamples/android-architecture-components
网友评论