Android Context完全解析與各種獲取Context方法*


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類去實現的。

那么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,否則將會出錯。

Context的應用場景

 

大家注意看到有一些NO上添加了一些數字,其實這些從能力上來說是YES,但是為什么說是NO呢?下面一個一個解釋:

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

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

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

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

 

好了,這里我們看下表格,重點看Activity和Application,可以看到,和UI相關的方法基本都不建議或者不可使用Application,並且,前三個操作基本不可能在Application中出現。實際上,只要把握住一點,凡是跟UI相關的,都應該使用Activity做為Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,當然了,注意Context引用的持有,防止內存泄漏。

 

Context數量

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

[plain]  view plain  copy
 
  1. Context數量 = Activity數量 + Service數量 + 1  

上面的1代表着Application的數量,因為一個應用程序中可以有多個Activity和多個Service,但是只能有一個Application。

Android獲取各種Context

1. getApplicationContext() :

這個函數返回的這個Application的上下文,所以是與app掛鈎的,所以在整個生命周期里面都是不變的,這個好理解,但是使用的時候要注意,該context是和引用的生命周期一致的,所以和activity生命周期掛鈎的任務不要使用該context,比如網絡訪問,防止內存泄露

2. getBasecontext():

stackoverflow上面寫的是,這個函數不應該被使用,用Context代替,而Context是與activity相關連,所以當activity死亡后可能會被destroyed,我舉個我自己寫的例子

public Dialog displayDialog(int choice) {  
    switch(choice){  
    case 0:  
      AlertDialog aDialog = new AlertDialog.Builder(this)  
      .setIcon(R.drawable.ic_launcher)  
      .setTitle("Hello World")  
      .setPositiveButton("OK", new DialogInterface.OnClickListener() {  
  
      @Override  
      public void onClick(DialogInterface arg0, int arg1) {  
        Toast.makeText(getBaseContext(), "OK clicked", Toast.LENGTH_SHORT).show();  
      }  
    });
  }
}

  

這個例子中的getBaseContext()就不能被this代替,因為上面的this返回的是這個activity的context,而在這個onClick函數中如果使用this的話,則返回的是這個AlertDialog的context,所以要使用的是當前activity名.this 去使用,比如當前activity為 TestActivity,那么在里面就是用TestActivity.this即可

 3. getApplication():

getApplication只能被Activity和Services使用,雖然在現在的Android的實現中,getApplication和getApplicationContext返回一樣的對象,但也不能保證這兩個函數一樣(例如在特殊的提供者來說),所以如果你想得到你在Manifest文件里面注冊的App class,你不要去調用getApplicationContext,以為你可能得不到你所要的app實例(你顯然有測試框架的經驗)。。。。

翻譯完成,一目了然(哪里翻譯錯誤,請指出,水B一只),原文:

getApplication() is available to Activity and Services only. Although in current Android Activity and Service implementations, getApplication() and getApplicationContext() return the same object, there is no guarantee that this will always be the case (for example, in a specific vendor implementation). So if you want the Application class you registered in the Manifest, you should never call getApplicationContext() and cast it to your application, because it may not be the application instance (which you obviously experienced with the test framework).

4. getParent() :

返回activity的上下文,如果這個子視圖的話,換句話說,就是當在子視圖里面調用的話就返回一個帶有子視圖的activity對象,一目了然。。。

5.getActivity():

在fragment中使用,返回該fragment所依附的activity上下文

6.this

記住Activity,Service類,Application類是繼承自Context類的,所以在有的時候需要上下文,只需要使用this關鍵字即可,但是有的時候再線程里面,this關鍵字的意義就改變了,但這個時候如果需要上下文,則需要使用 類名.this,這樣就可以了

這里有點注意的:

做項目時遇見的,提一下吧,動態注冊廣播,在調用registerBroadcast函數的時候,需要傳入一個上下文和broadcastReceiver,查看源碼可以知道,存儲的時候context是作為一個key的作用,所以使用同一個context來注冊同一個廣播,onreceive只會調用一次,但是如果使用不同的context,則會調用多次,雖然不調用unregisterBroadcast有時也沒事,不會報錯,但是一定不要忘記取消注銷

后續:為了簡化context的使用方法,現在有這么一種方法,就是在Application類里面維護一個弱引用:

/** 用來保存當前該Application的context */  
private static Context instance;  
/** 用來保存最新打開頁面的context */  
private volatile static WeakReference<Context> instanceRef = null;  

再寫一個方法,

public static Context getInstance(){  
        if (instanceRef == null || instanceRef.get() == null){  
            synchronized (RootApplication.class) {  
                if (instanceRef == null || instanceRef.get() == null) {  
                    Context context = ActivityManager.getInstance().getActivity();  
                    if (context != null)  
                        instanceRef = new WeakReference<>(context);  
                    else {  
                        instanceRef = new WeakReference<>(instance);  
                        L.w("請確保RootActivity調用setInstanceRef方法");  
                    }  
                }  
            }  
        }  
        return instanceRef.get();  
    }  

  

最后在應用的Activity基類中(這個應該有的吧)加上兩個語句:

public void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    RootApplication.setInstanceRef(this);  
}  
protected void onResume() {  
    super.onResume();  
    //也要在onresume函數里面進行設置,保證弱引用一直引用當前的可見頁面  
    RootApplication.setInstanceRef(this);  
}  

  

這樣每次調用application的getInstance()方法一定能夠返回一個context,而且是當前唯一可見activity的context,其他地方就可以直接使用了,不用到處傳遞context,再此處統一維護即可,

Context泄露:Handler&內部類

復制代碼
1 public class SampleActivity extends Activity {
2 
3   private final Handler mLeakyHandler = new Handler() {
4     @Override
5     public void handleMessage(Message msg) {
6       // ... 
7     }
8   }
9 }
復制代碼

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

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

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

那么,到底是內存泄漏?好像很難懂,讓我們以下面的代碼作為一個例子

復制代碼
1 public class SampleActivity extends Activity {
 2  
 3   private final Handler mLeakyHandler = new Handler() {
 4     @Override
 5     public void handleMessage(Message msg) {
 6       // ...
 7     }
 8   }
 9  
10   @Override
11   protected void onCreate(Bundle savedInstanceState) {
12     super.onCreate(savedInstanceState);
13  
14     // 延時10分鍾發送一個消息
15     mLeakyHandler.postDelayed(new Runnable() {
16       @Override
17       public void run() { }
18     }, 60 * 10 * 1000);
19  
20     // 返回前一個Activity
21     finish();
22   }
23 }
復制代碼

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

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

復制代碼
1 public class SampleActivity extends Activity {
 2     /**
 3     * 匿名類的靜態實例不會隱式持有他們外部類的引用
 4     */
 5     private static final Runnable sRunnable = new Runnable() {
 6             @Override
 7             public void run() {
 8             }
 9         };
10 
11     private final MyHandler mHandler = new MyHandler(this);
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16 
17         // 延時10分鍾發送一個消息.
18         mHandler.postDelayed(sRunnable, 60 * 10 * 1000);
19 
20         // 返回前一個Activity
21         finish();
22     }
23 
24     /**
25     * 靜態內部類的實例不會隱式持有他們外部類的引用。
26     */
27     private static class MyHandler extends Handler {
28         private final WeakReference<SampleActivity> mActivity;
29 
30         public MyHandler(SampleActivity activity) {
31             mActivity = new WeakReference<SampleActivity>(activity);
32         }
33 
34         @Override
35         public void handleMessage(Message msg) {
36             SampleActivity activity = mActivity.get();
37 
38             if (activity != null) {
39                 // ...
40             }
41         }
42     }
43 }
復制代碼

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

Application Context的設計

基本上每一個應用程序都會有一個自己的Application,並讓它繼承自系統的Application類,然后在自己的Application類中去封裝一些通用的操作。其實這並不是Google所推薦的一種做法,因為這樣我們只是把Application當成了一個通用工具類來使用的,而實際上使用一個簡單的單例類也可以實現同樣的功能。但是根據我的觀察,有太多的項目都是這樣使用Application的。當然這種做法也並沒有什么副作用,只是說明還是有不少人對於Application理解的還有些欠缺。那么這里我們先來對Application的設計進行分析,講一些大家所不知道的細節,然后再看一下平時使用Application的問題。

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

[html]  view plain  copy
 
  1. <application  
  2.     android:name=".MyApplication"  
  3.     android:allowBackup="true"  
  4.     android:icon="@drawable/ic_launcher"  
  5.     android:label="@string/app_name"  
  6.     android:theme="@style/AppTheme" >  
  7.     ......  
  8. </application>  

指定完成后,當我們的程序啟動時Android系統就會創建一個MyApplication的實例,如果這里不指定的話就會默認創建一個Application的實例。

 

前面提到過,現在很多的Application都是被當作通用工具類來使用的,那么既然作為一個通用工具類,我們要怎樣才能獲取到它的實例呢?如下所示:

[java]  view plain  copy
 
  1. public class MainActivity extends Activity {  
  2.       
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         MyApplication myApp = (MyApplication) getApplication();  
  8.         Log.d("TAG", "getApplication is " + myApp);  
  9.     }  
  10.       
  11. }  

可以看到,代碼很簡單,只需要調用getApplication()方法就能拿到我們自定義的Application的實例了,打印結果如下所示:

 

那么除了getApplication()方法,其實還有一個getApplicationContext()方法,這兩個方法看上去好像有點關聯,那么它們的區別是什么呢?我們將代碼修改一下:

[java]  view plain  copy
 
  1. public class MainActivity extends Activity {  
  2.       
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         MyApplication myApp = (MyApplication) getApplication();  
  8.         Log.d("TAG", "getApplication is " + myApp);  
  9.         Context appContext = getApplicationContext();  
  10.         Log.d("TAG", "getApplicationContext is " + appContext);  
  11.     }  
  12.       
  13. }  

同樣,我們把getApplicationContext()的結果打印了出來,現在重新運行代碼,結果如下圖所示:

 

咦?好像打印出的結果是一樣的呀,連后面的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,因為前面已經說過了,Application本身就是一個Context,所以這里獲取getApplicationContext()得到的結果就是MyApplication本身的實例。

那么有的朋友可能就會問了,既然這兩個方法得到的結果都是相同的,那么Android為什么要提供兩個功能重復的方法呢?實際上這兩個方法在作用域上有比較大的區別。getApplication()方法的語義性非常強,一看就知道是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到。那么也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的實例,這時就可以借助getApplicationContext()方法了,如下所示:

[java]  view plain  copy
 
  1. public class MyReceiver extends BroadcastReceiver {  
  2.   
  3.     @Override  
  4.     public void onReceive(Context context, Intent intent) {  
  5.         MyApplication myApp = (MyApplication) context.getApplicationContext();  
  6.         Log.d("TAG", "myApp is " + myApp);  
  7.     }  
  8.   
  9. }  

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

那么更加細心的朋友會發現,除了這兩個方法之外,其實還有一個getBaseContext()方法,這個baseContext又是什么東西呢?我們還是通過打印的方式來驗證一下:

哦?這次得到的是不同的對象了,getBaseContext()方法得到的是一個ContextImpl對象。這個ContextImpl是不是感覺有點似曾相識?回去看一下Context的繼承結構圖吧,ContextImpl正是上下文功能的實現類。也就是說像Application、Activity這樣的類其實並不會去具體實現Context的功能,而僅僅是做了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的。那么這樣的設計到底是怎么實現的呢?我們還是來看一下源碼吧。因為Application、Activity、Service都是直接或間接繼承自ContextWrapper的,我們就直接看ContextWrapper的源碼,如下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Proxying implementation of Context that simply delegates all of its calls to 
  3.  * another Context.  Can be subclassed to modify behavior without changing 
  4.  * the original Context. 
  5.  */  
  6. public class ContextWrapper extends Context {  
  7.     Context mBase;  
  8.       
  9.     /** 
  10.      * Set the base context for this ContextWrapper.  All calls will then be 
  11.      * delegated to the base context.  Throws 
  12.      * IllegalStateException if a base context has already been set. 
  13.      *  
  14.      * @param base The new base context for this wrapper. 
  15.      */  
  16.     protected void attachBaseContext(Context base) {  
  17.         if (mBase != null) {  
  18.             throw new IllegalStateException("Base context already set");  
  19.         }  
  20.         mBase = base;  
  21.     }  
  22.   
  23.     /** 
  24.      * @return the base context as set by the constructor or setBaseContext 
  25.      */  
  26.     public Context getBaseContext() {  
  27.         return mBase;  
  28.     }  
  29.   
  30.     @Override  
  31.     public AssetManager getAssets() {  
  32.         return mBase.getAssets();  
  33.     }  
  34.   
  35.     @Override  
  36.     public Resources getResources() {  
  37.         return mBase.getResources();  
  38.     }  
  39.   
  40.     @Override  
  41.     public ContentResolver getContentResolver() {  
  42.         return mBase.getContentResolver();  
  43.     }  
  44.   
  45.     @Override  
  46.     public Looper getMainLooper() {  
  47.         return mBase.getMainLooper();  
  48.     }  
  49.       
  50.     @Override  
  51.     public Context getApplicationContext() {  
  52.         return mBase.getApplicationContext();  
  53.     }  
  54.   
  55.     @Override  
  56.     public String getPackageName() {  
  57.         return mBase.getPackageName();  
  58.     }  
  59.   
  60.     @Override  
  61.     public void startActivity(Intent intent) {  
  62.         mBase.startActivity(intent);  
  63.     }  
  64.       
  65.     @Override  
  66.     public void sendBroadcast(Intent intent) {  
  67.         mBase.sendBroadcast(intent);  
  68.     }  
  69.   
  70.     @Override  
  71.     public Intent registerReceiver(  
  72.         BroadcastReceiver receiver, IntentFilter filter) {  
  73.         return mBase.registerReceiver(receiver, filter);  
  74.     }  
  75.   
  76.     @Override  
  77.     public void unregisterReceiver(BroadcastReceiver receiver) {  
  78.         mBase.unregisterReceiver(receiver);  
  79.     }  
  80.   
  81.     @Override  
  82.     public ComponentName startService(Intent service) {  
  83.         return mBase.startService(service);  
  84.     }  
  85.   
  86.     @Override  
  87.     public boolean stopService(Intent name) {  
  88.         return mBase.stopService(name);  
  89.     }  
  90.   
  91.     @Override  
  92.     public boolean bindService(Intent service, ServiceConnection conn,  
  93.             int flags) {  
  94.         return mBase.bindService(service, conn, flags);  
  95.     }  
  96.   
  97.     @Override  
  98.     public void unbindService(ServiceConnection conn) {  
  99.         mBase.unbindService(conn);  
  100.     }  
  101.   
  102.     @Override  
  103.     public Object getSystemService(String name) {  
  104.         return mBase.getSystemService(name);  
  105.     }  
  106.   
  107.     ......  
  108. }  

由於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的各種方法呢?我們來做個實驗試一下就知道了:

[java]  view plain  copy
 
  1. public class MyApplication extends Application {  
  2.       
  3.     public MyApplication() {  
  4.         String packageName = getPackageName();  
  5.         Log.d("TAG", "package name is " + packageName);  
  6.     }  
  7.       
  8. }  

這是一個非常簡單的自定義Application,我們在MyApplication的構造方法當中獲取了當前應用程序的包名,並打印出來。獲取包名使用了getPackageName()方法,這個方法就是由Context提供的。那么上面的代碼能正常運行嗎?跑一下就知道了,你將會看到如下所示的結果:

 

應用程序一啟動就立刻崩潰了,報的是一個空指針異常。看起來好像挺簡單的一段代碼,怎么就會成空指針了呢?但是如果你嘗試把代碼改成下面的寫法,就會發現一切正常了:

[java]  view plain  copy
 
  1. public class MyApplication extends Application {  
  2.       
  3.     @Override  
  4.     public void onCreate() {  
  5.         super.onCreate();  
  6.         String packageName = getPackageName();  
  7.         Log.d("TAG", "package name is " + packageName);  
  8.     }  
  9.       
  10. }  

運行結果如下所示:

 

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

Application中在onCreate()方法里去初始化各種全局的變量數據是一種比較推薦的做法,但是如果你想把初始化的時間點提前到極致,也可以去重寫attachBaseContext()方法,如下所示:

[java]  view plain  copy
 
  1. public class MyApplication extends Application {  
  2.       
  3.     @Override  
  4.     protected void attachBaseContext(Context base) {  
  5.         // 在這里調用Context的方法會崩潰  
  6.         super.attachBaseContext(base);  
  7.         // 在這里可以正常調用Context的方法  
  8.     }  
  9.       
  10. }  

以上是我們平時在使用Application時需要注意的一個點,下面再來介紹另外一種非常普遍的Application誤用情況。

 

其實Android官方並不太推薦我們使用自定義的Application,基本上只有需要做一些全局初始化的時候可能才需要用到自定義Application,官方文檔描述如下:

但是就我的觀察而言,現在自定義Application的使用情況基本上可以達到100%了,也就是我們平時自己寫測試demo的時候可能不會使用,正式的項目幾乎全部都會使用自定義Application。可是使用歸使用,有不少項目對自定義Application的用法並不到位,正如官方文檔中所表述的一樣,多數項目只是把自定義Application當成了一個通用工具類,而這個功能並不需要借助Application來實現,使用單例可能是一種更加標准的方式。

不過自定義Application也並沒有什么副作用,它和單例模式二選一都可以實現同樣的功能,但是我見過有一些項目,會把自定義Application和單例模式混合到一起使用,這就讓人大跌眼鏡了。一個非常典型的例子如下所示:

[java]  view plain  copy
 
  1. public class MyApplication extends Application {  
  2.       
  3.     private static MyApplication app;  
  4.       
  5.     public static MyApplication getInstance() {  
  6.         if (app == null) {  
  7.             app = new MyApplication();  
  8.         }  
  9.         return app;  
  10.     }  
  11.       
  12. }  

就像單例模式一樣,這里提供了一個getInstance()方法,用於獲取MyApplication的實例,有了這個實例之后,就可以調用MyApplication中的各種工具方法了。

 

但是這種寫法對嗎?這種寫法是大錯特錯!因為我們知道Application是屬於系統組件,系統組件的實例是要由系統來去創建的,如果這里我們自己去new一個MyApplication的實例,它就只是一個普通的Java對象而已,而不具備任何Context的能力。有很多人向我反饋使用 LitePal 時發生了空指針錯誤其實都是由於這個原因,因為你提供給LitePal的只是一個普通的Java對象,它無法通過這個對象來進行Context操作。

那么如果真的想要提供一個獲取MyApplication實例的方法,比較標准的寫法又是什么樣的呢?其實這里我們只需謹記一點,Application全局只有一個,它本身就已經是單例了,無需再用單例模式去為它做多重實例保護了,代碼如下所示:

[java]  view plain  copy
 
  1. public class MyApplication extends Application {  
  2.       
  3.     private static MyApplication app;  
  4.       
  5.     public static MyApplication getInstance() {  
  6.         return app;  
  7.     }  
  8.       
  9.     @Override  
  10.     public void onCreate() {  
  11.         super.onCreate();  
  12.         app = this;  
  13.     }  
  14.       
  15. }  

getInstance()方法可以照常提供,但是里面不要做任何邏輯判斷,直接返回app對象就可以了,而app對象又是什么呢?在onCreate()方法中我們將app對象賦值成this,this就是當前Application的實例,那么app也就是當前Application的實例了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM