以下内容为原创,欢迎转载,转载请注明

来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6237731.html

使用Dagger 2来构建UserScope

原文:http://frogermcs.github.io/building-userscope-with-dagger2/

在Dagger 2中自定义scopes可以在不寻常存活时间(与Application和界面生命周期不同的)的依赖上给我带来更好的控制。但是在Android app中正确地实现它需要记住几个事情:scope不能存活比application进程更长的周期,进程可以被系统杀死并且在更多的对象实例的用户流中恢复过来。今天我们将介绍所有这些,并尝试实现可用于生产的UserScope。

在我的其中一篇博客中写了关于自定义scopesSubcomponents。作为一个例子,我使用了UserScope,只要用户是登录状态就应该存活。一个scopes生命周期的例子:

虽然它看起来非常简单,它的实现在那篇文章中已经展示,但是它的代码是脱离与生产环境的。这就是为什么我想要又一次深入这个话题 - 但是会有更多实现的上下文细节。

Example app

我们将构建3个界面的应用,它可以从Github API获取用户详情(让我们假设这在生产环境app中作为一个认证的事件)。

App将会有3个scopes:

  • (Application scope, @Singleton) - 只要application存活依赖就存活。
  • @UserScope - 只要用户会话是激活状态依赖就存活(在单个应用程序中启动)。重要的是:这个scope存活时间不会超过application本身。每一个新的app实例都会创建一个新的@UserScope(甚至app不同的启动间用户会话没有关闭)。
  • @ActivityScope - 只要Activity界面存活依赖就存活。

它怎么工作的呢?

当app从Github API得到特定用户名的数据,新的界面会被打开(UserDetails)。在内部,LoginActivityPresenter访问UserManager来开启一个会话(从Github API获取数据)。当操作成功,用户保存到UserDataStore,并且UserManager创建UserComponent

  1. //UserManager
  2. public Observable<User> startSessionForUser(String username) {
  3. return githubApiService.getUser(username)
  4. .map(User.UserResponseToUser())
  5. .doOnNext(new Action1<User>() {
  6. @Override
  7. public void call(User user) {
  8. userDataStore.createUser(user);
  9. startUserSession();
  10. }
  11. })
  12. .subscribeOn(Schedulers.io())
  13. .observeOn(AndroidSchedulers.mainThread());
  14. }
  15. private boolean startUserSession() {
  16. User user = userDataStore.getUser();
  17. if (user != null) {
  18. Timber.i("Session started, user: %s", user);
  19. userComponent = userComponentBuilder.sessionModule(new UserModule(user)).build();
  20. return true;
  21. }
  22. return false;
  23. }

UserManager是一个单例,所以它的存活时间与Applicaiton一致,而且它对生命周期与用户会话一样的UserComponet负责。当用户决定去关闭会话,component会被移除,这样所有@UserScope注解了的对象应该准备被GC回收。

  1. //UserManager
  2. public void closeUserSession() {
  3. Timber.i("Close session for user: %s", userDataStore.getUser());
  4. userComponent.logoutManager().startLogoutProcess();
  5. userDataStore.clearUser();
  6. userComponent = null;
  7. }

Components的层次结构

在我们的app中所有的subcomponents使用了一个AppComponent作为一个根component。显示用户相关内容的Subcomponents使用保持了带@Userscope注解的对象(一个用户会话一个实例)的UserComponent

UserDetailsActivityComponent组件层次结构示例如下所示:

  1. // AppComponent.java
  2. @Singleton
  3. @Component(modules = {
  4. AppModule.class,
  5. GithubApiModule.class
  6. })
  7. public interface AppComponent {
  8. UserComponent.Builder userComponentBuilder();
  9. }
  10. // UserComponent.java
  11. @UserScope
  12. @Subcomponent(modules = UserModule.class)
  13. public interface UserComponent {
  14. @Subcomponent.Builder
  15. interface Builder {
  16. UserComponent.Builder sessionModule(UserModule userModule);
  17. UserComponent build();
  18. }
  19. UserDetailsActivityComponent plus(UserDetailsActivityComponent.UserDetailsActivityModule module);
  20. }
  21. // UserDetailsActivityComponent.java
  22. @ActivityScope
  23. @Subcomponent(modules = UserDetailsActivityComponent.UserDetailsActivityModule.class)
  24. public interface UserDetailsActivityComponent {
  25. UserDetailsActivity inject(UserDetailsActivity activity);
  26. }

对于UserComponent,我们使用Subcomponent builder模式来让我们的代码更加整洁,有可能注入这个Builder到UserModuleUserComponent.Builder用于创建组件UserModule.startUserSession方法如上所示)。

在app的多次启动中恢复UserScope

就像我之前提到的UserScope的存活时间不能超过application进程。所以如果我们假设用户会话可以在app的多次启动之间保存,我们需要为我们的UserScope去处理状态恢复操作。有两种场景我们需要记住:

用户从头开始启动程序

这是最常见的情况,应该很简单地去处理。用户从头开始启动app(比如,通过点击app icon)。Application对象被创建,然后第一个ActivityLAUNCHER)被启动。我们需要提供简单的逻辑检查我们是否有保存的用户在我们的数据存储中。如果是则将用户传送到UserComponent自动创建的正确的界面(在我们案例中是UserDetailsActivity)。

用户进程被系统kill

但是还有一种情况我们经常会忘记。Application进程可以被系统杀死(比如,因为内存不足)。这意味着所有application数据被销毁(application,activities,static fields)。不幸的是这不能被很好的处理 - 我们在Applicaiton生命周期中没有任何回调。令它更复杂的是,android保存了activity栈。也就是说,当用户决定启动之前被杀死于流中间的应用程序时,系统将试图带用户回到此界面。

对于我们而言我们需要准备在任何被使用的屏幕中去恢复UserComponent

让我们考虑这个例子:

  1. 用户在UserDetailsActivity界面最小化app。
  2. 整个app被系统杀死(稍后我会展示如何模拟它)
  3. 用户打开任务切换栏,点击我们的app界面。
  4. 系统创建了一个新的Application实例。也就是说有一个新的AppComponent被创建。然后系统并不会打开LoginActivity(我们的启动activity),而是立即打开UserDetailsActivity

对于我们而言,UserComponent被恢复回来(新的实例被创建)。然后这是我们的责任。例子的解决方案看起来如下:

  1. public abstract class BaseUserActivity extends BaseActivity {
  2. @Inject
  3. UserManager userManager;
  4. @Override
  5. protected void setupActivityComponent(AppComponent appComponent) {
  6. appComponent.inject(this);
  7. setupUserComponent();
  8. }
  9. private void setupUserComponent() {
  10. isUserSessionStarted = userManager.isUserSessionStartedOrStartSessionIfPossible();
  11. onUserComponentSetup(userManager.getUserComponent());
  12. //This screen cannot work when user session is not started.
  13. if (!isUserSessionStarted) {
  14. finish();
  15. }
  16. }
  17. protected abstract void onUserComponentSetup(UserComponent userComponent);
  18. }

每一个使用了UserComponent的Activity都继承了我们的BaseUserActivity类(setupActivityComponent()方法在BaseActivityonCreate()方法中调用)。

UserManager从Application创建的AppComponent中被注入。会话通过这种方式开启:

  1. public boolean isUserSessionStartedOrStartSessionIfPossible() {
  2. return userComponent != null || startUserSession();
  3. }
  4. private boolean startUserSession() {
  5. //This method was described earlier
  6. }

如果用户不再存在怎么办?

这里有另外一种方式来处理 - 如果用户登出(比如,通过SyncAdapter),然后UserComponent不能被创建怎么办?这就是为什么在我们的BaseUserActivity中有这几行:

  1. private void setupUserComponent() {
  2. //...
  3. //This screen cannot work when user session is not started.
  4. if (!isUserSessionStarted) {
  5. finish();
  6. }
  7. }

但是这里有一个情况时当UserComponent不能被创建时我们必须要记住依赖注入将不会发生。这就是为什么我每次需要在onCreate()方法中检查来防止来自依赖注入的NullPointerExceptions。

  1. //UserDetailsActivity
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_user_details);
  6. tvUser = (TextView) findViewById(R.id.tvUser);
  7. tvUserUrl = (TextView) findViewById(R.id.tvUserUrl);
  8. //This check has to be called. Otherwise, when user is not logged in, presenter won't be injected and this line will cause NPE
  9. if (isUserSessionStarted()) {
  10. presenter.onCreate();
  11. }
  12. }

怎么去模拟应用进程被系统杀死

  1. 打开你想测试的用户界面
  2. 通过系统Home键最小化app。
  3. 在Android Studio,Android Monitor 选中你的应用,然后点击Terminate
  4. 现在在你的设备上打开任务切换栏,找到你的app(你应该仍然能在最后一个可见的界面上预览到)。
  5. 你的app被启动,新的Application实例被创建,并且Activity被恢复。

源码

例子中展示怎么样去创建和使用UserComponent的可用的源码已经在Github上:Dagger 2 recipes - UserScope

