美文网首页
Android Architecture Component 学

Android Architecture Component 学

作者: lesliefang | 来源:发表于2018-02-27 17:27 被阅读110次

    之前一直都用 MVC 架构开发 android, 所有的业务逻辑都写在 Activity 中,感觉一般情况下 MVC 就够用了。现在来看一个官方推荐的 AAC 架构。
    主要参考: https://developer.android.google.cn/topic/libraries/architecture/guide.html

    aac.png

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

    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

    相关文章

      网友评论

          本文标题:Android Architecture Component 学

          本文链接:https://www.haomeiwen.com/subject/uqlmxftx.html