Android Context

一、Context 概念

從Android系統的角度來理解:Context是一個場景,描述的是一個應用程序環境的信息,即上下文,表明與操做系統的交互的一種過程。html

從程序的角度上來理解:Context是個抽象類,而Activity、Service、Application等都是該類的一個實現。android

二、Context 繼承結構

 

應用在三種狀況下會建立Context對象(即一般說的context): 
1> 建立Application 對象時,即第一次啓動app時。 整個App共一個Application對象,(咱們只需謹記一點,Application全局只有一個,它自己就已是單例了)因此也只有一個Application 的Context,Application銷燬,它也銷燬; 
2> 建立Activity對象時。Activity銷燬,它也銷燬; 
3> 建立Service對象時。Service銷燬,它也銷燬。nginx

由此能夠獲得應用程序App能夠建立的Context(Activity和Service沒啓動就不會建立)個數公式通常爲: 
總Context實例個數 = Service個數 + Activity個數 + 1(Application對應的Context對象) 設計模式

三、Context 如何獲取

一般咱們想要獲取Context對象,主要有如下四種方法 
1:View.getContext,返回當前View對象的Context對象,一般是當前正在展現的Activity對象。 
2:Activity.getApplicationContext,獲取當前Activity所在的(應用)進程的Context對象,一般咱們使用Context對象時,要優先考慮這個全局的進程Context。 
3:ContextWrapper.getBaseContext():用來獲取一個ContextWrapper進行裝飾以前的Context,可使用這個方法,這個方法在實際開發中使用並很少,也不建議使用。 
4:Activity.this 返回當前的Activity實例,若是是UI控件須要使用Activity做爲Context對象,可是默認的Toast實際上使用ApplicationContext也能夠。app

public class MyActivity extends Activity {
    Context mContext;
    public void method() {

    mContext = this; //獲取當前Activity的上下文,若是須要綁定Activity的生命週期,使用它

    mContext=MyActivity.this;//獲取當前MyActivity的上下文,不方便使用this的時候推薦使用這種方式

    //調用Activity.getApplicationContext()
    mContext = getApplicationContext();//獲取當前Application的上下文,若是須要綁定應用的生命週期,使用它

    //Activity.getApplication()
    mContext = getApplication();//獲取當前Application的上下文,

    //調用ContextWrapper.getBaseContext()
    mContext = getBaseContext();//從上下文A內上下文訪問上下文A,不建議使用,若是須要,推薦使用XxxClass.this直接指出上下文
   }
}


public class MyView extends View {
    Context mContext;
    public void method() {

    //調用View.getContext()
    mContext = getContext(); //獲取這個View運行所在地的上下文
   }
}
3.1 getApplicationContext()和getApplication()
  • getApplicationContext 取得的是當前app所使用的application,這在AndroidManifest中惟一指定。意味着,在當前app的任意位置使用這個函數獲得的是同一個Context,getApplicationContext(): 返回應用的上下文,生命週期是整個應用,應用摧毀,它才摧毀。
  • getApplication():andorid 開發中共享全局數據;

  getApplication()只能在Activity和Service裏使用,指向的是Application對象,由於Application也是Context的一個子類,因此getApplication()能夠被用來指向Context。框架

  好比若是想要獲取在應用清單文件中聲明的類,最好不要使用getApplicationContext(),而且最好使用強制轉換爲本身自定義的Application,由於那樣可能會得不到Application對象。ide

Log.i("dyl", "getApplication is = " + myApp);
Log.i("dyl", "getApplicationContext is = " + appContext);

經過上面的代碼,打印得出二者的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,由於前面已經說過了,Application自己就是一個Context,因此這裏獲取getApplicationContext()獲得的結果就是Application自己的實例。那麼問題來了,既然這兩個方法獲得的結果都是相同的,那麼Android爲何要提供兩個功能重複的方法呢?實際上這兩個方法在做用域上有比較大的區別。getApplication()方法的語義性很是強,一看就知道是用來獲取Application實例的,可是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數狀況下咱們都是在Activity或者Service中使用Application的,可是若是在一些其它的場景,好比BroadcastReceiver中也想得到Application的實例,這時就能夠藉助getApplicationContext()方法了函數

四、Context 使用過程當中的注意項

1)Activity mActivity =new Activity()工具

這樣寫語法上沒有任何錯誤,Android的應用程序開發採用JAVA語言,Activity本質上也是一個對象。可是,oop

Android程序不像Java程序同樣,隨便建立一個類,寫個main()方法就能運行,Android應用模型是基於組件的應用設計模式,組件的運行要有一個完整的Android工程環境,在這個環境下,Activity、Service等系統組件纔可以正常工做,而這些組件並不能採用普通的Java對象建立方式,new一下就能建立實例了,而是要有它們各自的上下文環境,才能使得其正常工做。即走正常的onCreate-onStart-onResume。。。

2)你們在編寫一些類時,例如工具類,可能會編寫成單例的方式,這些工具類大多須要去訪問資源,也就說須要Context的參與。

在這樣的狀況下,就須要注意Context的引用問題。

public class CustomManager  
{  
    private static CustomManager sInstance;  
    private Context mContext;  
  
    private CustomManager(Context context)  
    {  
        this.mContext = context;  
    }  
  
    public static synchronized CustomManager getInstance(Context context)  
    {  
        if (sInstance == null)  
        {  
            sInstance = new CustomManager(context);  
        }  
        return sInstance;  
    }  
}

