Android-Context

http://blog.csdn.net/guolin_blog/article/details/47028975android

 

Context類型

咱們知道,Android應用都是使用Java語言來編寫的,那麼你們能夠思考一下,一個Android程序和一個Java程序,他們最大的區別在哪裏?劃分界限又是什麼呢?其實簡單點分析,Android程序不像Java程序同樣,隨便建立一個類,寫個main()方法就能跑了,而是要有一個完整的Android工程環境,在這個環境下,咱們有像Activity、Service、BroadcastReceiver等系統組件,而這些組件並非像一個普通的Java對象new一下就能建立實例的了,而是要有它們各自的上下文環境,也就是咱們這裏討論的Context。能夠這樣講,Context是維持Android程序中各組件可以正常工做的一個核心功能類。數據庫

下面咱們來看一下Context的繼承結構:安全

Context的繼承結構仍是稍微有點複雜的,能夠看到,直系子類有兩個,一個是ContextWrapper,一個是ContextImpl。那麼從名字上就能夠看出,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。而ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity。性能優化

那麼在這裏咱們至少看到了幾個所比較熟悉的面孔,Activity、Service、還有Application。由此,其實咱們就已經能夠得出結論了,Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各類承擔着不一樣的做用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的。app

那麼Context到底能夠實現哪些功能呢?這個就實在是太多了,彈出Toast、啓動Activity、啓動Service、發送廣播、操做數據庫等等等等都須要用到Context。因爲Context的具體能力是由ContextImpl類去實現的,所以在絕大多數場景下,Activity、Service和Application這三種類型的Context都是能夠通用的。不過有幾種場景比較特殊,好比啓動Activity,還有彈出Dialog。出於安全緣由的考慮,Android是不容許Activity或Dialog憑空出現的,一個Activity的啓動必需要創建在另外一個Activity的基礎之上,也就是以此造成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),所以在這種場景下,咱們只能使用Activity類型的Context,不然將會出錯。ide

 

Context數量

那麼一個應用程序中到底有多少個Context呢?其實根據上面的Context類型咱們就已經能夠得出答案了。Context一共有Application、Activity和Service三種類型,所以一個應用程序中Context數量的計算公式就能夠這樣寫:工具

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

上面的1表明着Application的數量,由於一個應用程序中能夠有多個Activity和多個Service,可是隻能有一個Application。性能

Application Context的設計

基本上每個應用程序都會有一個本身的Application,並讓它繼承自系統的Application類,而後在本身的Application類中去封裝一些通用的操做。其實這並非Google所推薦的一種作法,由於這樣咱們只是把Application當成了一個通用工具類來使用的,而實際上使用一個簡單的單例類也能夠實現一樣的功能。可是根據個人觀察,有太多的項目都是這樣使用Application的。固然這種作法也並無什麼反作用,只是說明仍是有很多人對於Application理解的還有些欠缺優化

 

首先新建一個MyApplication並讓它繼承自Application,而後在AndroidManifest.xml文件中對MyApplication進行指定,以下所示:

<application  
    android:name=".MyApplication"  
    android:allowBackup="true"  
    android:icon="@drawable/ic_launcher"  
    android:label="@string/app_name"  
    android:theme="@style/AppTheme" >  
    ......  
</application>

指定完成後,當咱們的程序啓動時Android系統就會建立一個MyApplication的實例,若是這裏不指定的話就會默認建立一個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()方法,能夠獲取到Application的實例。這兩個方法獲得的是同一個對象。其實這個結果也很好理解,由於前面已經說過了,Application自己就是一個Context,因此這裏獲取getApplicationContext()獲得的結果就是MyApplication自己的實例。

那麼有的朋友可能就會問了,既然這兩個方法獲得的結果都是相同的,那麼Android爲何要提供兩個功能重複的方法呢?實際上這兩個方法在做用域上有比較大的區別。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);  
    }  
  
}  

也就是說,getApplicationContext()方法的做用域會更廣一些,任何一個Context的實例,只要調用getApplicationContext()方法均可以拿到咱們的Application對象。

那麼更加細心的朋友會發現,除了這兩個方法以外,其實還有一個getBaseContext()方法,這個baseContext又是什麼東西呢?

此次獲得的是不一樣的對象了,getBaseContext()方法獲得的是一個ContextImpl對象

這個ContextImpl是否是感受有點似曾相識?回去看一下Context的繼承結構圖吧,ContextImpl正是上下文功能的實現類。也就是說像Application、Activity這樣的類其實並不會去具體實現Context的功能,而僅僅是作了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的。那麼這樣的設計究竟是怎麼實現的呢?咱們仍是來看一下源碼吧。由於Application、Activity、Service都是直接或間接繼承自ContextWrapper的,咱們就直接看ContextWrapper的源碼,以下所示:

