Android Architecture Component 學習

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

aac.png

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.png

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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容