Context是一个抽象类,源码位于android.content包中。描述的是一个应用程序环境的信息,即上下文。通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent信息 ,得到各种服务(getSystemService)等。在下面再详细说说。
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也是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也是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继承自ContextWrapper,是维持全局应用状态的基类,位于android.app包中。它还实现了ComponentCallbacks2,实现此接口的有Activity,Service,Content Provider,Fragment,Application及其子类,这个接口作用是细化内存管理,其对所有应用组件都非常有用。其构造方法为:
public Application() {
super(null);
}
在无参的构造方法中调用了父类的构造方法,向父类构造方法传的值为null。
其有两个子类MultiDexApplication,是multidex 需要用到的 ;MockApplication,和MockContext作用类似。
Android:backupAgent用来设置备份代理。对于大部分应用程序来说,都或多或少保存着一些持久性的数据,比如数据库和共享文件,或者有自己的配置信息。为了保证这些数据和配置信息的安全性以及完整性,Android提供了这样一个机制。
详细参考backupAgent的用法
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继承自ContextWrapper, 位于包android.content中,是ContextWrapper的特别版,在其初始化设置后修改他的基础上下文。很少用到,其只有一个公共方法setBaseContext(),关于什么是BaseContext在下面会有解释。
IsolatedContext和RenamingDelegatingContext在Api24被弃用,这里就不介绍了。Service和Application的类继承关系比较像,而Activity还多了一层继承ContextThemeWrapper,这是因为Activity有主题的概念,而Service是没有界面的服务。然后下面就着重说说Context和Application。
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类中常用的方法有:
// 获取应用程序包的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)直接调用就可以了。
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运行所在地的上下文
}
}
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()
}
这一对和上面一对类似,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对象。
在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标志,当作一个新任务。
public class Application extends ContextWrapper implements ComponentCallbacks2 {
private ArrayList mComponentCallbacks =
new ArrayList();
private ArrayList mActivityLifecycleCallbacks =
new ArrayList();
private ArrayList 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/**
* 当后台程序已终止资源还匮乏时会调用这个方法。
* 好的利用程序一般会在这个方法里面释放一些没必要要的资源来应付当后台程序已终止
* 前台利用程序内存还不够时的情况。
*/
@CallSuper
public void onLowMemory() {
Object[] callbacks = collectComponentCallbacks();
if (callbacks != null) {
for (int i=0; i@CallSuper
public void onTrimMemory(int level) {
Object[] callbacks = collectComponentCallbacks();
if (callbacks != null) {
for (int i=0; iif (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();
}
mAssistCallbacks.add(callback);
}
}
public void unregisterOnProvideAssistDataListener(OnProvideAssistDataListener callback) {
synchronized (this) {
if (mAssistCallbacks != null) {
mAssistCallbacks.remove(callback);
}
}
}
// ------------------ Internal API ------------------
/**
* @hide
*/
略
}
装饰模式(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 类)。
装饰模式 - 设计模式之结构型模式