/** 
 * Proxying implementation of Context that simply delegates all of its calls to 
 * another Context.  Can be subclassed to modify behavior without changing 
 * the original Context. 
 */  
public class ContextWrapper extends Context {  
    Context mBase;  
      
    /** 
     * Set the base context for this ContextWrapper.  All calls will then be 
     * delegated to the base context.  Throws 
     * IllegalStateException if a base context has already been set. 
     *  
     * @param base The new base context for this wrapper. 
     */  
    protected void attachBaseContext(Context base) {  
        if (mBase != null) {  
            throw new IllegalStateException("Base context already set");  
        }  
        mBase = base;  
    }  
  
    /** 
     * @return the base context as set by the constructor or setBaseContext 
     */  
    public Context getBaseContext() {  
        return mBase;  
    }  
  
    @Override  
    public AssetManager getAssets() {  
        return mBase.getAssets();  
    }  
  
    @Override  
    public Resources getResources() {  
        return mBase.getResources();  
    }  
  
    @Override  
    public ContentResolver getContentResolver() {  
        return mBase.getContentResolver();  
    }  
  
    @Override  
    public Looper getMainLooper() {  
        return mBase.getMainLooper();  
    }  
      
    @Override  
    public Context getApplicationContext() {  
        return mBase.getApplicationContext();  
    }  
  
    @Override  
    public String getPackageName() {  
        return mBase.getPackageName();  
    }  
  
    @Override  
    public void startActivity(Intent intent) {  
        mBase.startActivity(intent);  
    }  
      
    @Override  
    public void sendBroadcast(Intent intent) {  
        mBase.sendBroadcast(intent);  
    }  
  
    @Override  
    public Intent registerReceiver(  
        BroadcastReceiver receiver, IntentFilter filter) {  
        return mBase.registerReceiver(receiver, filter);  
    }  
  
    @Override  
    public void unregisterReceiver(BroadcastReceiver receiver) {  
        mBase.unregisterReceiver(receiver);  
    }  
  
    @Override  
    public ComponentName startService(Intent service) {  
        return mBase.startService(service);  
    }  
  
    @Override  
    public boolean stopService(Intent name) {  
        return mBase.stopService(name);  
    }  
  
    @Override  
    public boolean bindService(Intent service, ServiceConnection conn,  
            int flags) {  
        return mBase.bindService(service, conn, flags);  
    }  
  
    @Override  
    public void unbindService(ServiceConnection conn) {  
        mBase.unbindService(conn);  
    }  
  
    @Override  
    public Object getSystemService(String name) {  
        return mBase.getSystemService(name);  
    }  
  
    ......  
}

因爲ContextWrapper中的方法仍是很是多的,我就進行了一些篩選,只貼出來了部分方法。那麼上面的這些方法相信你們都是很是熟悉的,getResources()、getPackageName()、getSystemService()等等都是咱們常常要用到的方法。那麼全部這些方法的實現又是什麼樣的呢?其實全部ContextWrapper中方法的實現都很是統一,就是調用了mBase對象中對應當前方法名的方法。

 

那麼這個mBase對象又是什麼呢?咱們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法實際上是由系統來調用的,它會把ContextImpl對象做爲參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,以後ContextWrapper中的全部方法其實都是經過這種委託的機制交由ContextImpl去具體實現的,因此說ContextImpl是上下文功能的實現類是很是準確的。

那麼另外再看一下咱們剛剛打印的getBaseContext()方法,在第26行。這個方法只有一行代碼,就是返回了mBase對象而已,而mBase對象其實就是ContextImpl對象,所以剛纔的打印結果也獲得了印證。

使用Application的問題

雖然說Application的用法確實很是簡單,可是咱們平時的開發工做當中也着實存在着很多Application誤用的場景,那麼今天就來看一看有哪些比較容易犯錯的地方是咱們應該注意的。

Application是Context的其中一種類型,那麼是否就意味着,只要是Application的實例,就能隨時使用Context的各類方法呢?咱們來作個實驗試一下就知道了:

 

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);  
    }  
      
}

在構造方法中調用Context的方法就會崩潰,在onCreate()方法中調用Context的方法就一切正常,那麼這兩個方法之間到底發生了什麼事情呢?咱們從新回顧一下ContextWrapper類的源碼,ContextWrapper中有一個attachBaseContext()方法,這個方法會將傳入的一個Context參數賦值給mBase對象,以後mBase對象就有值了。而咱們又知道,全部Context的方法都是調用這個mBase對象的同名方法,那麼也就是說若是在mBase對象還沒賦值的狀況下就去調用Context中的任何一個方法時,就會出現空指針異常,上面的代碼就是這種狀況。Application中方法的執行順序以下圖所示:

Application中在onCreate()方法裏去初始化各類全局的變量數據是一種比較推薦的作法,可是若是你想把初始化的時間點提早到極致,也能夠去重寫attachBaseContext()方法,以下所示:

public class MyApplication extends Application {  
      
    @Override  
    protected void attachBaseContext(Context base) {  
        // 在這裏調用Context的方法會崩潰  
        super.attachBaseContext(base);  
        // 在這裏能夠正常調用Context的方法  
    }  
      
}

 

如下原文:http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=2650661511&idx=1&sn=1b7390e2c971e50a0db4d07c7b9ebb6f&scene=4#wechat_redirect

 

context使用表:

 

Context相關的內存泄漏問題

在討論內存泄漏以前,先簡單的說說Android中內存的回收

 

Dalivik虛擬機扮演了常規的垃圾回收角色,爲了GC可以從App中及時回收內存,咱們須要時時刻刻在適當的時機來釋放引用對象,Dalvik的GC會自動把離開活動線程的對象進行回收。

 

什麼是Android內存泄漏:

 

雖然Android是一個自動管理內存的開發環境,可是垃圾回收器只會移除那些已經失去引用的、不可達的對象,在十幾萬、幾十萬行代碼中,因爲你的失誤使得一個本應該被銷燬的對象仍然被錯誤的持有,那麼該對象就永遠不會被釋放掉,這些已經沒有任何價值的對象,仍然佔據彙集在你的堆內存中,GC就會被頻繁觸發,多說幾句,若是手機不錯,一次GC的時間70毫秒,不會對應用的性能產生什麼影響,可是若是一個手機的性能不是那麼出色,一次GC時間120毫秒,出現大量的GC操做,我相信用戶就能感受到了吧。這些無用的引用堆積在堆內存中,越積越多最終致使Crash,有關一些性能優化推薦給你們一個我總結的博客。

 

「Android性能優化總結」

 

有些跑題了,咱們趕忙來看看什麼狀況下Context會引起內存泄漏

 

  • 錯誤的單例模式

咱們來分析一下這個非線程安全的單例模式,假設你在Activity A去getInstance得到instance對象,順手傳了一個this,好了,如今一個常駐內存的Singleton保存了你傳入Activity A的對象,而且一直持有Activity A的引用,這樣即便你Activity被銷燬掉,可是由於它的引用還存在於一個Singleton中,是不可能被GC掉的,這樣就致使了內存泄漏。

 

  • View持有Activity的引用

再來分析一下,有一個靜態的Drawable對象,當我給ImageView設置這個Drawable時,ImageView像上面那個例子同樣,保存了這個mDrawable的引用(你們能夠點開源碼705行去看,不少UI組件都是統一的操做,一直持有傳入的對象),然而ImageView傳入了this,也就是ImageView一樣持有一個MainActivity的mContext。由於被static修飾的mDrawable是常駐內存的,MainActivity是它的間接引用,因此當MainActivity被銷燬時,也不能被GC掉,因此也形成了內存泄漏。

 

使用Context的正確姿式

通俗一點說,Context形成的內存泄漏,幾乎都是當Context銷燬的時候,卻還被各類不合理、無故地引用着。那麼哪一個Context對象是不會被銷燬的呢?對了,Application的Context對象能夠理解爲隨着進程存在的,因此當Application的Context能搞定的狀況下,而且生命週期長的對象,優先使用Application的Context

調用一行代碼:

 

LaucherApplication.getContext();

 

回頭看看上面那張表格,顯然Application的Context不是萬能的,涉及UI加載操做時,彷佛咱們只能使用Activity的Context,因此你當你使用Activity的Context時,你要對持有Activity的對象心中有數,保證它能隨着生命週期的銷燬而被回收,慎用static關鍵字,不要由於方便訪問就各類static亂入。

 

多說一點,上表中Layout Inflation中只能使用Activity的Context,而各類View在建立時,須要傳入的Context參數也是Activity的,你們懂了吧,當解析XML文件的時候,傳入的參數也就統一了,相信你們必定能想明白這點。

 

寫在最後:

 

給你們推薦一個內存檢測的自動化工具,LeakCanary,可是當你曾經寫出的代碼不規範不負責,已經達到十幾萬行,幾十萬行的時候,再去抽絲剝繭試圖解開已經打上層層死結的引用關聯,是很是難的。因此平時仍是要注意下細節哈~

相關文章
相關標籤/搜索