Android context类族及其装饰着模式 学习

来源:互联网 发布:渡劫 知乎 编辑:程序博客网 时间:2024/05/08 02:45

关于Context的学习,紧跟着就是关于Activity的启动。在下一篇学习中,学习关于Activity的启动。

本次学习两部分,第一部分是Android的上下文对象,第二部分是源码context族中用到的设计模式,装饰者模式。

一就是Context族类的结构图,然后根据Context结构图分析一下其装饰着模式的对应关系。

在看一下Activity,Service,Application中都继承Context类族的关系,还有Context里用到的ShareperedPreference,getResource,getColor等公共方法关系。

在一个应用中Context个数问题

Context的作用,能干什么

Context的作用域

有一点关于今后学习知识流程:首先要看这个知识体系的说明文档。其次要画出流程图、结构图。第三对每一个需要学的知识点细扣。第四对各个知识点之间有什么联系。


需要参考的是,这个知识点讲的是什么?有什么优点?怎么去使用?

这个知识点是如何实现的?为什么会有这样的优点?可不可把这样的优点实现方式应用到其他场景中?

还有没有其他的实现方式,或者更好的实现方式。

但是学习过程中,一再要强调注意学习的主线。


====================================

今天学习下Context相关内容,三部分:

一、Context类相关继承关系、源码实现

二、以及在所使用到的设计模式

三、在开发中Context 使用的相关内容

一、Context类基本信息、Context类族关系、源码实现

①Context类基本信息

主要包括了Context类、ContextImpl类、ContextWrapper类 、ContextThemeImpl类

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 {    ......}
文档注释可以概括为三点:

1、它描述的是一个应用程序环境的信息,即上下文。

2、该类是一个抽象(abstractclass)类,Android提供了该抽象类的具体实现类(后面我们会讲到是ContextIml类)。

3、通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent

信息 等。

我个人理解:Context是贯穿于Activity,Service组件、以及Application当中,因为他们三个都是继承自Context类。所以叫做上下文对象。


ContextImpl类:

/** * Common implementation of Context API, which provides the base * context object for Activity and other application components. */class ContextImpl extends Context {    private Context mOuterContext;    ......}
文档注释:对Context的API进行的实现,对Activity以及其他组件  提供了一个基础的context对象。

更加进一步说明了context是一个上下文对象。


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,简单代表所有关于调用它的其他Context.可以被子类修改方法而不去改变原始的Context。

说明其所拥有的设计模式。装饰着模式,虽然还不清晰。用的组合而非继承。ContextImpl与ContextWrapper之间的组合。



通过查看源码,可以确定Context类族关系以及Activity、Service、Application的关系如图所示。

②通过源码来看Context的ComtextImpl实现类与ContextWrapper类的组合关系。

以下代码学习自:工匠若水(http://blog.csdn.net/yanbober/article/details/45967639)

通过startActivity启动一个新的Activity时系统会回调ActivityThread的handleLaunchActivity()方法

该方法内部会调用performLaunchActivity()方法去创建一个Activity实例,然后回调Activity的onCreate()等方法。

所以Activity的ContextImpl实例化是在ActivityThread类的performLaunchActivity方法中,如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {        ......            //已经创建好新的activity实例            if (activity != null) {                //创建一个Context对象                Context appContext = createBaseContextForActivity(r, activity);//代码①                ......                //将上面创建的appContext传入到activity的attach方法                activity.attach(appContext, this, getInstrumentation(), r.token,//代码②                        r.ident, app, r.intent, r.activityInfo, title, r.parent,                        r.embeddedID, r.lastNonConfigurationInstances, config,                        r.referrer, r.voiceInteractor);                ......            }        ......        return activity;    }

先看代码① createBaseContextForActivity传递进去的activity,就是让当前的context持有activity。

看下createBaseContextForActivity方法代码:

private Context createBaseContextForActivity(ActivityClientRecord r,            final Activity activity) {        //实质就是new一个ContextImpl对象,调运ContextImpl的有参构造初始化一些参数            ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);        //特别特别留意这里!!!        //ContextImpl中有一个Context的成员叫mOuterContext,通过这条语句就可将当前新Activity对象赋值到创建的ContextImpl的成员mOuterContext(也就是让ContextImpl内部持有Activity)。        appContext.setOuterContext(activity);        //创建返回值并且赋值        Context baseContext = appContext;        ......        //返回ContextImpl对象        return baseContext;    }

在看代码②attach();把context传递到该方法中,调用了Activity里attach方法。

final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {        //特别特别留意这里!!!        //与上面createBaseContextForActivity方法中setOuterContext语句类似,不同的在于:        //通过ContextThemeWrapper类的attachBaseContext方法,将createBaseContextForActivity中实例化的ContextImpl对象传入到ContextWrapper类的mBase变量,这样ContextWrapper(Context子类)类的成员mBase就被实例化为Context的实现类ContextImpl        attachBaseContext(context);//③        ......    }
在Activity的attach里方法③,又调用了Activity父类的父类ContextWrapper类的attachBaseContext方法,把ContextImpl对象实例化的context传递进去。

这就是一个组合关系的使用,

然后,Activity通过ContextWrapper的成员mBase来引用了一个ContextImpl对象,

这样,Activity组件以后就可以通过这个ContextImpl对象来执行一些具体的操作(启动Service等);

同时ContextImpl类又通过自己的成员mOuterContext引用了与它关联的Activity,这样ContextImpl类也可以操作Activity。

其他关于Service,Application的Context源码执行流程大体类似,详细可以学习工匠若水

二、Context中所用到的设计模式:装饰者模式

首先介绍下装饰者模式:

Decorator模式(别名Wrapper):动态将职责附加到对象上,若要扩展功能,装饰者提供了比继承更具弹性的代替方案。

类的设计原则:

① 多用组合,少用继承。

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。

然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。

②对扩展开放,对修改关闭。


其次在装饰模式结构图中包含如下几个角色:

Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,

它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。

Concrete Component(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,

实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。

Decorator(抽象装饰类):它也是抽象构件类的子类,抽象装饰类不一定是抽象方法。

用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,

通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法以达到装饰的目的。

Concrete Decorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,

它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。


看下UML图:


看上去与Context类族中的ContextImpl与ContextWrapper,以及Service,Application一样结构。

详细装饰者模式 参见:李可乐(http://blog.csdn.net/card361401376/article/details/51222351)、PleaseCallMeCoder(http://blog.csdn.net/sdkfjksf/article/details/52624483)


三、开发中Context的相关使用

①首先根据Context的结构图可知,每一个Activity、Service都是继承自Context。

所以在一个App中,Context的数量总和 = Activity数量 + Service数量 + 1;//1  就是 Application

②在App中各个Context访问的资源唯一性。

看源码:

class ContextImpl extends Context {    ......    private final ResourcesManager mResourcesManager;    private final Resources mResources;    ......    @Override    public Resources getResources() {        return mResources;    }    ......}


Resources对象就是上面ContextImpl的成员变量mResources。那我们追踪可以发现mResources的赋值操作如下:

private ContextImpl(ContextImpl container, ActivityThread mainThread,            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,            Display display, Configuration overrideConfiguration) {        ......        //单例模式获取ResourcesManager对象        mResourcesManager = ResourcesManager.getInstance();        ......        //packageInfo对于一个APP来说只有一个,所以resources 是同一份        Resources resources = packageInfo.getResources(mainThread);        if (resources != null) {            if (activityToken != null                    || displayId != Display.DEFAULT_DISPLAY                    || overrideConfiguration != null                    || (compatInfo != null && compatInfo.applicationScale                            != resources.getCompatibilityInfo().applicationScale)) {                //mResourcesManager是单例,所以resources是同一份                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),                        packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,                        overrideConfiguration, compatInfo, activityToken);            }        }        //把resources赋值给mResources        mResources = resources;        ......    }
可以看到是获取资源是一个单例模式。所以不同的Context得到的Resource是同一套资源。

③getApplication与getApplicationContext区别:

二者的返回对象不同。第一个方法写在Activity类、Service类中返回类型是一个Application,

    第二个方法写在了ContextImpl中,返回类型是一个Context,不管是从LoaderApk中,还是从ActivityThread中(UI thread)

其实也是返回一个Application对象。

二者的作用域不同。如果在广播,数据库的操作中,就需要使用getApplicationContext。其作用域更广。因为如果要借助Application实例,

只能从ContextImpl类中获取,所以说其作用域更大。


    ④对于context对于在AlertDialog,startActivity,LayoutInflation(引入布局)中,有所区别:   

学习参考自简书—尹star:(http://www.jianshu.com/p/94e0f9ab3f1d)

启动Activity,还有弹出Dialog。

出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,

一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。

而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),

因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

上图:


图中Application和Service所不推荐的两种使用情况。

1:如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错

android.util.AndroidRuntimeException: Calling startActivity from outside ofan Activitycontext requiresthe FLAG_ACTIVITY_NEW_TASKflag. 

Is this reallywhat youwant?这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。

解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,

而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service同Application。

2:在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。

所以这种方式也不推荐使用。

一句话总结:凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,

当然了,注意Context引用的持有,防止内存泄漏。


⑤关于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;    }}

这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,

假如Activity A去getInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象

,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏


View持有的内存引用

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掉,所以造成内存泄漏。


数据库持有Activity的Context引用


Context的正确姿势:

1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。

2:不要让生命周期长于Activity的对象持有到Activity的引用。

3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,

如果使用静态内部类,将外部实例引用作为弱引用持有。



















1 0
原创粉丝点击