一,Context類簇中的類概述
Context的API文檔
類關係:
Context類
Context是一個抽象類,源碼位於android.content包中。描述的是一個應用程序環境的信息,即上下文。通過它我們可以獲取應用程序的資源和類,也包括一些應用級別操作,例如:啓動一個Activity,發送廣播,接受Intent信息 ,得到各種服務(getSystemService)等。在下面再詳細說說。
ContextImpl類
ContextImpl是Context抽象類另一個直接子類,有一個私有的構造方法。源碼位於android.app包中,但它在API文檔中找不到,是一個默認訪問權限的類,也就是說它只允許android.app包中的類可以調用它,或者只有和它同包的類纔可以通過其父類的方法使用它。它是Context抽象類的具體實現,也是說Context抽象類中抽象方法在ContextImpl類中都有實現,比如:
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
同時,ContextImpl類又通過自己的成員變量mOuterContext來引用了與它關聯的一個Activity組件,這樣,ContextImpl類也可以將一些操作轉發給Activity組件來處理。請注意,該函數的大部分功能都是直接調用其類成員mPackageInfo來完成。
ContextWrapper類
ContextWrapper也是Context抽象類直接子類,是其包裝類,也位於android.app中,在其類中聲明瞭一個Context引用mBase,指向一個ContextIml實例,一般在創建Application、Service、Activity時賦值,構造方法:
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
Wrapper是包裝的意思,表示其是對Context類進行了包裝的一個類,它把Context類的方法都類似如下的形式進行了重寫:
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
@Override
public ComponentName startService(Intent service) {
return mBase.startService(service);
}
如果重寫的是沒返回值,則直接調用父類的此方法;如果重寫的有返回值,則返回調用父類返回的值。
因爲ContextWrapper類和ContextImpl類處於同一包中,所以當調用ContextWrapper類中方法時,無論是通過Context調用,還是在ContextWrapper的子類中調用(如果子類重寫沒super就不會調用ContextWrapper類中方法了),最終都是調用ContextImpl類中的同名方法。比如啓動Activity,在Activity裏啓動,調用的是Activity類裏重寫的 startActivity(),如果是在Service裏啓動,調用的就是ContextImpl類中的 startActivity()。這種模式是裝飾模式,Context是抽象構件類,ContextImpl類是具體構件類,ContextWrapper類是抽象裝飾類。後面附上有裝飾模式的介紹。
MockContext類
MockContext也是Context的子類,Mock以爲模擬,假裝,嘲笑,這裏可以理解爲模擬Context,源碼位於 android.test.mock包中,API文檔中找不到。類中的方法都類似如下:
@Override
public void startActivity(Intent intent) {
throw new UnsupportedOperationException();
}
當我們要測試一個模塊A,他依賴與其它模塊B,但是模塊B還沒實現或現在根本沒有,這時就要使用MockContext和其他同樣位於android.test.mock包中的類。通過它可以注入其他依賴,模擬Context,或者監聽測試的類。用法參考Mock在Android TDD中的使用
Application類
Application繼承自ContextWrapper,是維持全局應用狀態的基類,位於android.app包中。它還實現了ComponentCallbacks2,實現此接口的有Activity,Service,Content Provider,Fragment,Application及其子類,這個接口作用是細化內存管理,其對所有應用組件都非常有用。其構造方法爲:
public Application() {
super(null);
}
在無參的構造方法中調用了父類的構造方法,向父類構造方法傳的值爲null。
其有兩個子類MultiDexApplication,是multidex 需要用到的 ;MockApplication,和MockContext作用類似。
backupAgent類
Android:backupAgent用來設置備份代理。對於大部分應用程序來說,都或多或少保存着一些持久性的數據,比如數據庫和共享文件,或者有自己的配置信息。爲了保證這些數據和配置信息的安全性以及完整性,Android提供了這樣一個機制。
詳細參考backupAgent的用法
ContextThemeWrapper類
ContextThemeWrapper繼承自ContextWrapper,位於android.view包中,該類內部包含了主題(Theme)相關的接口,即android:theme屬性指定的。構造方法如下:
public ContextThemeWrapper() {
super(null);
}
public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
super(base);
mThemeResource = themeResId;
}
public ContextThemeWrapper(Context base, Resources.Theme theme) {
super(base);
mTheme = theme;
}
MutableContextWrapper類
MutableContextWrapper繼承自ContextWrapper, 位於包android.content中,是ContextWrapper的特別版,在其初始化設置後修改他的基礎上下文。很少用到,其只有一個公共方法setBaseContext(),關於什麼是BaseContext在下面會有解釋。
其他類
IsolatedContext和RenamingDelegatingContext在Api24被棄用,這裏就不介紹了。Service和Application的類繼承關係比較像,而Activity還多了一層繼承ContextThemeWrapper,這是因爲Activity有主題的概念,而Service是沒有界面的服務。然後下面就着重說說Context和Application。
二,Context
Context是上下文,代表的是運行的環境,它的實現爲ContextImpl,是應用運行中自動創建的。應用在三種情況下會創建Context對象(即通常說的context):
1> 創建Application 對象時,即第一次啓動app時。 整個App共一個Application對象,所以也只有一個Application 的Context,Application銷燬,它也銷燬;
2> 創建Activity對象時。Activity銷燬,它也銷燬;
3> 創建Service對象時。Service銷燬,它也銷燬。
由此可以得到應用程序App可以創建的Context(Activity和Service沒啓動就不會創建)個數公式一般爲:
總Context實例個數 = Service個數 + Activity個數 + 1(Application對應的Context對象)
Context對象的創建推薦閱讀Android中Context詳解
Context類中常用的方法
Context類中常用的方法有:
// 獲取應用程序包的AssetManager實例
public abstract AssetManager getAssets();
// 獲取應用程序包的Resources實例
public abstract Resources getResources();
// 獲取PackageManager實例,以查看全局package信息
public abstract PackageManager getPackageManager();
// 獲取應用程序包的ContentResolver實例
public abstract ContentResolver getContentResolver();
// 它返回當前進程的主線程的Looper,此線程分發調用給應用組件(activities, services等)
public abstract Looper getMainLooper();
// 返回當前進程的單實例全局Application對象的Context
public abstract Context getApplicationContext();
// 從string表中獲取本地化的、格式化的字符序列
public final CharSequence getText(int resId) {
return getResources().getText(resId);
}
// 從string表中獲取本地化的字符串
public final String getString(int resId) {
return getResources().getString(resId);
}
public final String getString(int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}
// 返回一個可用於獲取包中類信息的class loader
public abstract ClassLoader getClassLoader();
// 返回應用程序包名
public abstract String getPackageName();
// 返回應用程序信息
public abstract ApplicationInfo getApplicationInfo();
// 根據文件名獲取SharedPreferences
public abstract SharedPreferences getSharedPreferences(String name,
int mode);
// 其根目錄爲: Environment.getExternalStorageDirectory()
/*
* @param type The type of files directory to return. May be null for
* the root of the files directory or one of
* the following Environment constants for a subdirectory:
* {@link android.os.Environment#DIRECTORY_MUSIC},
* {@link android.os.Environment#DIRECTORY_PODCASTS},
* {@link android.os.Environment#DIRECTORY_RINGTONES},
* {@link android.os.Environment#DIRECTORY_ALARMS},
* {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
* {@link android.os.Environment#DIRECTORY_PICTURES}, or
* {@link android.os.Environment#DIRECTORY_MOVIES}.
*/
public abstract File getExternalFilesDir(String type);
// 返回應用程序obb文件路徑
public abstract File getObbDir();
// 啓動一個新的activity
public abstract void startActivity(Intent intent);
// 啓動一個新的activity
public void startActivityAsUser(Intent intent, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
// 啓動一個新的activity
// intent: 將被啓動的activity的描述信息
// options: 描述activity將如何被啓動
public abstract void startActivity(Intent intent, Bundle options);
// 啓動多個新的activity
public abstract void startActivities(Intent[] intents);
// 啓動多個新的activity
public abstract void startActivities(Intent[] intents, Bundle options);
// 廣播一個intent給所有感興趣的接收者,異步機制
public abstract void sendBroadcast(Intent intent);
// 廣播一個intent給所有感興趣的接收者,異步機制
public abstract void sendBroadcast(Intent intent,String receiverPermission);
//發送有序廣播
public abstract void sendOrderedBroadcast(Intent intent,String receiverPermission);
public abstract void sendOrderedBroadcast(Intent intent,
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras);
public abstract void sendBroadcastAsUser(Intent intent, UserHandle user);
public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission);
// 註冊一個BroadcastReceiver,且它將在主activity線程中運行
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter);
//取消註冊BroadcastReceiver
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler);
public abstract void unregisterReceiver(BroadcastReceiver receiver);
// 請求啓動一個application service
public abstract ComponentName startService(Intent service);
// 請求停止一個application service
public abstract boolean stopService(Intent service);
// 連接一個應用服務,它定義了application和service間的依賴關係
public abstract boolean bindService(Intent service, ServiceConnection conn,
int flags);
// 斷開一個應用服務,當服務重新開始時,將不再接收到調用,
// 且服務允許隨時停止
public abstract void unbindService(ServiceConnection conn);
// 返回系統級service
/*
* @see #WINDOW_SERVICE
* @see android.view.WindowManager
* @see #LAYOUT_INFLATER_SERVICE
* @see android.view.LayoutInflater
* @see #ACTIVITY_SERVICE
* @see android.app.ActivityManager
* @see #POWER_SERVICE
* @see android.os.PowerManager
* @see #ALARM_SERVICE
* @see android.app.AlarmManager
* @see #NOTIFICATION_SERVICE
* @see android.app.NotificationManager
* @see #KEYGUARD_SERVICE
* @see android.app.KeyguardManager
* @see #LOCATION_SERVICE
* @see android.location.LocationManager
* @see #SEARCH_SERVICE
* @see android.app.SearchManager
* @see #SENSOR_SERVICE
* @see android.hardware.SensorManager
* @see #STORAGE_SERVICE
* @see android.os.storage.StorageManager
* @see #VIBRATOR_SERVICE
* @see android.os.Vibrator
* @see #CONNECTIVITY_SERVICE
* @see android.net.ConnectivityManager
* @see #WIFI_SERVICE
* @see android.net.wifi.WifiManager
* @see #AUDIO_SERVICE
* @see android.media.AudioManager
* @see #MEDIA_ROUTER_SERVICE
* @see android.media.MediaRouter
* @see #TELEPHONY_SERVICE
* @see android.telephony.TelephonyManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
* @see android.app.UiModeManager
* @see #DOWNLOAD_SERVICE
* @see android.app.DownloadManager
*/
public abstract Object getSystemService(String name);
//檢查權限
public abstract int checkPermission(String permission, int pid, int uid);
// 返回一個新的與application name對應的Context對象
public abstract Context createPackageContext(String packageName,
int flags) throws PackageManager.NameNotFoundException;
// 返回基於當前Context對象的新對象,其資源與display相匹配
public abstract Context createDisplayContext(Display display);
Context其主要功能如下:
啓動Activity
啓動和停止Service
發送廣播消息(Intent)
註冊廣播消息(Intent)接收者
可以訪問APK中各種資源(如Resources和AssetManager等)
可以訪問Package的相關信息
APK的各種權限管理
Context幾乎算是對APK包無所不知的大管家,大家需要什麼,Context子類裏(通常在Activity和Service)直接調用就可以了。
獲取Context
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運行所在地的上下文
}
}
關於this和getBaseContext()
Activity和Application直接父類是 ContextWrapper,而不是Context,ContextWrapper.getBaseContext()最終調用的是ContextImpl.getBaseContext(),它和this雖然都可以代表Activity的Context,但指向的並不是同一個對象(this != getBaseContext()),通過this調用Context更簡捷。因此他們在某些場合會有些不同。。如下面的例子,使用this會引發錯誤:
Spinner spinner = (Spinner) findViewById(R.id.spinner);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?>arg0, View arg1, int arg2, long arg3){
Toast.makeText(getBaseContext(),"SELECTED", Toast.LENGTH_SHORT).show(); //這兒使用了getBaseContext()
}
當把getBaseContext()變成this就會有錯誤。爲什麼這種情況下需要使用getBaseContext()方法,而不能使用this呢?
上面說過了this和getBaseContext()指向的對象不同,在這裏this指的不是Activity,而是spinner這個類。因爲我們在onItemSelected(AdapterView<?>arg0, View arg1, int arg2, long arg3)方法中使用它。這個方法是來自Spinner類,而Spinner從AdapterView.OnItemSelectedListener接口中繼承這個方法。
getBaseContext()是 ContextWrapper中的方法。雖然這兒可以使用它,但是不建議。因爲Toast可以在應用的任何地方使用,並且它不關聯窗口,因此也可以使用Application的上下文。推薦使用XxxClass.this直接指出了使用的是誰的上下文,更簡捷。
Toast要求指出上下文,它是由誰產生的,this返回的就是誰的上下文。再比如點擊AlertDialog的按鈕彈出Toast,this返回的是AlertDialog的上下文。
AlertDialog aDialog = new AlertDialog.Builder(this)//這兒使用了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()
}
關於getApplicationContext()和getApplication()
這一對和上面一對類似,getApplication()只能被Activity和Service裏使用,在Activity類和Service類中定義一樣:
/** 返回屬於這個service(Activity中是activity)的application. */
public final Application getApplication() {
return mApplication;
}
可以看出getApplication()指向的是Application對象,因爲Application也是Context 的一個子類,所以getApplication()可以被用來指向Context。
ContextWrapper.getApplicationContext()最終調用的是ContextImpl.getApplicationContext():
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
他們雖然都可以代表Application的Context,但指向的可能並不是同一個對象(getApplication() != getApplicationContext()),通過getApplication()調用Context更簡捷。因此他們在某些場合可能也會有些不同。。比如如果想要獲取在應用清單文件中聲明的類,最好不要使用getApplicationContext(),並且最好使用強制轉換爲自己自定義的Application,因爲那樣可能會得不到Application對象。
關於Application的context和Activity的context
在Activity中,this 是其 對象,可以用來指向Activity的context,Activity銷燬Activity的context也被銷燬。Application的上下文,Application銷燬也銷燬。
有時候會有問何時用getApplicationContext()(本人更傾向於使用getApplication())和何時用this的問題,其實就是選擇使用Application的上下文還是Activity的上下文的問題。再說白點就是Application的用法。
通過Application可以來進行一些,如:數據傳遞、數據共享和數據緩存等操作,比如我們在某一個Activity中改變了一些全局變量的值,那麼在同一個應用的其他Activity中如果使用了這些變量值也會隨着改變,這樣就實現了數據傳遞和數據共享。這種涉及到全局性的操作時,要使用Application的context。可以通過繼承Application類來實現應用程序級的全局變量,這種全局變量方法相對靜態類更有保障,直到應用的所有Activity全部被destory掉之後纔會被釋放掉。
比如有一個全局的數據操作類用到了context,這個時候就要getApplication() 而不是用Activity的context,這就保證了數據庫的操作與activity無關(不會一直引用Activity的資源,防止內存泄漏),例如在Activity中有:
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
setContentView(label);
}
把activity context傳遞給view,意味着view擁有一個指向activity的引用,進而引用activity佔有的資源。當屏幕旋轉的時候,系統會銷燬當前的activity,保存狀態信息,再創建一個新的activity。如果activity信息比較輕還好,但如果裏面的信息太多,比如它需要加載一個很大的圖片,當每次旋轉屏幕的時候都銷燬這個圖片,再重新加載會花費不少時間,顯然這是我們不希望的。
實現這個要求的簡單想法就是定義一個靜態的Drawable,這樣Activity類創建銷燬它始終保存在內存中:
public class myactivity extends Activity {
private static Drawable sBackground;
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);//this在這裏
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
}
這段程序看起來很簡單,但是卻問題很大。當屏幕旋轉的時候會有leak(即gc沒法銷燬activity)。我們知道,屏幕旋轉的時候系統會銷燬當前的activity。但是當drawable和view關聯後,drawable保存了view的reference,即sBackground保存了label的引用,而label保存了activity的引用。既然drawable不能銷燬,它所引用和間接引用的都不能銷燬,這樣系統就沒有辦法銷燬當前的activity,於是造成了內存泄露。
在Java中內存泄漏是指,某個(某些)對象已經不再被使用時應該被gc所回收,但有一個對象持有這個對象的引用因而阻止gc回收這個對象。避免這種內存泄露的方法是避免activity中的任何屬於activity的對象的生命週期長過activity,避免由於對象對activity的非正常引用導致activity不能正常被銷燬。這時我們可以使用application的context。application context伴隨application的一生,而與activity的生命週期無關。application context可以通過Context.getApplication()或者Activity.getApplicationContext()方法獲取。但因爲我們使用的sBackground不是系統默認的全局變量,所以我們不能直接像this那樣傳入application 的context。另外我們還有可能在很多處地方使用這種全局性操作,所以最好自定義一個自己的Application:
首先創建一個MyApplication類,繼承自Application,並作相關實現:
public class MyApplication extends Application{
private static final Drawable sBackground;
public void onCreate(){
super.onCreate();
setBackground(sBackground);//初始化變量
}
public void setBackground(Drawable sBackground)
{
this.sBackground = sBackground;
}
public String getBackground()
{
return sBackground;
}
}
然後在Manifest.xml文件中配置自定義的Application
<application
android:name=".MyApplication">
</application>
這樣就已經實現了一種全局獲取Context的機制:
public class Myactivity extends Activity {
private MyApplication app;
private static Drawable sBackground;
protected void onCreate(Bundle state) {
super.onCreate(state);
app = (MyApplication) getApplication(); // 獲得MyApplication對象
TextView label = new TextView(this);//在這裏傳入上下文
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);//第一次加載背景資源
app.setBackground(sBackground);//緩存到Application中
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
}
注意:這樣當程序啓動的時候會初始化MyApplication類,而不再是默認的Application類。
經常導致內存泄漏的一些原因:
1. 一個靜態對象,生命週期長過了所在的Context的生命週期
2. 一個對象,作用域超出了所在Context的作用域
Dialog的使用要求指出Activity的上下文,如果使用Application的Context則無法彈出對話框。
Intent也要求指出上下文,如果想啓動一個新的Activity,就必須在Intent中使用Activity的上下文,這樣新啓動的Activity才能和當前Activity有關聯(在activity棧);也可以使用application的context,但是需要在Intent中添加 Intent.FLAG_ACTIVITY_NEW_TASK標誌,當作一個新任務。
Application類源碼
public class Application extends ContextWrapper implements ComponentCallbacks2 {
private ArrayList<ComponentCallbacks> mComponentCallbacks =
new ArrayList<ComponentCallbacks>();
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<ActivityLifecycleCallbacks>();
private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;
/** @hide */
public LoadedApk mLoadedApk;
//Activity生命週期回調接口
public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
void onActivityPaused(Activity activity);
void onActivityStopped(Activity activity);
void onActivitySaveInstanceState(Activity activity, Bundle outState);
void onActivityDestroyed(Activity activity);
}
/**
* 使用{@link Application#registerOnProvideAssistDataListener}
* 和{@link Application#unregisterOnProvideAssistDataListener}的回調接口 .
*/
public interface OnProvideAssistDataListener {
/**
* 當用戶請求一個 assist的時候調用,
* 和當前應用的所有的context一塊創建一個完整的{@link Intent#ACTION_ASSIST} Intent。
* 你可以覆蓋這個方法去通過bundle存放你喜歡的數據 {@link Intent#EXTRA_ASSIST_CONTEXT}
* .
*/
public void onProvideAssistData(Activity activity, Bundle data);
}
public Application() {
super(null);
}
/**當應用啓動時調用,
* 在所有activity, service,
* or receiver 對象 (包括content providers) 創建之前.
* 它的實現應該儘可能的不費時間 (比如使用懶加載狀態) 因爲這個方法花費的時間直接影響在進程中啓動第一個activity,service, or receiver .
* 如果重寫這個方法, 一定要調用 super.onCreate().
*/
@CallSuper
public void onCreate() {
}
/**
* 此方法是用於在模擬的過程環境中。
* 它不會在Android設備產品中被調用, where進程被簡單粗暴的killing。
* 沒有代碼(包括這個回調)被執行當這麼做的時候.
*/
@CallSuper
public void onTerminate() {
}
/**
* 配置改變時觸發這個方法。
*/
@CallSuper
public void onConfigurationChanged(Configuration newConfig) {
Object[] callbacks = collectComponentCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
((ComponentCallbacks)callbacks[i]).onConfigurationChanged(newConfig);
}
}
}
/**
* 當後臺程序已終止資源還匱乏時會調用這個方法。
* 好的利用程序一般會在這個方法裏面釋放一些沒必要要的資源來應付當後臺程序已終止
* 前臺利用程序內存還不夠時的情況。
*/
@CallSuper
public void onLowMemory() {
Object[] callbacks = collectComponentCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
((ComponentCallbacks)callbacks[i]).onLowMemory();
}
}
}
@CallSuper
public void onTrimMemory(int level) {
Object[] callbacks = collectComponentCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
Object c = callbacks[i];
if (c instanceof ComponentCallbacks2) {
((ComponentCallbacks2)c).onTrimMemory(level);
}
}
}
}
//註冊組件的回調
public void registerComponentCallbacks(ComponentCallbacks callback) {
synchronized (mComponentCallbacks) {
mComponentCallbacks.add(callback);
}
}
//取消註冊組件的回調
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
synchronized (mComponentCallbacks) {
mComponentCallbacks.remove(callback);
}
}
//註冊Activity生命週期的回調
public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
synchronized (mActivityLifecycleCallbacks) {
mActivityLifecycleCallbacks.add(callback);
}
}
//取消註冊Activity生命週期的回調
public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
synchronized (mActivityLifecycleCallbacks) {
mActivityLifecycleCallbacks.remove(callback);
}
}
public void registerOnProvideAssistDataListener(OnProvideAssistDataListener callback) {
synchronized (this) {
if (mAssistCallbacks == null) {
mAssistCallbacks = new ArrayList<OnProvideAssistDataListener>();
}
mAssistCallbacks.add(callback);
}
}
public void unregisterOnProvideAssistDataListener(OnProvideAssistDataListener callback) {
synchronized (this) {
if (mAssistCallbacks != null) {
mAssistCallbacks.remove(callback);
}
}
}
// ------------------ Internal API ------------------
/**
* @hide
*/
略
}
附,Java中的裝飾模式
裝飾模式(Decorator Pattern):動態地給一個對象增加一些額外的職責。
裝飾模式結構如圖所示:
在裝飾模式結構圖中包含如下幾個角色:
Component(抽象構件):它是具體構件和抽象裝飾類的共同父類,聲明瞭在具體構件中實現的業務方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之後的對象,實現客戶端的透明操作。
ConcreteComponent(具體構件):它是抽象構件類的子類,用於定義具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器可以給它增加額外的職責(方法)。
Decorator(抽象裝飾類):它也是抽象構件類的子類,抽象裝飾類不一定是抽象方法。用於給具體構件增加職責,但是具體職責在其子類中實現。它維護一個指向抽象構件對象的引用,通過該引用可以調用裝飾之前構件對象的方法,並通過其子類擴展該方法,以達到裝飾的目的。
ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類,負責向構件添加新的職責。每一個具體裝飾類都定義了一些新的行爲,它可以調用在抽象裝飾類中定義的方法,並可以增加新的方法用以擴充對象的行爲。
裝飾模式的核心在於抽象裝飾類的設計,其典型代碼如下所示:
class Decorator implements Component
{
private Component component; //維持一個對抽象構件對象的引用
public Decorator(Component component) //注入一個抽象構件類型的對象
{
this.component=component;
}
public void operation()
{
component.operation(); //調用原有業務方法
}
}
在抽象裝飾類 Decorator 中定義了一個 Component 類型的對象 component,維持一個對抽象構件對象的引用,並可以通過構造方法或 Setter 方法將一個 Component 類型的對象注入進來,同時由於 Decorator 類實現了抽象構件Component 接口,因此需要實現在其中聲明的業務方法 operation(),需要注意的是在 Decorator 中並未真正實現 operation() 方法,而只是調用原有 component 對象的 operation() 方法,它沒有真正實施裝飾,而是提供一個統一的接口,將具體裝飾過程交給子類完成。
在 Decorator 的子類即具體裝飾類中將繼承 operation() 方法並根據需要進行擴展,典型的具體裝飾類代碼如下:
class ConcreteDecorator extends Decorator
{
public ConcreteDecorator(Component component)
{
super(component);
}
public void operation()
{
super.operation(); //調用原有業務方法
addedBehavior(); //調用新增業務方法
}
//新增業務方法
public void addedBehavior()
{
……
}
}
在具體裝飾類中可以調用到抽象裝飾類的 operation() 方法,同時可以定義新的業務方法,如 addedBehavior()。
透明裝飾模式與半透明裝飾模式
由於在抽象裝飾類 Decorator 中注入的是 Component 類型的對象,因此我們可以將一個具體構件對象注入其中,再通過具體裝飾類來進行裝飾;此外,我們還可以將一個已經裝飾過的 Decorator 子類的對象再注入其中進行多次裝飾,從而對原有功能的多次擴展。
透明裝飾模式與半透明裝飾模式
裝飾模式雖好,但存在一個問題。如果客戶端希望單獨調用具體裝飾類新增的方法,而不想通過抽象構件中聲明的方法來調用新增方法時將遇到一些麻煩,我們通過一個實例來對這種情況加以說明:
在 Sunny 軟件公司開發的 Sunny OA 系統中,採購單(PurchaseRequest)和請假條(LeaveRequest)等文件(Document)對象都具有顯示功能,現在要爲其增加審批、刪除等功能,使用裝飾模式進行設計。
我們使用裝飾模式可以得到如圖所示結構圖:
文件對象功能增加實例結構圖
在圖中,Document充當抽象構件類,PurchaseRequest 和 LeaveRequest 充當具體構件類,Decorator 充當抽象裝飾類,Approver 和 Deleter 充當具體裝飾類。其中 Decorator 類和 Approver 類的示例代碼如下所示:
//抽象裝飾類
class Decorator implements Document
{
private Document document;
public Decorator(Document document)
{
this. document = document;
}
public void display()
{
document.display();
}
}
//具體裝飾類
class Approver extends Decorator
{
public Approver(Document document)
{
super(document);
System.out.println("增加審批功能!");
}
public void approve()
{
System.out.println("審批文件!");
}
}
大家注意,Approver 類繼承了抽象裝飾類 Decorator 的 display() 方法,同時新增了業務方法 approve(),但這兩個方法是獨立的,沒有任何調用關係。如果客戶端需要分別調用這兩個方法,代碼片段如下所示:
Document doc; //使用抽象構件類型定義
doc = new PurchaseRequest();
Approver newDoc; //使用具體裝飾類型定義
newDoc = new Approver(doc);
newDoc.display();//調用原有業務方法
newDoc.approve();//調用新增業務方法
如果newDoc也使用Document類型來定義,將導致客戶端無法調用新增業務方法approve(),因爲在抽象構件類Document中沒有對approve()方法的聲明。也就是說,在客戶端無法統一對待裝飾之前的具體構件對象和裝飾之後的構件對象。
在實際使用過程中,由於新增行爲可能需要單獨調用,因此這種形式的裝飾模式也經常出現,這種裝飾模式被稱爲半透明(Semi-transparent)裝飾模式,而標準的裝飾模式是透明(Transparent)裝飾模式。下面我們對這兩種裝飾模式進行較爲詳細的介紹:
透明裝飾模式
在透明裝飾模式中,要求客戶端完全針對抽象編程,裝飾模式的透明性要求客戶端程序不應該將對象聲明爲具體構件類型或具體裝飾類型,而應該全部聲明爲抽象構件類型。對於客戶端而言,具體構件對象和具體裝飾對象沒有任何區別。也就是應該使用如下代碼:
Component c, c1; //使用抽象構件類型定義對象
c = new ConcreteComponent();
c1 = new ConcreteDecorator (c);
而不應該使用如下代碼:
ConcreteComponent c; //使用具體構件類型定義對象
c = new ConcreteComponent();
或
ConcreteDecorator c1; //使用具體裝飾類型定義對象
c1 = new ConcreteDecorator(c);
透明裝飾模式可以讓客戶端透明地使用裝飾之前的對象和裝飾之後的對象,無須關心它們的區別,此外,還可以對一個已裝飾過的對象進行多次裝飾,得到更爲複雜、功能更爲強大的對象。在實現透明裝飾模式時,要求具體裝飾類的 operation() 方法覆蓋抽象裝飾類的 operation() 方法,除了調用原有對象的 operation() 外還需要調用新增的 addedBehavior() 方法來增加新行爲,
半透明裝飾模式
透明裝飾模式的設計難度較大,而且有時我們需要單獨調用新增的業務方法。爲了能夠調用到新增方法,我們不得不用具體裝飾類型來定義裝飾之後的對象,而具體構件類型還是可以使用抽象構件類型來定義,這種裝飾模式即爲半透明裝飾模式,也就是說,對於客戶端而言,具體構件類型無須關心,是透明的;但是具體裝飾類型必須指定,這是不透明的。如本節前面所提到的文件對象功能增加實例,爲了能夠調用到在 Approver 中新增方法 approve(),客戶端代碼片段如下所示:
……
Document doc; //使用抽象構件類型定義
doc = new PurchaseRequest();
Approver newDoc; //使用具體裝飾類型定義
newDoc = new Approver(doc);
……
半透明裝飾模式可以給系統帶來更多的靈活性,設計相對簡單,使用起來也非常方便;但是其最大的缺點在於不能實現對同一個對象的多次裝飾,而且客戶端需要有區別地對待裝飾之前的對象和裝飾之後的對象。在實現半透明的裝飾模式時,我們只需在具體裝飾類中增加一個獨立的 addedBehavior() 方法來封裝相應的業務處理,由於客戶端使用具體裝飾類型來定義裝飾後的對象,因此可以單獨調用 addedBehavior() 方法來擴展系統功能。
裝飾模式注意事項
在使用裝飾模式時,通常我們需要注意以下幾個問題:
(1) 儘量保持裝飾類的接口與被裝飾類的接口相同,這樣,對於客戶端而言,無論是裝飾之前的對象還是裝飾之後的對象都可以一致對待。這也就是說,在可能的情況下,我們應該儘量使用透明裝飾模式。
(2) 儘量保持具體構件類 ConcreteComponent 是一個“輕”類,也就是說不要把太多的行爲放在具體構件類中,我們可以通過裝飾類對其進行擴展。
(3) 如果只有一個具體構件類,那麼抽象裝飾類可以作爲該具體構件類的直接子類。如圖所示:
裝飾模式總結
裝飾模式降低了系統的耦合度,可以動態增加或刪除對象的職責,並使得需要裝飾的具體構件類和具體裝飾類可以獨立變化,以便增加新的具體構件類和具體裝飾類。在軟件開發中,裝飾模式應用較爲廣泛,例如在 JavaIO 中的輸入流和輸出流的設計、javax.swing 包中一些圖形界面構件功能的增強等地方都運用了裝飾模式。
裝飾模式的主要優點如下:
(1) 對於擴展一個對象的功能,裝飾模式比繼承更加靈活性,不會導致類的個數急劇增加。
(2) 可以通過一種動態的方式來擴展一個對象的功能,通過配置文件可以在運行時選擇不同的具體裝飾類,從而實現不同的行爲。
(3) 可以對一個對象進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行爲的組合,得到功能更爲強大的對象。
(4) 具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類,原有類庫代碼無須改變,符合“開閉原則”。
裝飾模式的主要缺點如下:
(1) 使用裝飾模式進行系統設計時將產生很多小對象,這些對象的區別在於它們之間相互連接的方式有所不同,而不是它們的類或者屬性值有所不同,大量小對象的產生勢必會佔用更多的系統資源,在一定程序上影響程序的性能。
(2) 裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味着比繼承更加易於出錯,排錯也很困難,對於多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較爲繁瑣。
在以下情況下可以考慮使用裝飾模式:
(1) 在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
(2) 當不能採用繼承的方式對系統進行擴展或者採用繼承不利於系統擴展和維護時可以使用裝飾模式。不能採用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴展,爲支持每一種擴展或者擴展之間的組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因爲類已定義爲不能被繼承(如 Java 語言中的 final 類)。