對於上述的單例,你們應該都不陌生(請別計較getInstance的效率問題),內部保持了一個Context的引用;這麼寫是沒有問題的,問題在於,這個Context哪來的咱們不能肯定,很大的可能性,你在某個Activity裏面爲了方便,直接傳了個this;這樣問題就來了,咱們的這個類中的sInstance是一個static引用,在其內部引用了一個Activity做爲Context,也就是說,咱們的這個Activity只要咱們的應用活着,就沒有辦法進行內存回收。(直接傳入Activity的引用,若是當前Activity退出了,但應用尚未退出,sInstance一直持有Activity的引用,MyActivity就不能被回收了)。而咱們的Activity的生命週期確定沒這麼長,因此形成了內存泄漏。那麼,咱們如何才能避免這樣的問題呢?有人會說,咱們能夠軟引用,嗯,軟引用,假如被回收了,你不怕NullPointException麼。把上述代碼作下修改:

public static synchronized CustomManager getInstance(Context context)  
{  
        if (sInstance == null)  
        {  
            sInstance = new CustomManager(context.getApplicationContext());  
        }  
        return sInstance;  
}

這樣,咱們就解決了內存泄漏的問題,由於咱們引用的是一個ApplicationContext,它的生命週期和咱們的單例對象一致。

或者直接傳入ApplicationContext就能夠了。單例模式的靜態特性致使它的對象的生命週期是和應用同樣的

SingleInstance singleInstance = SingleInstance.getInstance(getApplicationContext());

3)Intent也要求指出上下文,若是想啓動一個新的Activity,就必須在Intent中使用Activity的上下文,這樣新啓動的Activity才能和當前Activity有關聯(在activity棧);也可使用application的context,可是須要在Intent中添加 Intent.FLAG_ACTIVITY_NEW_TASK標誌,看成一個新任務。ApplicationContext去啓動一個LaunchMode爲standard的Activity的時候會報錯,非Activity類型的Context並無所謂的任務棧,因此待啓動的Activity就找不到棧了。解決這個問題的方法就是爲待啓動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啓動的時候就爲它建立一個新的任務棧,而此時Activity是以singleTask模式啓動的。因此這種用Application啓動Activity的方式不推薦使用,Service同Application。

public static void openActivity(Context context){
        Intent intent = new Intent(context.getApplicationContext(), SecondActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.getApplicationContext().startActivity(intent);
    }

 4)Context泄露:Handler&內部類

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ... 
    }
  }
}

若是沒有仔細觀察,上面的代碼可能致使嚴重的內存泄露。Android Lint會給出下面的警告:

In Android, Handler classes should be static or leaks might occur.

可是究竟是泄漏,如何發生的?讓咱們肯定問題的根源,先寫下咱們所知道的
一、當一個Android應用程序第一次啓動時,Android框架爲應用程序的主線程建立一個Looper對象。一個Looper實現了一個簡單的消息隊列,在一個循環中處理Message對象。全部主要的應用程序框架事件(如活動生命週期方法調用,單擊按鈕,等等)都包含在Message對象,它被添加到Looper的消息隊列而後一個個被處理。主線程的Looper在應用程序的整個生命週期中存在。
二、當一個Handle在主線程被實例化,它就被關聯到Looper的消息隊列。被髮送到消息隊列的消息會持有一個Handler的引用,以便Android框架能夠在Looper最終處理這個消息的時候,調用Handler#handleMessage(Message)
三、在Java中,非靜態的內部類和匿名類會隱式地持有一個他們外部類的引用。靜態內部類則不會。

那麼,究竟是內存泄漏?好像很難懂,讓咱們如下面的代碼做爲一個例子:

public class SampleActivity extends Activity {
 
  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    // 延時10分鐘發送一個消息
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { }
    }, 60 * 10 * 1000);
 
    // 返回前一個Activity
    finish();
  }
}

當這個Activity被finished後,延時發送的消息會繼續在主線程的消息隊列中存活10分鐘,直到他們被處理。這個消息持有這個Activity的Handler引用,這個Handler有隱式地持有他的外部類(在這個例子中是SampleActivity)。直到消息被處理前,這個引用都不會被釋放。所以Activity不會被垃圾回收機制回收,泄露他所持有的應用程序資源。注意,第15行的匿名Runnable類也同樣。匿名類的非靜態實例持有一個隱式的外部類引用,所以context將被泄露。

爲了解決這個問題,Handler的子類應該定義在一個新文件中或使用靜態內部類。靜態內部類不會隱式持有外部類的引用。因此不會致使它的Activity泄露。若是你須要在Handle內部調用外部Activity的方法,那麼讓Handler持有一個Activity的弱引用(WeakReference)以便你不會意外致使context泄露。爲了解決咱們實例化匿名Runnable類可能致使的內存泄露,咱們將用一個靜態變量來引用他(由於匿名類的靜態實例不會隱式持有他們外部類的引用)。

public class SampleActivity extends Activity {
    /**
    * 匿名類的靜態實例不會隱式持有他們外部類的引用
    */
    private static final Runnable sRunnable = new Runnable() {
            @Override
            public void run() {
            }
        };

    private final MyHandler mHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 延時10分鐘發送一個消息.
        mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

        // 返回前一個Activity
        finish();
    }

    /**
    * 靜態內部類的實例不會隱式持有他們外部類的引用。
    */
    private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;

        public MyHandler(SampleActivity activity) {
            mActivity = new WeakReference<SampleActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            SampleActivity activity = mActivity.get();

            if (activity != null) {
                // ...
            }
        }
    }
}

靜態和非靜態內部類的區別是比較難懂的,但每個Android開發人員都應該瞭解。開發中不能碰的雷區是什麼?不在一個Activity中使用非靜態內部類, 以防它的生命週期比Activity長。相反,儘可能使用持有Activity弱引用的靜態內部類。

相關文章
相關標籤/搜索