Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android Context深度剖析

android Context深度剖析

編輯:關於Android編程

android程序和java程序的區別

Android程序不像Java程序一樣,隨便創建一個類,寫個main()方法就能跑了,而是要有一個完整的Android工程環境,在這個環境下,我們有像Activity、Service、BroadcastReceiver等系統組件,而這些組件並不是像一個普通的Java對象new一下就能創建實例的了,而是要有它們各自的上下文環境,也就是我們這裡討論的Context。可以這樣講,Context是維持Android程序中各組件能夠正常工作的一個核心功能類。

Context是什麼?

Context的中文翻譯為:語境; 上下文; 背景; 環境,在開發中我們經常說稱之為“上下文”,那麼這個“上下文”到底是指什麼意思呢?在語文中,我們可以理解為語境,在程序中,我們可以理解為當前對象在程序中所處的一個環境,一個與系統交互的過程。比如微信聊天,此時的“環境”是指聊天的界面以及相關的數據請求與傳輸,Context在加載資源、啟動Activity、獲取系統服務、創建View等操作都要參與。

那Context到底是什麼呢?一個Activity就是一個Context,一個Service也是一個Context。Android程序員把“場景”抽象為Context類,他們認為用戶和操作系統的每一次交互都是一個場景,比如打電話、發短信,這些都是一個有界面的場景,還有一些沒有界面的場景,比如後台運行的服務(Service)。一個應用程序可以認為是一個工作環境,用戶在這個環境中會切換到不同的場景,這就像一個前台秘書,她可能需要接待客人,可能要打印文件,還可能要接聽客戶電話,而這些就稱之為不同的場景,前台秘書可以稱之為一個應用程序。

Context的子類結構

Context提供了關於應用環境全局信息的接口。它是一個抽象類,它的執行被Android系統所提供。它允許獲取以應用為特征的資源和類型,是一個統領一些資源(應用程序環境變量等)的上下文。就是說,它描述一個應用程序環境的信息(即上下文);是一個抽象類,Android提供了該抽象類的具體實現類;通過它我們可以獲取應用程序的資源和類(包括應用級別操作,如啟動Activity,發廣播,接受Intent等)。
這裡寫圖片描述
Context類本身是一個純abstract類,它有兩個具體的實現子類:ContextImpl和ContextWrapper。其中ContextWrapper類,如其名所言,這只是一個包裝而已,ContextWrapper構造函數中必須包含一個真正的Context引用,同時ContextWrapper中提供了attachBaseContext()用於給ContextWrapper對象中指定真正的Context對象,調用ContextWrapper的方法都會被轉向其所包含的真正的Context對象。ContextThemeWrapper類,如其名所言,其內部包含了與主題(Theme)相關的接口,這裡所說的主題就是指在AndroidManifest.xml中通過android:theme為Application元素或者Activity元素指定的主題。當然,只有Activity才需要主題,Service是不需要主題的,因為Service是沒有界面的後台場景,所以Service直接繼承於ContextWrapper,Application同理。而ContextImpl類則真正實現了Context中的所以函數,應用程序中所調用的各種Context類的方法,其實現均來自於該類。一句話總結:Context的兩個子類分工明確,其中ContextImpl是Context的具體實現類,ContextWrapper是Context的包裝類。Activity,Application,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會創建ContextImpl對象,由ContextImpl實現Context中的方法。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMSBpZD0="一個程序包含幾個context">一個程序包含幾個Context:

Context一共有Application、Activity和Service三種類型,因此一個應用程序中Context數量的計算公式就可以這樣寫:

Context數量 = Activity數量 + Service數量 + 1  

Application Context的設計:

基本上每一個應用程序都會有一個自己的Application,並讓它繼承自系統的Application類,然後在自己的Application類中去封裝一些通用的操作。

獲取Application

public class MainActivity extends Activity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        MyApplication myApp = (MyApplication) getApplication();  
        Log.d("TAG", "getApplication is " + myApp);  
    }  

}  

getApplication()方法和getApplicationContext()方法的聯系

public class MainActivity extends Activity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        MyApplication myApp = (MyApplication) getApplication();  
        Log.d("TAG", "getApplication is " + myApp);  
        Context appContext = getApplicationContext();  
        Log.d("TAG", "getApplicationContext is " + appContext);  
    }  

}  

打印出的結果是一樣的呀,連後面的內存地址都是相同的,看來它們是同一個對象。

為什麼?

