App 组件化/模块化之路——Android 框架组件(Android Architecture Components)使用指南

简介: 面对越来越复杂的 App 需求,Google 官方发布了Android 框架组件库(Android Architecture Components )。为开发者更好的开发 App 提供了非常好的样本。这个框架里的组件是配合 Android 组件生命周期的,所以它能够很好的规避组件生命周期管理的问题。

面对越来越复杂的 App 需求,Google 官方发布了Android 框架组件库(Android Architecture Components )。为开发者更好的开发 App 提供了非常好的样本。这个框架里的组件是配合 Android 组件生命周期的,所以它能够很好的规避组件生命周期管理的问题。今天我们就来看看这个库的使用。

通用的框架准则

官方建议在架构 App 的时候遵循以下两个准则:

  1. 关注分离

    其中早期开发 App 最常见的做法是在 Activity 或者 Fragment 中写了大量的逻辑代码,导致 Activity 或 Fragment 中的代码很臃肿,十分不易维护。现在很多 App 开发者都注意到了这个问题,所以前两年 MVP 结构就非常有市场,目前普及率也很高。

  2. 模型驱动UI

    模型持久化的好处就是:即使系统回收了 App 的资源用户也不会丢失数据,而且在网络不稳定的情况下 App 依然可以正常地运行。从而保证了 App 的用户体验。

App 框架组件

框架提供了以下几个核心组件,我们将通过一个实例来说明这几个组件的使用。

  • ViewModel
  • LiveData
  • Room

假设要实现一个用户信息展示页面。这个用户信息是通过REST API 从后台获取的。

建立UI

我们使用 fragment (UserProfileFragment.java) 来实现用户信息的展示页面。为了驱动 UI,我们的数据模型需要持有以下两个数据元素

  • 用户ID: 用户的唯一标识。可以通过 fragment 的 arguments 参数进行传递这个信息。这样做的好处就是如果系统销毁了应用,这个参数会被保存并且下次重新启动时可以恢复之前的数据。
  • 用户对象数据:POJO 持有用户数据。

我们要创建 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 来管理依赖。

连接 ViewModel 和 Repository

修改 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;
    }
}

 

持久化数据 (Room 组件)

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 子类
  • 创建数据访问接口(DAO)
  • 在 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 模式封装数据源。这些数据源可以是网络数据,缓存以及持久化数据。

框架结构图

final architecture

参考文档:

https://developer.android.com/topic/libraries/architecture/guide.html#recommendedapparchitecture

https://github.com/googlesamples/android-architecture-components

微信关注我们,可以获取更多

目录
相关文章
|
3月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
3天前
|
算法 JavaScript Android开发
|
13天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
45 1
|
25天前
|
缓存 小程序 索引
uni-app开发微信小程序时vant组件van-tabs的使用陷阱及解决方案
uni-app开发微信小程序时vant组件van-tabs的使用陷阱及解决方案
133 1
|
26天前
|
存储 API 数据库
uniapp APP自动更新组件
uniapp APP自动更新组件
57 1
|
28天前
|
存储 前端开发 UED
uni-app:基础组件 (下)
本文介绍了多种前端组件及其用法,包括:label 组件用于表单元素的标签;picker 组件用于实现日期、时间及普通列表的选择器;textarea 组件用于输入多行文本,并可通过 v-model 双向绑定数据;process 组件用于显示进度条;swiper 组件用于轮播图展示;match-media 组件根据屏幕尺寸展示内容;audio 组件用于播放音频;switch 组件用于开关选择;scroll-view 组件实现滚动视图功能;以及 storage 的使用方法,如设置、获取和移除本地存储等。
|
28天前
|
存储 前端开发 JavaScript
uni-app:基础组件 (上)
本文介绍了uni-app中多个组件的使用方法,包括存储操作、图标展示、按钮样式、表单输入、导航跳转和输入框控制等。通过具体代码示例展示了如何设置存储键值、使用不同类型的按钮、实现表单提交与重置功能、控制输入框的显示与清除等功能。
|
2月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
307 3
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
71 8
|
2月前
|
移动开发 小程序 前端开发
uni-app组件样式修改不生效
uni-app组件样式修改不生效