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

简介:

来自天天博客: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:

它怎么工作的呢?

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

//UserManager
public Observable<User> startSessionForUser(String username) {
    return githubApiService.getUser(username)
            .map(User.UserResponseToUser())
            .doOnNext(new Action1<User>() {
                @Override
                public void call(User user) {
                    userDataStore.createUser(user);
                    startUserSession();
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());
}

private boolean startUserSession() {
    User user = userDataStore.getUser();
    if (user != null) {
        Timber.i("Session started, user: %s", user);
        userComponent = userComponentBuilder.sessionModule(new UserModule(user)).build();
        return true;
    }

    return false;
}

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

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

Components的层次结构

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

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

// AppComponent.java 

@Singleton
@Component(modules = {
        AppModule.class,
        GithubApiModule.class
})
public interface AppComponent {
    UserComponent.Builder userComponentBuilder();
}


// UserComponent.java 

@UserScope
@Subcomponent(modules = UserModule.class)
public interface UserComponent {
    
    @Subcomponent.Builder
    interface Builder {
        UserComponent.Builder sessionModule(UserModule userModule);
        UserComponent build();
    }

    UserDetailsActivityComponent plus(UserDetailsActivityComponent.UserDetailsActivityModule module);
}

// UserDetailsActivityComponent.java 

@ActivityScope
@Subcomponent(modules = UserDetailsActivityComponent.UserDetailsActivityModule.class)
public interface UserDetailsActivityComponent {
    UserDetailsActivity inject(UserDetailsActivity activity);
}

对于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被恢复回来(新的实例被创建)。然后这是我们的责任。例子的解决方案看起来如下:

public abstract class BaseUserActivity extends BaseActivity {

    @Inject
    UserManager userManager;

    @Override
    protected void setupActivityComponent(AppComponent appComponent) {
        appComponent.inject(this);
        setupUserComponent();
    }

    private void setupUserComponent() {
    isUserSessionStarted = userManager.isUserSessionStartedOrStartSessionIfPossible();
    onUserComponentSetup(userManager.getUserComponent());

    //This screen cannot work when user session is not started.
    if (!isUserSessionStarted) {
        finish();
    }
}

    protected abstract void onUserComponentSetup(UserComponent userComponent);

}

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

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

public boolean isUserSessionStartedOrStartSessionIfPossible() {
    return userComponent != null || startUserSession();
}

private boolean startUserSession() {
    //This method was described earlier
}

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

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

private void setupUserComponent() {
    //...

    //This screen cannot work when user session is not started.
    if (!isUserSessionStarted) {
        finish();
    }
}

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

//UserDetailsActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_user_details);
    tvUser = (TextView) findViewById(R.id.tvUser);
    tvUserUrl = (TextView) findViewById(R.id.tvUserUrl);

    //This check has to be called. Otherwise, when user is not logged in, presenter won't be injected and this line will cause NPE
    if (isUserSessionStarted()) {
        presenter.onCreate();
    }
}

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

  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介绍(翻译):

http://www.cnblogs.com/tiantianbyconan/p/5092083.html


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

http://www.cnblogs.com/tiantianbyconan/p/5092525.html


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

http://www.cnblogs.com/tiantianbyconan/p/5095426.html


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

http://www.cnblogs.com/tiantianbyconan/p/5098943.html


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

http://www.cnblogs.com/tiantianbyconan/p/5193437.html


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

http://www.cnblogs.com/tiantianbyconan/p/6234811.html


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

http://www.cnblogs.com/tiantianbyconan/p/6236646.html


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

http://www.cnblogs.com/tiantianbyconan/p/6237731.html


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

http://www.cnblogs.com/tiantianbyconan/p/6266442.html

本文转自天天_byconan博客园博客,原文链接:http://www.cnblogs.com/tiantianbyconan/p/6237731.html ,如需转载请自行联系原作者
相关文章
|
1月前
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
2月前
|
存储 Java Android开发
探索安卓应用开发:构建你的第一个"Hello World"应用
【9月更文挑战第24天】在本文中,我们将踏上一段激动人心的旅程,深入安卓应用开发的奥秘。通过一个简单而经典的“Hello World”项目,我们将解锁安卓应用开发的基础概念和步骤。无论你是编程新手还是希望扩展技能的老手,这篇文章都将为你提供一次实操体验。从搭建开发环境到运行你的应用,每一步都清晰易懂,确保你能顺利地迈出安卓开发的第一步。让我们开始吧,探索如何将一行简单的代码转变为一个功能齐全的安卓应用!
|
3月前
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
67 10
|
27天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
48 5
|
28天前
|
前端开发 JavaScript 测试技术
Android适合构建中大型项目的架构模式全面对比
Android适合构建中大型项目的架构模式全面对比
43 2
|
1月前
|
开发工具 Android开发 iOS开发
Android vs iOS:构建移动应用时的关键考量####
本文深入探讨了Android与iOS两大移动平台在开发环境、性能优化、用户体验设计及市场策略方面的差异性,旨在为开发者提供决策依据。通过对比分析,揭示两个平台各自的优势与挑战,帮助开发者根据项目需求做出更明智的选择。 ####
|
1月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
47 4
|
1月前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
51 2
|
2月前
|
开发框架 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的指南
在移动应用开发的广阔天地中,安卓与iOS两大平台各占半壁江山。本文将深入浅出地对比这两大操作系统的开发环境、工具和用户体验设计,揭示它们在编程语言、开发工具以及市场定位上的根本差异。我们将从开发者的视角出发,逐步剖析如何根据项目需求和目标受众选择适合的平台,同时探讨跨平台开发框架的利与弊,为那些立志于打造下一个热门应用的开发者提供一份实用的指南。
61 5
|
2月前
|
开发工具 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的关键考量
在数字时代的浪潮中,安卓和iOS这两大操作系统如同双子星座般耀眼夺目,引领着移动应用的潮流。它们各自拥有独特的魅力和深厚的用户基础,为开发者提供了广阔的舞台。然而,正如每枚硬币都有两面,安卓与iOS在开发过程中也展现出了截然不同的特性。本文将深入剖析这两者在开发环境、编程语言、用户体验设计等方面的显著差异,并探讨如何根据目标受众和项目需求做出明智的选择。无论你是初涉移动应用开发的新手,还是寻求拓展技能边界的资深开发者,这篇文章都将为你提供宝贵的见解和实用的建议,帮助你在安卓与iOS的开发之路上更加从容自信地前行。