編輯:關於Android編程
相信Android開發者沒有人不知道Context,每一個工程、每一個組件都在用。但是正是因為太熟悉了,反而不會仔細了解它。這篇文章就來說說大家熟視而無睹的Context。
老規矩,在節目開始之前,先來一個搞笑段子:
學渣問學霸:150分的卷子,怎樣才能考到140分?
學霸回答:少答5道選擇題就可以了啊~
Context的中文翻譯為:語境; 上下文; 背景; 環境,在開發中我們經常說稱之為“上下文”,那麼這個“上下文”到底是指什麼意思呢?在語文中,我們可以理解為語境,在程序中,我們可以理解為當前對象在程序中所處的一個環境,一個與系統交互的過程。比如微信聊天,此時的“環境”是指聊天的界面以及相關的數據請求與傳輸,Context在加載資源、啟動Activity、獲取系統服務、創建View等操作都要參與。
那Context到底是什麼呢?一個Activity就是一個Context,一個Service也是一個Context。Android程序員把“場景”抽象為Context類,他們認為用戶和操作系統的每一次交互都是一個場景,比如打電話、發短信,這些都是一個有界面的場景,還有一些沒有界面的場景,比如後台運行的服務(Service)。一個應用程序可以認為是一個工作環境,用戶在這個環境中會切換到不同的場景,這就像一個前台秘書,她可能需要接待客人,可能要打印文件,還可能要接聽客戶電話,而這些就稱之為不同的場景,前台秘書可以稱之為一個應用程序。
如何生動形象的理解Context
上面的概念中采用了通俗的理解方式,將Context理解為“上下文”或者“場景”,如果你仍然覺得很抽象,不好理解。在這裡我給出一個可能不是很恰當的比喻,希望有助於大家的理解:一個Android應用程序,可以理解為一部電影或者一部電視劇,Activity,Service,Broadcast Receiver,Content Provider這四大組件就好比是這部戲裡的四個主角:胡歌,霍建華,詩詩,Baby。他們是由劇組(系統)一開始就定好了的,整部戲就是由這四位主演領銜擔綱的,所以這四位主角並不是大街上隨隨便便拉個人(new 一個對象)都能演的。有了演員當然也得有攝像機拍攝啊,他們必須通過鏡頭(Context)才能將戲傳遞給觀眾,這也就正對應說四大組件(四位主角)必須工作在Context環境下(攝像機鏡頭)。那Button,TextView,LinearLayout這些控件呢,就好比是這部戲裡的配角或者說群眾演員,他們顯然沒有這麼重用,隨便一個路人甲路人乙都能演(可以new一個對象),但是他們也必須要面對鏡頭(工作在Context環境下),所以Button mButton=new Button(Context)是可以的。雖然不很恰當,但還是很容易理解的,希望有幫助。
/**
* Interface to global information about an application environment. This is
* an abstract class whose implementation is provided by
* the Android system. It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
/**
* File creation mode: the default mode, where the created file can only
* be accessed by the calling application (or all applications sharing the
* same user ID).
* @see #MODE_WORLD_READABLE
* @see #MODE_WORLD_WRITEABLE
*/
public static final int MODE_PRIVATE = 0x0000;
public static final int MODE_WORLD_WRITEABLE = 0x0002;
public static final int MODE_APPEND = 0x8000;
public static final int MODE_MULTI_PROCESS = 0x0004;
.
.
.
}
源碼中的注釋是這麼來解釋Context的:Context提供了關於應用環境全局信息的接口。它是一個抽象類,它的執行被Android系統所提供。它允許獲取以應用為特征的資源和類型,是一個統領一些資源(應用程序環境變量等)的上下文。就是說,它描述一個應用程序環境的信息(即上下文);是一個抽象類,Android提供了該抽象類的具體實現類;通過它我們可以獲取應用程序的資源和類(包括應用級別操作,如啟動Activity,發廣播,接受Intent等)。既然上面Context是一個抽象類,那麼肯定有他的實現類咯,我們在Context的源碼中通過IDE可以查看到他的子類最終可以得到如下關系圖:
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中的方法。
其實這個問題本身並沒有什麼意義,關鍵還是在於對Context的理解,從上面的關系圖我們已經可以得出答案了,在應用程序中Context的具體實現子類就是:Activity,Service,Application。那麼Context數量=Activity數量+Service數量+1。當然如果你足夠細心,可能會有疑問:我們常說四大組件,這裡怎麼只有Activity,Service持有Context,那Broadcast Receiver,Content Provider呢?Broadcast Receiver,Content Provider並不是Context的子類,他們所持有的Context都是其他地方傳過去的,所以並不計入Context總數。上面的關系圖也從另外一個側面告訴我們Context類在整個Android系統中的地位是多麼的崇高,因為很顯然Activity,Service,Application都是其子類,其地位和作用不言而喻。
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實例就可以為所欲為,它的使用還是有一些規則限制的。由於Context的具體實例是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog。出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啟動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會出錯。
從上圖我們可以發現Activity所持有的Context的作用域最廣,無所不能。因為Activity繼承自ContextThemeWrapper,而Application和Service繼承自ContextWrapper,很顯然ContextThemeWrapper在ContextWrapper的基礎上又做了一些操作使得Activity變得更強大,這裡我就不再貼源碼給大家分析了,有興趣的童鞋可以自己查查源碼。上圖中的YES和NO我也不再做過多的解釋了,這裡我說一下上圖中Application和Service所不推薦的兩種使用情況。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxzdHJvbmc+MaO6PC9zdHJvbmc+yOe5+87Sw8fTw0FwcGxpY2F0aW9uQ29udGV4dMilxvS2r9K7uPZMYXVuY2hNb2RlzqpzdGFuZGFyZLXEQWN0aXZpdHm1xMqxuvK74bGotO1hbmRyb2lkLnV0aWwuQW5kcm9pZFJ1bnRpbWVFeGNlcHRpb246IENhbGxpbmcgc3RhcnRBY3Rpdml0eSBmcm9tIG91dHNpZGUgb2YgYW4gQWN0aXZpdHkgY29udGV4dCByZXF1aXJlcyB0aGUgRkxBR19BQ1RJVklUWV9ORVdfVEFTSyBmbGFnLiBJcyB0aGlzIHJlYWxseSB3aGF0IHlvdSB3YW50P9XiysfS8s6qt8dBY3Rpdml0ecDg0M21xENvbnRleHSyosO709DL+c69tcTIzs7x1bujrMv50tS0/cb0tq+1xEFjdGl2aXR5vs3V0rK7tb3Vu8HLoaO94r721eK49s7KzOK1xLe9t6i+zcrHzqq0/cb0tq+1xEFjdGl2aXR51ri2qEZMQUdfQUNUSVZJVFlfTkVXX1RBU0ux6rzHzrujrNXi0fnG9LavtcTKsbryvs3Oqsv8tLS9qNK7uPbQwrXEyM7O8dW7o6y2+LTLyrFBY3Rpdml0ecrH0tRzaW5nbGVUYXNrxKPKvcb0tq+1xKGjy/nT0NXi1tbTw0FwcGxpY2F0aW9uxvS2r0FjdGl2aXR5tcS3vcq9srvNxrz2yrnTw6OsU2VydmljZc2sQXBwbGljYXRpb26hozxiciAvPg0KPHN0cm9uZz4yo7o8L3N0cm9uZz7U2kFwcGxpY2F0aW9uus1TZXJ2aWNl1tDIpWxheW91dCBpbmZsYXRl0rLKx7rPt6i1xKOstavKx7vhyrnTw8+1zbPErMjPtcTW98zi0fnKvaOsyOe5+8Tj19S2qNLlwcvEs9Cp0fnKvb/JxNyyu7vhsbvKudPDoaPL+dLU1eLW1re9yr3SsrK7zca89sq508OhozxiciAvPg0K0ru+5Luw19y94aO6t7LKx7j6VUnP4LnYtcSjrLa806a4w8q508NBY3Rpdml0edf2zqpDb250ZXh0wLS0psDto7vG5Mv7tcTSu9CpstnX96OsU2VydmljZSxBY3Rpdml0eSxBcHBsaWNhdGlvbrXIyrXA/ba8v8nS1KOstbHIu8HLo6zXotLiQ29udGV4dNL908O1xLPW09CjrLfA1rnE2rTm0LnCqaGjPC9wPg0KPGgyIGlkPQ=="如何獲取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也可以。
getApplication()和getApplicationContext()
上面說到獲取當前Application對象用getApplicationContext,不知道你有沒有聯想到getApplication(),這兩個方法有什麼區別?相信這個問題會難倒不少開發者。
程序是不會騙人的,我們通過上面的代碼,打印得出兩者的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,因為前面已經說過了,Application本身就是一個Context,所以這裡獲取getApplicationContext()得到的結果就是Application本身的實例。那麼問題來了,既然這兩個方法得到的結果都是相同的,那麼Android為什麼要提供兩個功能重復的方法呢?實際上這兩個方法在作用域上有比較大的區別。getApplication()方法的語義性非常強,一看就知道是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的實例,這時就可以借助getApplicationContext()方法了。
publicclassMyReceiverextendsBroadcastReceiver{
@Override
publicvoidonReceive(Contextcontext,Intentintent){
ApplicationmyApp=(Application)context.getApplicationContext();
}
}
但Context並不能隨便亂用,用的不好有可能會引起內存洩露的問題,下面就示例兩種錯誤的引用方式。
錯誤的單例模式
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
正確的單例模式
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context.getApplicationContext());
}
return instance;
}
}
這是一個非線程安全的單例模式,instance作為靜態對象,其生命周期要長於普通的對象,其中也包含Activity,假如Activity A去getInstance獲得instance對象,傳入this,常駐內存的Singleton保存了你傳入的Activity A對象,並一直持有,即使Activity被銷毀掉,但因為它的引用還存在於一個Singleton中,就不可能被GC掉,這樣就導致了內存洩漏。
View持有Activity引用
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);
}
}
有一個靜態的Drawable對象當ImageView設置這個Drawable時,ImageView保存了mDrawable的引用,而ImageView傳入的this是MainActivity的mContext,因為被static修飾的mDrawable是常駐內存的,MainActivity是它的間接引用,MainActivity被銷毀時,也不能被GC掉,所以造成內存洩漏。
正確的做法
不使用單例,或者使用ApplicationContext,然後及時釋放不需要的內存。
正確使用Context
一般Context造成的內存洩漏,幾乎都是當Context銷毀的時候,卻因為被引用導致銷毀失敗,而Application的Context對象可以理解為隨著進程存在的,所以我們總結出使用Context的正確姿勢:
1:當Application的Context能搞定的情況下,並且生命周期長的對象,優先使用Application的Context。
2:不要讓生命周期長於Activity的對象持有到Activity的引用。
3:盡量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作為弱引用持有。
總之Context在Android系統中的地位很重要,它幾乎無所不能,但它也不是你想用就能隨便用的,謹防使用不當引起的內存問題。
上篇給大家介紹QQ5.0側滑菜單的視頻課程,對於側滑的時的動畫效果的實現有了新的認識,似乎打通了任督二脈,目前可以實現任意效果的側滑菜單了,感謝鴻洋大大!!用的是Hori
既然是通過相機來渲染場景,那麼沒有相機,我們也就什麼看不到了。THREE.js中提供了Camera類對相機這個角色進行抽象。相機將三維的場景投影到二維的屏幕,根據投影的方
效果圖: 這樣來寫: @Override protected void onCreate(Bundle savedInstanceSta
我用的是cocos2d-2.0-x-2.0.3 之前弄了一天也沒成功 今天來了下載了最新的ndk8 更新了sdk 又重新是了一遍 居然成功了,不知道是工具的版本問題還是哪
public class MainFragmentPa