天天看點

Android Context 幹貨

Android Context 幹貨

image.png

一、源碼角度解析Context

從系統的角度來了解:

Context

是一個場景,代表與作業系統的互動的一種過程。

Context

是一個抽象類;

Activity

Service

Application

是它的子類;

Android Context 幹貨

二、Context 應用場景

Android Context 幹貨

數字1:啟動Activity在這些類中是可以的,但是需要建立一個新的task。一般情況不推薦。

數字2:在這些類中去layout inflate是合法的,但是會使用系統預設的主題樣式,如果你自定義了某些樣式可能不會被使用。

數字3:在receiver為null時允許,在4.2或以上的版本中,用于擷取黏性廣播的目前值。(可以無視)

注:ContentProvider、BroadcastReceiver之是以在上述表格中,是因為在其内部方法中都有一個context用于使用。

預設的Toast實際上使用ApplicationContext也可以,因為總有時候在異步線程中做了一些土司操作,這種情況下在

Activity

關閉時候,很容易造成

context

為空的情況,是以所有的土司都采用

ApplicationContext

四、Fragment 中 Context 的擷取

Fragment

的生命周期中,在生命周期處于

onAttach()

onDetach()

之間的時候

getActivity()

方法才不會傳回

null

。是以我們可以在

fragment

初始化的時候建立

Context

引用。在fragment銷毀的時候銷毀引用。代碼如下:

private Context mContext;

Override
public void onAttach(Context context) {
    super.onAttach(context);
    mContext = context;//mContext 是成員變量,上下文引用
}

Override
public void onDetach() {
    super.onDetach();
    mContext = null;
}
           

注意:Activity 中有的

onAttach

有兩個方法

void onAttach(Activity activity);
void onAttach(Context context);
           

五、Context 引發的記憶體洩露解決

  • 不要讓生命周期長于Activity的對象持有到Activity的引用
  • 盡量使用Application的Context而不是Activity的Context。
  • 盡量不要在Activity中使用非靜态内部類,因為非靜态内部類會隐式持有外部類執行個體的引用(具體可以檢視細話Java:”失效”的private修飾符了解)。如果使用靜态内部類,将外部執行個體引用作為弱引用持有。

解決的方法就是不持有Activity的引用,而是持有Application的Context引用。擷取方式檢視

ContextHolder

方式。下面會介紹。

  • 單例模式用application的context。

    如果我們在Activity A中或者其他地方使用Foo.getInstance()時,我們總是會順手寫一個『this』或者『mContext』(這個變量也是指向this)。 試想一下,目前我們所用的Foo是單例,意味着被初始化後會一直存在與記憶體中,以友善我們以後調用的時候不會在此次建立Foo對象。但Foo中的 『mContext』變量一直都會持有Activity A中的『Context』,導緻Activity A即使執行了onDestroy方法,也不能夠将自己銷毀。但『applicationContext』就不同了,它一直伴随着我們應用存在(中途也可能 會被銷毀,但也會自動reCreate),是以就不用擔心Foo中的『mContext』會持有某Activity的引用,讓其無法銷毀。

實際上,隻要把握住一點,凡是跟UI相關的,都應該使用 Activity做為Context來處理(吐司除外);其他的一些操作,Service,Activity,Application等執行個體都可以,當然了,注意 Context引用的持有,防止記憶體洩漏。

六、擷取Context的四種方式方式

  • View.getContext

    ,傳回目前View對象的Context對象,通常是目前正在展示的Activity對象。
  • Activity.getApplicationContext

    ,擷取目前Activity所在的(應用)程序的Context對象,通常我們使用Context對象時,要優先考慮這個全局的程序Context。
  • ContextWrapper.getBaseContext()

    :用來擷取一個ContextWrapper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發中使用并不多,也不建議使用。
  • Activity.this 傳回目前的Activity執行個體,如果是UI控件需要使用Activity作為Context對象,但是預設的Toast實際上使用ApplicationContext也可以。

七、第三方庫通用的擷取Context方式

首先我們構造一個存儲Context的類ContextHolder,在Application初始化時将Application傳入ContextHolder,這個方法在很多第三方庫都能見到類似的處理。

public class ContextHolder {
    static Context ApplicationContext;
    public static void initial(Context context) {
        ApplicationContext = context;
    }
    public static Context getContext() {
        return ApplicationContext;
    }
}

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ContextHolder.initial(this);
    }
}
           

這樣我們就能在任意位置調用ContextHolder.getContext()來擷取應用Context。

那麼有沒有可能不需要任何初始化操作就能完成這個需求呢?筆者做了一些嘗試。

由于實際上擷取應用Context也就是擷取目前應用執行個體,經筆者研究下面2種方法都可以通過反射直接擷取目前應用。

try {    
    Application application = (Application) Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null, (Object[]) null);    
} catch (Exception e) {    
    e.printStackTrace();
}

try {    
    Application application = (Application) Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null, (Object[]) null);    
} catch (Exception e) {    
    e.printStackTrace();
}
           

經測試,即使應用處于背景仍能正确擷取到調用此方法的Application。

參考: http://blog.csdn.net/lmj623565791/article/details/40481055 http://www.jianshu.com/p/9d75e328f1de http://www.jianshu.com/p/808b9d92d6cd http://blog.nimbledroid.com/2016/05/23/memory-leaks-zh.html

繼續閱讀