感谢阅读!

作者

Miroslaw Stanek

Head of Mobile Development @ Azimo

> __[Android]使用Dagger 2依赖注入 - DI介绍(翻译):__

> __[Android]使用Dagger 2依赖注入 - API(翻译):__

> __[Android]使用Dagger 2依赖注入 - 自定义Scope(翻译):__

> __[Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译):__

> __[Android]Dagger2Metrics - 测量DI图表初始化的性能(翻译):__

> __[Android]使用Dagger 2进行依赖注入 - Producers(翻译):__

> __[Android]在Dagger 2中使用RxJava来进行异步注入(翻译):__

> __[Android]使用Dagger 2来构建UserScope(翻译):__

> __[Android]在Dagger 2中Activities和Subcomponents的多绑定(翻译):__

[Android]使用Dagger 2来构建UserScope(翻译)的更多相关文章

  1. [Android]使用Dagger 2进行依赖注入 - Producers(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6234811.html 使用Dagger 2进行依赖注入 - P ...

  2. [Android]在Dagger 2中使用RxJava来进行异步注入(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客: # 在Dagger 2中使用RxJava来进行异步注入 > 原文: 几星期前我写了一篇关于在Dagger 2中使用*Producers*进行 ...

  3. [Android]使用Dagger 2依赖注入 - DI介绍(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...

  4. [Android]使用Dagger 2依赖注入 - API(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092525.html 使用Dagger 2依赖注入 - API ...

  5. [Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html 使用Dagger 2依赖注入 - 自定义 ...

  6. [Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5098943.html 使用Dagger 2依赖注入 - 图表创 ...

  7. [Android]在Dagger 2中Activities和Subcomponents的多绑定(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6266442.html 在Dagger 2中Activities ...

  8. Android 和 Dagger 2 中的依赖注入

    原文:Dependency Injection in Android with Dagger 2 作者:Joe Howard 译者:kmyhy 在现代开发团队中到处充斥着"你一定要用依赖注入 ...

  9. 4.0、Android Studio配置你的构建

    Android构建系统编译你的app资源和源码并且打包到APK中,你可以用来测试,部署,签名和发布.Android Studio使用Gradle,一个高级的构建套件,来自动化和管理构建进程,同时可以允 ...

随机推荐

  1. Python高手之路【六】python基础之字符串格式化

    Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[PEP-3101] This ...

  2. win10 环境 gitbash 显示中文乱码问题处理

    gitbash 是 windows 环境下非常好用的命令行终端,可以模拟一下linux下的命令如ls / mkdir 等等,如果使用过程中遇到中文显示不完整或乱码的情况,多半是因为编码问题导致的,修改 ...

  3. TDD在Unity3D游戏项目开发中的实践

    0x00 前言 关于TDD测试驱动开发的文章已经有很多了,但是在游戏开发尤其是使用Unity3D开发游戏时,却听不到特别多关于TDD的声音.那么本文就来简单聊一聊TDD如何在U3D项目中使用以及如何使 ...

  4. 基于SignalR实现B/S系统对windows服务运行状态的监测

    通常来讲一个BS项目肯定不止单独的一个BS应用,可能涉及到很多后台服务来支持BS的运行,特别是针对耗时较长的某些任务来说,Windows服务肯定是必不可少的,我们还需要利用B/S与windows服务进 ...

  5. 操作系统篇-hello world(免系统运行程序)

     || 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言     今天起开始分享关于操作系统的相关知识,本人也是菜鸟一个,正处于学习阶段,这整个操作系统篇也是我边学习边总结的一些结果,希 ...

  6. Mybatis XML配置

    Mybatis常用带有禁用缓存的XML配置 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...

  7. Sass之坑Compass编译报错

    前段时间在使用Compass时遇到了其为难处理的一个坑,现记录到博客希望能帮助到各位. 一.问题: 利用Koala或者是gulp编译提示如下,截图为koala编译提示错误: 二.解决办法 从问题截图上 ...

  8. 简约而不简单的Django新手图文教程

    本文面向:有python基础,刚接触web框架的初学者. 环境:windows7   python3.5.1  pycharm专业版  Django 1.10版 pip3 一.Django简介 百度百 ...

  9. 我的MYSQL学习心得(三) 查看字段长度

    我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...

  10. EQueue文件持久化消息关键点设计思路

    要持久化的关键数据有三种 消息: 队列,队列中存放的是消息索引信息,即消息在文件中的物理位置(messageOffset)和在队列中的逻辑位置(queueOffset)的映射信息: 队列消费进度,表示 ...