[Android]使用Dagger 2來構建UserScope(翻譯)

来源:http://www.cnblogs.com/tiantianbyconan/archive/2016/12/30/6237731.html
-Advertisement-
Play Games

以下內容為原創,歡迎轉載,轉載請註明 來自天天博客: 使用Dagger 2來構建UserScope 原文: 在Dagger 2中自定義scopes可以在不尋常存活時間(與Application和界面生命周期不同的)的依賴上給我帶來更好的控制。但是在Android app中正確地實現它需要記住幾個事情 ...



以下內容為原創,歡迎轉載,轉載請註明
來自天天博客: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



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • /** * 訪問者模式 * @author TMAC-J * 在客戶端和元素之間添加一個訪問者 * 當你需要添加一些和元素關係不大的需求時,可以直接放在訪問者裡面 * 或者是元素之間有一些公共的代碼塊,你可以把它放在訪問者裡面,就不用寫重覆代碼了 * 適用於元素數據基本不變,操作不斷變化的場景 * ... ...
  • 一、Ibatis常用動態sql語法,簡單粗暴用一例子 <select id="iBatisSelectList" parameterClass="java.util.HashMap" resultMap="BeanFieldMap"> SELECT Column_list FROM Table_na ...
  • 一、Bundle進行IPC介紹 四大組件中的三大組件(Activity、Service、Receiver)都是支持在Intent中傳遞Bundle數據的,由於Bundle實現了Parcelable介面,所以它可以方便地在不同的進程之間傳輸。當然,傳輸的數據必須能夠被序列化,比如基本類型、實現了Par ...
  •     即將與2016徹底告別,每當年終歲末時總會有一絲驚慌失措的感覺,感嘆時間流逝的悄無聲息,惶恐自己在過去的一年中是否碌碌無為,浪費了青春。仔細回顧2016年,過去一年家庭、工作、生活上發生了很多變化,可以用三個關鍵詞來概括:喜悅、壓力和起點。 喜悅 &n ...
  • typedefNS_OPTIONS(NSUInteger, NSStringCompareOptions) { NSCaseInsensitiveSearch = 1, //不區分大小寫比較 NSLiteralSearch = 2, //逐位元組比較 區分大小寫 NSBackwardsSearch = ...
  • 最近經常有朋友反饋說我的安卓項目中,在一些類中會出現Created by panchengjia on 2016/12/30的字樣,是如何自動實現的(預設一般為Administrator),如下圖: 實現上圖這種效果,僅僅修改控制面板中的用戶賬戶名是沒有用的。 下麵我簡單介紹下windows環境下的 ...
  • 作者:Antonio Leiva 時間:Dec 19, 2016 原文鏈接:https://antonioleiva.com/kotlin-integrations-android-sdk/ 使用Kotlin語言不僅僅簡化你的代碼,而且也可以簡化從Kotlin調用Java代碼。 這是怎樣工作的?簡單 ...
  • 當沒有更多數據的時候顯示NoMoreData 我的理解是先結束刷新再顯示沒有更多 今天之前一直沒發現有問題 貼之前的代碼 今天卻跳出一個bug, 當endRefreshingWithNoMoreData之後再次下拉載入仍然進入刷新狀態,搞了好久最後修改代碼 其實今天的這個頁面的跳轉做了一定處理 NA ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...