使用ApplicationContext作為全域變數引用的缺陷

來源:互聯網
上載者:User

標籤:android   application context   全域變數   

在上一篇部落格中,我講了初次開發安卓必須知道的 6件事(6 THINGS I WISH I KNEW BEFORE I WROTE MY FIRST ANDROID APP)。其中一條就是:不要有一個Context的靜態引用。我這麼警告的原因是一個Context的靜態引用可能引發記憶體泄露。但是一位讀者指出:一個Application Context的靜態引用不會造成記憶體泄露,因為只要程式還在運行,Application Context的生命週期就不會結束。我則反駁到:技術上來說,你可以擁有一個Application Context的靜態引用而不造成記憶體泄露,但是我推薦你這樣做。

在這篇部落格中,我想解釋一下為什麼擁有和使用一個Application Context的靜態引用不是一個理想的選擇。之所以強調“理想的選擇”,因為我並不是說使用Application Context的靜態引用每次都會造成程式崩潰。相對的,我這篇部落格所說的是一些使用Context靜態引用的缺陷,以至於說這並不是開發安卓應用最簡潔的方式。

1. 對象/方法 使用Application Context靜態引用都是“欺騙”這一點是出自Google對於編寫可測試代碼的指南(Google’s Guide to Writing testable code)。在這個指南中,他們指出了:
靜態擷取全域變數並沒有將它們(全域變數)建構函式和方法的依賴關係告訴給閱讀代碼的人。全域變數和單例通過API掩蓋了它們真實的依賴關係。如果想要真正理解依賴關係,開發人員必須逐行閱讀代碼。(Accessing global state statically doesn’t clarify those shared dependencies to readers of the constructors and methods that use the Global State. Global State and Singletons make APIs lie about their true dependencies. To really understand the dependencies, developers must read every line of code.)

一個Application Context的全域靜態引用也正如這點所說:讀這個對象的人無法知道,這個對象依賴Context只是為了使用它的API。當一個對象擁有一個清晰真實的API來表達它的依賴,就能夠更容易的理解類或者方法的功能以及它將如何?這個功能。

下面用一個簡單的例子進行闡述。假設你當你閱讀代碼時,遇到了一個這樣的方法名稱:

public void displayString(String stringToDisplay)

當你遇到這個名稱的時候,你沒有辦法知道這個方法會怎樣顯示參數傳入的字串。現在,假設你閱讀到的是這樣的方法名:

public void displayString(Context context, String stringToDisplay)

對於這樣的方法名,你有一個線索(譯者:線索指Context參數):這個方法也許是用Toast顯示字串。因為Context是一個“萬能類”,對於一個特定的對象或者方法使用了它並不總是能夠顯示這個對象/方法的功能或者它如何?這個功能的。但是,一點點的提示也比沒有任何提示要強。

(譯者:可能翻譯的有些不通順了,這裡總的解釋一下。就是說使用ApplicationContext的靜態引用去使用一些方法的話,你是無法判斷這個方法是不是需要用到Context的。而如果不使用ApplicationContext的靜態引用的話,當一個方法需要用到Context對象時(如Toast),就必須多一個Context參數。而不需要時,則不會有Context參數。閱讀代碼的時候就可以根據這一點對方法的實現上有一定的估計)。

2. 使用了Application Context靜態引用的對象不是封裝的封裝雖然經常被提及,但是並沒有一個準確的定義。我也不打算使這個定義更複雜。當說“封裝”時,我指的是 Steve FreemanNat Pryce基於測試的物件導向編程(Growing Object Oriented Software Guided by Tests)所提到的概念:

(封裝)保障了一個對象的行為只能被它的API所影響。它保障了無關對象之間不會產生未知的引用,從而使得我們可以控制一個對象的修改對系統其它部分的影響。([It] Ensures that the behavior of an object can only be affected through its API. It lets us control how much a change to one object will impact other parts of the system by ensuring that there are no unexpected dependencies between unrelated components. -Pg. 92)

因為使用ApplicationContext靜態引用的對象關聯的是一個全域依賴,這些對象的行為可能會被全域共用的Application Context所影響。因為Application Context並不是這些對象API的一部分,這就意味著對象行為的改變可能不是被該對象的API所影響的。換一句話說,這就意味著使用Application Context靜態引用會破壞封裝。

在大多數情況下,以這樣一種形式破壞封裝不會產生太大影響。事實上,我僅可以想象的幾個可能產生問題的例子也看起來像是故意編造的。但是,我仍然認為,在其他條件相等的情況下,我們應當選擇能100%在全部情況下適用的結構,而不是99%都適用的結構。再一次的,使用ApplicationContext的靜態引用和對封裝的破壞並不會使你的程式崩潰,但是這並不是最穩定的結構。

3. 使用了Application Context靜態引用的對象可能難以進行單元測試如果你的一個對象調用了Application Context裡的一個方法,並且你想要驗證這個方法在單元測試中被調用了,使用一個靜態引用不會讓你好受。正如我在為什麼Android單元測試這麼困難中所說,你會在一些情況下想要進行這樣一個操作。假設你已經有一個啟動安卓Service的ServiceLauncher對象。如果你使用了依賴注入來在ServiceLaucher被引用的時候傳入一個Context對象,單元測試就很簡單:
public class ServiceLauncherTests {    @Mock    Context mContext;    @Test    public void launchesSessionCalendarService() {        ServiceLauncher serviceLauncher = new ServiceLauncher(mContext);        serviceLauncher.launchSessionCalendarService();        verify(mContext).startService(any(Intent.class));    }}
如果這個ServiceLaucher使用了Application Context靜態引用,這個對象就很難進行單元測試了。在這個例子當中,你可以使用測試支援庫的UI測試來驗證Intent被發送了,但是UI測試比單元測試要慢。並且,你也可能需要驗證Context中也一些不使用Intent的方法。所以,注入一個Context到目標對象中相比全域靜態變數更加靈活,即使你可以使用測試支援庫來協助你驗證Intent的發送。4. 使用了Application Context靜態引用的對象更可能違背迪米特法則(最少知道法則、最少引用法則)我們經常使用Context去擷取一個我們需要的對象的引用。一個特定的對象可能會需要Resoureces, SharePreferences或者 PackageManager去實現它的功能。當我們有一個全域Application Context引用時,我們可能會嘗試去通過這樣一種方式去擷取這些對象的引用:
public class SmellySessionColorResolver {    public SmellySessionColorResolver() {    }    public int resolveSessionColor(int sessionColor) {        if (sessionColor == 0) {            // no color -- use default            sessionColor = IOApplication.getContext().getResources().getColor(R.color.default_session_color);        } else {            // make sure it's opaque            sessionColor = UIUtils.setColorAlpha(sessionColor, 255);        }        return sessionColor;    }}
這就違背了迪米特法則。我實際上也抱怨過違背迪米特法則使得一個程式難以進行單元測試。但是即使你不關心單元測試,違背迪米特法則通常被視為一種噁心的代碼風格。結論我不認為我講得東西存在太大的爭議。我認為我只是把從更聰明的人身上學到的通用編程課程給運用上了而已。當然,歡迎批評和指正。如果你確信了你需要避免Application Context靜態引用的使用,向必要的對象和方法中注入Context應當不是一件難事。你甚至可能發現你在重構過程中能夠消除一大堆違背迪米特法則的代碼。Android Studio的推測和重構功能使得這項工作更加輕鬆,哪怕有點無聊。譯者結論這篇文章基本欄舉了使用Application Context幾個公認的弊端。對於將Application Context當作全域變數的使用,可能是因為這是安卓獨特的方法,一度被認為是最合理的方法。然而Google官方文檔卻指出了,重寫Application類其實是一種不推薦的做法,因為並沒有任何理由去說明它比傳統的JAVA全域變數要好。而且這樣做還容易使得Application變得又臭又長,引用的時候也需要添加很長的一句話。因此,我還是贊同不要使用Application Context的。

使用ApplicationContext作為全域變數引用的缺陷

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.