Application本身就是一個Context,所以這裡獲取getApplicationContext()得到的結果就是MyApplication本身的實例。
際上這兩個方法在作用域上有比較大的區別。getApplication()方法的語義性非常強,一看就知道是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的實例,這時就可以借助getApplicationContext()方法了,如下所示:

public class MyReceiver extends BroadcastReceiver {  

    @Override  
    public void onReceive(Context context, Intent intent) {  
        MyApplication myApp = (MyApplication) context.getApplicationContext();  
        Log.d("TAG", "myApp is " + myApp);  
    }  

}  

getBaseContext()方法

除了這兩個方法之外,其實還有一個getBaseContext()方法,這個baseContext又是什麼東西呢?
getBaseContext()方法得到的是一個ContextImpl對象。這個ContextImpl是不是感覺有點似曾相識?回去看一下Context的繼承結構圖吧,ContextImpl正是上下文功能的實現類。也就是說像Application、Activity這樣的類其實並不會去具體實現Context的功能,而僅僅是做了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的。
ContextWrapper類的attachBaseContext()方法,這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法其實是由系統來調用的,它會把ContextImpl對象作為參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,之後ContextWrapper中的所有方法其實都是通過這種委托的機制交由ContextImpl去具體實現的,所以說ContextImpl是上下文功能的實現類是非常准確的。

使用Application注意的問題

public class MyApplication extends Application {  

    public MyApplication() {  
        String packageName = getPackageName();  
        Log.d("TAG", "package name is " + packageName);  
    }  

}  

報錯!

public class MyApplication extends Application {  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        String packageName = getPackageName();  
        Log.d("TAG", "package name is " + packageName);  
    }  

}  

這裡寫圖片描述
ContextWrapper中有一個attachBaseContext()方法,這個方法會將傳入的一個Context參數賦值給mBase對象,之後mBase對象就有值了。而我們又知道,所有Context的方法都是調用這個mBase對象的同名方法,那麼也就是說如果在mBase對象還沒賦值的情況下就去調用Context中的任何一個方法時,就會出現空指針異常,上面的代碼就是這種情況。

Context能干什麼

彈出Toast、啟動Activity、啟動Service、發送廣播、操作數據庫等等都需要用到Context。

TextView tv = new TextView(getContext());

ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);

AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);getApplicationContext().getSharedPreferences(name, mode);

getApplicationContext().getContentResolver().query(uri, ...);

getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;

getContext().startActivity(intent);

getContext().startService(intent);

getContext().sendBroadcast(intent);

Context作用域

由於Context的具體實例是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog。出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啟動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會出錯。

這裡寫圖片描述

如何獲取Context:

1:View.getContext,返回當前View對象的Context對象,通常是當前正在展示的Activity對象。
2:Activity.getApplicationContext,(或者getApplication())獲取當前Activity所在的(應用)進程的Context對象,通常我們使用Context對象時,要優先考慮這個全局的進程Context。
3:ContextWrapper.getBaseContext():用來獲取一個ContextWrapper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發中使用並不多,也不建議使用。
4:Activity.this 返回當前的Activity實例,如果是UI控件需要使用Activity作為Context對象,但是默認的Toast實際上使用ApplicationContext也可以。

Context的內存洩露的原因:

1.靜態Ui控件引起的

靜態的Drawable持有Activity的Context

public class MainActivity extends Activity {
    private static Drawable mDrawable;

    @Override
    protected void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv = new ImageView(this);
        mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
        iv.setImageDrawable(mDrawable);
    }
}

單例引起的內存洩露

我們使用餓漢式初始化單例,AppSettings我們需要持有一個Context作為成員變量,

public class AppSettings {    
    private Context mAppContext;
    private static AppSettings sInstance = new AppSettings();

    //some other codes
    public static AppSettings getInstance() {
      return sInstance;
    }

    public final void setup(Context context) {
        mAppContext = context;
    }
}

sInstance作為靜態對象,其生命周期要長於普通的對象,其中也包含Activity,當我們進行屏幕旋轉,默認情況下,系統會銷毀當前Activity,然後當前的Activity被一個單例持有,導致垃圾回收器無法進行回收,進而產生了內存洩露。

解決的方法就是不持有Activity的引用,而是持有Application的Context引用。代碼如下修改

public final void setup(Context context) {
    mAppContext = context.getApplicationContext(); 
}

Context如何避免內存洩露:

不要讓生命周期長於Activity的對象持有到Activity的引用
盡量使用Application的Context而不是Activity的Context
盡量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類實例的引用(具體可以查看細話Java:”失效”的private修飾符了解)。如果使用靜態內部類,將外部實例引用作為弱引用持有。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved