溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何解析Android Context的各種細節(jié)

發(fā)布時間:2021-11-26 14:02:21 來源:億速云 閱讀:132 作者:柒染 欄目:開發(fā)技術(shù)

本篇文章為大家展示了如何解析Android Context的各種細節(jié),內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

Context相信所有的Android開發(fā)人員基本上每天都在接觸,因為它太常見了。但是這并不代表Context沒有什么東西好講的,實際上Context有太多小的細節(jié)并不被大家所關(guān)注,那么今天我們就來學習一下那些你所不知道的細節(jié)。

Context類型

我們知道,Android應(yīng)用都是使用Java語言來編寫的,那么大家可以思考一下,一個Android程序和一個Java程序,他們最大的區(qū)別在哪里?劃分界限又是什么呢?其實簡單點分析,Android程序不像Java程序一樣,隨便創(chuàng)建一個類,寫個main()方法就能跑了,而是要有一個完整的Android工程環(huán)境,在這個環(huán)境下,我們有像Activity、Service、BroadcastReceiver等系統(tǒng)組件,而這些組件并不是像一個普通的Java對象new一下就能創(chuàng)建實例的了,而是要有它們各自的上下文環(huán)境,也就是我們這里討論的Context??梢赃@樣講,Context是維持Android程序中各組件能夠正常工作的一個核心功能類。

下面我們來看一下Context的繼承結(jié)構(gòu):

如何解析Android Context的各種細節(jié)

Context的繼承結(jié)構(gòu)還是稍微有點復雜的,可以看到,直系子類有兩個,一個是ContextWrapper,一個是ContextImpl。那么從名字上就可以看出,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現(xiàn)類。而ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity。

那么在這里我們至少看到了幾個所比較熟悉的面孔,Activity、Service、還有Application。由此,其實我們就已經(jīng)可以得出結(jié)論了,Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各種承擔著不同的作用,但它們都屬于Context的一種,而它們具體Context的功能則是由ContextImpl類去實現(xiàn)的。

那么Context到底可以實現(xiàn)哪些功能呢?這個就實在是太多了,彈出Toast、啟動Activity、啟動Service、發(fā)送廣播、操作數(shù)據(jù)庫等等等等都需要用到Context。由于Context的具體能力是由ContextImpl類去實現(xiàn)的,因此在絕大多數(shù)場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog。。出于安全原因的考慮,Android是不允許Activity或Dialog憑空出現(xiàn)的,一個Activity的啟動必須要建立在另一個Activity的基礎(chǔ)之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會出錯。

Context數(shù)量

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

Context數(shù)量 = Activity數(shù)量 + Service數(shù)量 + 1

上面的1代表著Application的數(shù)量,因為一個應(yīng)用程序中可以有多個Activity和多個Service,但是只能有一個Application。

Application Context的設(shè)計

基本上每一個應(yīng)用程序都會有一個自己的Application,并讓它繼承自系統(tǒng)的Application類,然后在自己的Application類中去封裝一些通用的操作。其實這并不是Google所推薦的一種做法,因為這樣我們只是把Application當成了一個通用工具類來使用的,而實際上使用一個簡單的單例類也可以實現(xiàn)同樣的功能。但是根據(jù)我的觀察,有太多的項目都是這樣使用Application的。當然這種做法也并沒有什么副作用,只是說明還是有不少人對于Application理解的還有些欠缺。那么這里我們先來對Application的設(shè)計進行分析,講一些大家所不知道的細節(jié),然后再看一下平時使用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系統(tǒng)就會創(chuàng)建一個MyApplication的實例,如果這里不指定的話就會默認創(chuàng)建一個Application的實例。

public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);MyApplication myApp = (MyApplication) getApplication();Log.d("TAG", "getApplication is " + myApp);}}

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

如何解析Android Context的各種細節(jié)

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

public class MainActivity extends Activity {@Overrideprotected 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);}}

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

如何解析Android Context的各種細節(jié)

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

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

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

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

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

如何解析Android Context的各種細節(jié)

哦?這次得到的是不同的對象了,getBaseContext()方法得到的是一個ContextImpl對象。這個ContextImpl是不是感覺有點似曾相識?回去看一下Context的繼承結(jié)構(gòu)圖吧,ContextImpl正是上下文功能的實現(xiàn)類。也就是說像Application、Activity這樣的類其實并不會去具體實現(xiàn)Context的功能,而僅僅是做了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的。那么這樣的設(shè)計到底是怎么實現(xiàn)的呢?我們還是來看一下源碼吧。因為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()等等都是我們經(jīng)常要用到的方法。那么所有這些方法的實現(xiàn)又是什么樣的呢?其實所有ContextWrapper中方法的實現(xiàn)都非常統(tǒng)一,就是調(diào)用了mBase對象中對應(yīng)當前方法名的方法。

那么這個mBase對象又是什么呢?我們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數(shù),并把這個參數(shù)賦值給了mBase對象。而attachBaseContext()方法其實是由系統(tǒng)來調(diào)用的,它會把ContextImpl對象作為參數(shù)傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,之后ContextWrapper中的所有方法其實都是通過這種委托的機制交由ContextImpl去具體實現(xiàn)的,所以說ContextImpl是上下文功能的實現(xiàn)類是非常準確的。

那么另外再看一下我們剛剛打印的getBaseContext()方法,在第26行。這個方法只有一行代碼,就是返回了mBase對象而已,而mBase對象其實就是ContextImpl對象,因此剛才的打印結(jié)果也得到了印證。

使用Application的問題

雖說Application的用法確實非常簡單,但是我們平時的開發(fā)工作當中也著實存在著不少Application誤用的場景,那么今天就來看一看有哪些比較容易犯錯的地方是我們應(yīng)該注意的。

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

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

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

如何解析Android Context的各種細節(jié)

應(yīng)用程序一啟動就立刻崩潰了,報的是一個空指針異常??雌饋砗孟裢唵蔚囊欢未a,怎么就會成空指針了呢?但是如果你嘗試把代碼改成下面的寫法,就會發(fā)現(xiàn)一切正常了:

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

運行結(jié)果如下所示:

如何解析Android Context的各種細節(jié)

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

如何解析Android Context的各種細節(jié)

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

public class MyApplication extends Application {@Overrideprotected void attachBaseContext(Context base) {// 在這里調(diào)用Context的方法會崩潰super.attachBaseContext(base);// 在這里可以正常調(diào)用Context的方法}}

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

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

如何解析Android Context的各種細節(jié)

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

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

public class MyApplication extends Application {private static MyApplication app;public static MyApplication getInstance() {if (app == null) {app = new MyApplication();}return app;}}

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

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

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

public class MyApplication extends Application {private static MyApplication app;public static MyApplication getInstance() {return app;}@Overridepublic void onCreate() {super.onCreate();app = this;}}

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

上述內(nèi)容就是如何解析Android Context的各種細節(jié),你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI