以下内容为原创,欢迎转载,转载请注明
来自天天博客: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。
在我的其中一篇博客中写了关于自定义scopes和Subcomponents。作为一个例子,我使用了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
。
//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到UserModule
(UserComponent.Builder
用于创建组件UserModule.startUserSession
方法如上所示)。
在app的多次启动中恢复UserScope
就像我之前提到的UserScope的存活时间不能超过application进程。所以如果我们假设用户会话可以在app的多次启动之间保存,我们需要为我们的UserScope
去处理状态恢复操作。有两种场景我们需要记住:
用户从头开始启动程序
这是最常见的情况,应该很简单地去处理。用户从头开始启动app(比如,通过点击app icon)。Application
对象被创建,然后第一个Activity
(LAUNCHER)被启动。我们需要提供简单的逻辑检查我们是否有保存的用户在我们的数据存储中。如果是则将用户传送到UserComponent自动创建的正确的界面(在我们案例中是UserDetailsActivity
)。
用户进程被系统kill
但是还有一种情况我们经常会忘记。Application进程可以被系统杀死(比如,因为内存不足)。这意味着所有application数据被销毁(application,activities,static fields)。不幸的是这不能被很好的处理 - 我们在Applicaiton生命周期中没有任何回调。令它更复杂的是,android保存了activity栈。也就是说,当用户决定启动之前被杀死于流中间的应用程序时,系统将试图带用户回到此界面。
对于我们而言我们需要准备在任何被使用的屏幕中去恢复UserComponent
。
让我们考虑这个例子:
- 用户在
UserDetailsActivity
界面最小化app。 - 整个app被系统杀死(稍后我会展示如何模拟它)
- 用户打开任务切换栏,点击我们的app界面。
- 系统创建了一个新的
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()
方法在BaseActivity
的onCreate()
方法中调用)。
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();
}
}
怎么去模拟应用进程被系统杀死
- 打开你想测试的用户界面
- 通过系统Home键最小化app。
- 在Android Studio,Android Monitor 选中你的应用,然后点击Terminate
- 现在在你的设备上打开任务切换栏,找到你的app(你应该仍然能在最后一个可见的界面上预览到)。
- 你的app被启动,新的Application实例被创建,并且Activity被恢复。
源码
例子中展示怎么样去创建和使用UserComponent
的可用的源码已经在Github上:Dagger 2 recipes - UserScope。
感谢阅读!
作者
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(翻译)的更多相关文章
-
[Android]使用Dagger 2进行依赖注入 - Producers(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6234811.html 使用Dagger 2进行依赖注入 - P ...
-
[Android]在Dagger 2中使用RxJava来进行异步注入(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客: # 在Dagger 2中使用RxJava来进行异步注入 > 原文: 几星期前我写了一篇关于在Dagger 2中使用*Producers*进行 ...
-
[Android]使用Dagger 2依赖注入 - DI介绍(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...
-
[Android]使用Dagger 2依赖注入 - API(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092525.html 使用Dagger 2依赖注入 - API ...
-
[Android]使用Dagger 2依赖注入 - 自定义Scope(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5095426.html 使用Dagger 2依赖注入 - 自定义 ...
-
[Android]使用Dagger 2依赖注入 - 图表创建的性能(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5098943.html 使用Dagger 2依赖注入 - 图表创 ...
-
[Android]在Dagger 2中Activities和Subcomponents的多绑定(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6266442.html 在Dagger 2中Activities ...
-
Android 和 Dagger 2 中的依赖注入
原文:Dependency Injection in Android with Dagger 2 作者:Joe Howard 译者:kmyhy 在现代开发团队中到处充斥着"你一定要用依赖注入 ...
-
4.0、Android Studio配置你的构建
Android构建系统编译你的app资源和源码并且打包到APK中,你可以用来测试,部署,签名和发布.Android Studio使用Gradle,一个高级的构建套件,来自动化和管理构建进程,同时可以允 ...
随机推荐
-
CSS 巧用 :before和:after
前几天的晚上较全面的去看了下css的一些文档和资料,大部分的样式运用都没什么大问题了,只是有些许较陌生,但是也知道他们的存在和实现的是什么样式.今天主要想在这篇学习笔记中写的也不多,主要是针对:bef ...
-
POJ3255Roadblocks[次短路]
Roadblocks Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 12697 Accepted: 4491 Descr ...
-
C#-WinForm-跨窗体 构造函数传值 及应用—登录式窗口传值、如何关闭主页面时关闭应用程序、如何打开唯一窗口—★★★★★五星重量级
构造函数可以传任意类型的值,并可以同时传多个值 结构函数传值的初步应用--简单的登陆式界面 现在我有两个窗体Form3和Form4,如下,如何点击Form3中的按钮后,打开Form4并将Form3中的 ...
-
基于webrtc的资源释放问题(一)
基于webrtc的资源释放问题(一) ——重复释放webrtc的相关资源 背景: 视频通讯大都只是作为一个功能存在于各种应用中,比如微信,qq .既然只是应用的一部分,这样就涉及反复的开启和关闭视频通 ...
-
IOS学习资源收集--关于动画的代码学习资源总汇(很棒的动画效果哦)
目录大纲: 1.很棒的iOS加载动画. github网址:https://github.com/NghiaTranUIT/FeSpinner 游老师的译文blog:http://www.cnblogs ...
-
java 时间戳与日期字符串相互转换
/** * 时间戳转换成日期格式字符串 * @param seconds 精确到秒的字符串 * @param formatStr * @return */ public static String t ...
-
KK的新书《必然》对未来科技趋势的预言
是他第一次在<失控>中提示我们-- 要用生物学而不是机械学的角度看待这个世界. 是他第一次在<科技想要什么>提示我们-- 科技本身就是一个生命体. 而在新书<必然 ...
-
java集合框架map
Map<K,V> K key V value Map集合:该集合存储键值对.一对一对往里存,而且要保证键的唯一性. 1,添加. 2,删除. 3,判断. 4,获取. Map |--Hasht ...
-
sqlDataAdapter的FillSchema用法
摘自于网络:http://blog.csdn.net/bupt_zoucq/article/details/6653385 FillSchema是用来向DataTable中填入详细的元数据信息的,例如 ...
-
七月在线爬虫班学习笔记(二)——Python基本语法及面向对象
第二课主要内容如下: 代码格式 基本语法 关键字 循环判断 函数 容器 面向对象 文件读写 多线程 错误处理 代码格式 syntax基本语法 a = 1234 print(a) a = 'abcd' ...