之前一直都用 MVC 架構開發(fā) android, 所有的業(yè)務邏輯都寫在 Activity 中,感覺一般情況下 MVC 就夠用了?,F(xiàn)在來看一個官方推薦的 AAC 架構。
主要參考: https://developer.android.google.cn/topic/libraries/architecture/guide.html

AAC 中主要多了一個 ViewModel, 業(yè)務代碼不再堆在 Activity (或 Fragment ) 中而是寫在 ViewModel 中。ViewModel 負責取數(shù)據(jù)、更新數(shù)據(jù)、處理業(yè)務邏輯。
下面以一個請求 Github 用戶信息顯示用戶id和用戶名的 demo 來看一下 AAC
用戶數(shù)據(jù): 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 做網(wǎng)絡請求
為了支持 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);
}
}
現(xiàn)在 Activity、View、ViewModel 連接在一起了,當 ViewModel 中獲取到用戶數(shù)據(jù)之后怎么更新 UI 呢????這就用到了 LiveData
LiveData 是一個數(shù)據(jù)容器,實現(xiàn)了觀察者模式,對數(shù)據(jù)進行監(jiān)聽,當數(shù)據(jù)發(fā)生變化時會通知所有的觀察者
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 中就能監(jiān)聽 User 的變化了
viewModel.getUser().observe(this, user -> {
if (user != null) {
mUserInfo.setText(user.getId() + "\n" + user.getName());
}
});
下面請求網(wǎng)絡數(shù)據(jù)
public interface UserService {
@GET("users/lesliebeijing")
Call<User> getUser();
}
當然可以直接在 ViewModel 進行網(wǎng)絡請求更新數(shù)據(jù),但為了更好的隔離這里增加一層 Repository, Repository 負責底層所有的數(shù)據(jù)交互,提供單一接口,向上層屏蔽底層數(shù)據(jù)訪問細節(jié)
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;
}
}
網(wǎng)絡請求成功 setValue 設置新的數(shù)據(jù)后 activity 中的監(jiān)聽就會執(zhí)行更新 UI 。
setValue 只能在主線程中執(zhí)行, 在普通線程中可調(diào)用 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;
}
}
第一階段結(jié)束, 查看源碼: https://github.com/lesliebeijing/aac-demo/tree/step1
官方 Demo 中用了 Dagger2 做依賴注入, DI 方便測試,但過多的使用 DI 也會導致代碼不清晰,我自己不是很喜歡用 DI, 所有 Demo 中沒有使用。
第二階段看看 Room 持久化
實現(xiàn)一個簡單的邏輯:如果數(shù)據(jù)庫中有緩存的用戶信息直接顯示,否則請求網(wǎng)絡獲取數(shù)據(jù)寫入數(shù)據(jù)庫

Room 是一個 ORM ,可以返回 LiveData 對數(shù)據(jù)庫的變化進行監(jiān)聽,也可以返回普通的對象。目前見過的最好用的 ORM 了。
首先為 User 添加注解
@Entity
public class User {
@PrimaryKey
private int id;
private String name;
// 省略...
}
接著創(chuàng)建 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;
}
}
最后創(chuàng)建 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(),如果用戶信息不存在會請求網(wǎng)絡獲取數(shù)據(jù)后寫入數(shù)據(jù)庫,Room 會監(jiān)聽數(shù)據(jù)庫的變化通過 LiveData 回調(diào)到 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());
}
}
第二階段結(jié)束,查看源碼 https://github.com/lesliebeijing/aac-demo/tree/step2
第三階段,看一下 Loading 狀態(tài)和錯誤信息處理
要監(jiān)聽請求狀態(tài)和錯誤可以把用戶數(shù)據(jù)和請求狀態(tài)包裝在一起返回
定義狀態(tài)
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);
}
}
下面我們實現(xiàn)一種比較常見的場景,如果數(shù)據(jù)庫有數(shù)據(jù)先顯示,然后根據(jù)情況決定是否請求數(shù)據(jù),請求數(shù)據(jù)成功后把新數(shù)據(jù)寫入數(shù)據(jù)庫
封裝一個通用的類來處理這種場景
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 可以監(jiān)聽多個數(shù)據(jù)源,并把多個數(shù)據(jù)源的事件合并在一起發(fā)送
改造 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();
}
注意現(xiàn)在返回 Resource<User> 了
最后前臺就可以根據(jù)狀態(tài)顯示 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