ContextWrapper是上下文功能的封装类

每个App里面都有一个Application,但是我们为什么还要自定义一个Application类呢?我们知道当App启动的时候,系统会自动加载并初始化Application类。其实Google 并不希望我们去自定义Application类。基本上只有需要做一些全局初始化的时候可能才需要用到自定义Application。

Application

Android提供了一个Application类,每当应用程序启动时,系统会自动将这个类进行初始化。在项目中,我们在一些工具类采用了单例模式,其生命周期和整个应用程序相同,并且可能直接或者间接的需要Context引用来进行获取资源的操作。那么我们需要一个全局Context也就是Application。

Android基础之Context文章中我们知道,Application生命周期是整个App

Context相信所有的Android开发人员基本上每天都在接触,因为它太常见了。但是这并不代表Context没有什么东西好讲的,实际上Context有太多小的细节并不被大家所关注,那么今天我们就来学习一下那些你所不知道的细节。

美高梅棋牌游戏 1官方文档

自定义Application用途

  • 为得到一个Application对象提供便捷
  • 封装一些通用操作
  • 初始化一些全局的变量数据

对于前两点,Google官方是不建议这样做的。因为使用一个单例模式同样可以做到。但是自定义Application没有任何副作用。而在Application中的onCreate()方法里去初始化各种全局的变量数据是一种比较推荐的做法。

Context类型

译文:注意:通常不需要子类化应用程序。在大多数情况下,静态单例可以以一种更模块化的方式提供相同的功能。如果你需要一个全局上下文(例如注册广播接收器),包括Context . getApplicationContext()作为一个上下文参数调用您的单例的getInstance()方法。

自定义Application

我们知道,Android应用都是使用Java语言来编写的,那么大家可以思考一下,一个Android程序和一个Java程序,他们最大的区别在哪里?划分界限又是什么呢?其实简单点分析,Android程序不像Java程序一样,随便创建一个类,写个main()方法就能跑了,而是要有一个完整的Android工程环境,在这个环境下,我们有像Activity、Service、BroadcastReceiver等系统组件,而这些组件并不是像一个普通的Java对象new一下就能创建实例的了,而是要有它们各自的上下文环境,也就是我们这里讨论的Context。可以这样讲,Context是维持Android程序中各组件能够正常工作的一个核心功能类。

市场上的App基本上都会创建自定义App,创建的自定义的Application类和一些单例的工具类差不多。可是使用归使用,有不少项目对自定义Application的用法并不到位,正如官方文档中所表述的一样,多数项目只是把自定义Application当成了一个通用工具类,而这个功能并不需要借助Application来实现,使用单例可能是一种更加标准的方式。不过使用自定义的Application类并没有什么副作用,它和单例模式一样都能实现全局功能。

新建一个Application类

新建一个MyApplication并让它继承自Application

public class MyApplication extends Application{
    private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
    }

    public static Context getInstance() {
        return mContext;
    }
}

下面我们来看一下Context的继承结构:

  • 得到一个Application对象
  • 封装一些全局性的操作
  • 初始化全局性的数据

在AndroidManifest文件中指定自定义的Application

 <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" />

指定完成后,当我们的程序启动时Android系统就会创建一个MyApplication的实例,如果这里不指定的话就会默认创建一个Application的实例。

美高梅棋牌游戏 2

  1. 自定义Application 方法很简单,首先创建一个类继承Application类就可以了。

使用自定义Application注意点

Context的继承结构还是稍微有点复杂的,可以看到,直系子类有两个,一个是ContextWrapper,一个是ContextImpl。那么从名字上就可以看出,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity。

初始化数据的时机

我们不能够在自定义的Application类的构造方法里初始化一些需要Context引用操作得到的数据,例如getResources()、getPackageName()、getSystemService()等等。一旦这样做,应用程序已启动就会报控指针的错误。我们应该在onCreate()方法中初始化。

ContextWrapper中有一个attachBaseContext()方法,这个方法会将传入的一个Context参数赋值给mBase对象,之后mBase对象就有值了。而我们又知道,所有Context的方法都是调用这个mBase对象的同名方法,那么也就是说如果在mBase对象还没赋值的情况下就去调用Context中的任何一个方法时,就会出现空指针异常,(引用自Android Context完全解析,你所不知道的Context的各种细节)

Application方法执行顺序:

盗用大牛郭霖的图片.png

那么在这里我们至少看到了几个所比较熟悉的面孔,Activity、Service、还有Application。由此,其实我们就已经可以得出结论了,Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的。

public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); } }

自定义Application采用单例模式

Application全局只有一个,它本身就已经是单例了,无需再用单例模式去为它做多重实例保护了。
错误示范:

public class MyApplication extends Application {  

    private static MyApplication app;  

    public static MyApplication getInstance() {  
        if (app == null) {  
            app = new MyApplication();  
        }  
        return app;  
    }  

}  

上述代码的getInstance()方法中,如果app == null,采用new来创建一个新的Application,并将其返回。返回的Application对象不具备Context的能力,只是一个普通的Application实例。这和第一个错误类似,Application对象应该由Android系统来创建。

正确的操作:

public class MyApplication extends Application {  

    private static MyApplication app;  

    public static MyApplication getInstance() {  
        return app;  
    }  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        app = this;  
    }  

}  

那么Context到底可以实现哪些功能呢?这个就实在是太多了,弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等等等都需要用到Context。由于Context的具体能力是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

  1. 然后在AndroidManifest.xml文件中的application节点上,引用就可以了

参考

Android Context完全解析,你所不知道的Context的各种细节

Context数量

那么一个应用程序中到底有多少个Context呢?其实根据上面的Context类型我们就已经可以得出答案了。Context一共有Application、Activity和Service三种类型,因此一个应用程序中Context数量的计算公式就可以这样写:

 <application android:name=".MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">

[plain]view plaincopy

指定完成后,当我们的程序启动时Android系统就会创建一个MyApplication的实例,如果这里不指定的话就会默认创建一个Application的实例。剩下的就是自己创建你需要的方法和实例了。

Context数量 = Activity数量 + Service数量 + 1

在查看官方文档的时候,我们可以知道Application属于Context类中的一种。那么我们想要获得Context中的各种方法是不是也可以通过getApplication来获得呢?

上面的1代表着Application的数量,因为一个应用程序中可以有多个Activity和多个Service,但是只能有一个Application。

美高梅棋牌游戏 3image.png

Application Context的设计

修改代码,获取包名,运行代码:

基本上每一个应用程序都会有一个自己的Application,并让它继承自系统的Application类,然后在自己的Application类中去封装一些通用的操作。其实这并不是Google所推荐的一种做法,因为这样我们只是把Application当成了一个通用工具类来使用的,而实际上使用一个简单的单例类也可以实现同样的功能。但是根据我的观察,有太多的项目都是这样使用Application的。当然这种做法也并没有什么副作用,只是说明还是有不少人对于Application理解的还有些欠缺。那么这里我们先来对Application的设计进行分析,讲一些大家所不知道的细节,然后再看一下平时使用Application的问题。

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

首先新建一个MyApplication并让它继承自Application,然后在AndroidManifest.xml文件中对MyApplication进行指定,如下所示:

getPackageName()这个方法是Context提供的,结果发现是空指针的操作。

[美高梅棋牌官网,html]view plaincopy

美高梅棋牌游戏 4运行结果

android:name=".MyApplication"

这是为什么呢?很简单的一段代码怎么会报空呢?我们重新回顾一下ContextWrapper类的源码,ContextWrapper中有一个attachBaseContext()方法,这个方法会将传入的一个Context参数赋值给mBase对象,之后mBase对象就有值了。而我们又知道,所有Context的方法都是调用这个mBase对象的同名方法,那么也就是说如果在mBase对象还没赋值的情况下就去调用Context中的任何一个方法时,就会出现空指针异常,上面的代码就是这种情况。Application方法的执行顺序是从构造方法到attachBaseContext()方法再到onCreate()方法。所以在初始化操作的时候Application中方法的执行顺序如下图所示:

android:allowBackup="true"

美高梅棋牌游戏 5Application方法的执行顺序

android:icon="@drawable/ic_launcher"

Application中在onCreate()方法里去初始化各种全局的变量数据是一种比较推荐的做法,但是如果你想把初始化的时间点提前到极致,也可以去重写attachBaseContext()方法。修改代码:

android:label="@string/app_name"

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

android:theme="@style/AppTheme">

或者

......

public class MyApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext; String packageName = getPackageName(); Log.d("TAG", "package name is " + packageName); }}

指定完成后,当我们的程序启动时Android系统就会创建一个MyApplication的实例,如果这里不指定的话就会默认创建一个Application的实例。

美高梅棋牌游戏 6运行结果

前面提到过,现在很多的Application都是被当作通用工具类来使用的,那么既然作为一个通用工具类,我们要怎样才能获取到它的实例呢?如下所示:

我们在创建自定义Application的时候,最常用的方法是去获得Application实例,列举一段你很熟悉的代码出来。

[java]view plain美高梅棋牌游戏,copy

public class MyApplication extends Application { public static MyApplication app; public static MyApplication getInstance(){ if(app==null){ app= new MyApplication(); } return app; } @Override public void onCreate() { super.onCreate(); }}

publicclassMainActivityextendsActivity {

这个获取Application单例方法getInstance(),用于获取MyApplication的实例,有了这个实例之后我们就能获取MyApplication这个类里面的方法了。但是因为Application类属于系统组件,系统组件的实例由系统去创建。所以这里new 一个 MyApplication类不具备任何获取Context的实例,它只是一个普通的Java对象。因为Application类全局只有一个,它本身就是一个单例,所以不需要再给它加上单例的外衣去保护它。

@Override

修改代码:

protectedvoidonCreate(Bundle savedInstanceState) {

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

super.onCreate(savedInstanceState);

getInstance()方法可以照常提供,但是里面不要做任何逻辑判断,直接返回app对象就可以了,而app对象又是什么呢?在onCreate()方法中我们将app对象赋值成this,this就是当前Application的实例,那么app也就是当前Application的实例了。

setContentView(R.layout.activity_main);

参考文章:Android Context完全解析,你所不知道的Context的各种细节

MyApplication myApp = (MyApplication) getApplication();

Log.d("TAG","getApplication is "+ myApp);

}

}

可以看到,代码很简单,只需要调用getApplication()方法就能拿到我们自定义的Application的实例了,打印结果如下所示:

美高梅棋牌游戏 7

那么除了getApplication()方法,其实还有一个getApplicationContext()方法,这两个方法看上去好像有点关联,那么它们的区别是什么呢?我们将代码修改一下:

[java]view plaincopy

publicclassMainActivityextendsActivity {

@Override

protectedvoidonCreate(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()的结果打印了出来,现在重新运行代码,结果如下图所示:

美高梅棋牌游戏 8

咦?好像打印出的结果是一样的呀,连后面的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,因为前面已经说过了,Application本身就是一个Context,所以这里获取getApplicationContext()得到的结果就是MyApplication本身的实例。

那么有的朋友可能就会问了,既然这两个方法得到的结果都是相同的,那么Android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了,如下所示:

[java]view plaincopy

publicclassMyReceiverextendsBroadcastReceiver {

@Override

publicvoidonReceive(Context context, Intent intent) {

MyApplication myApp = (MyApplication) context.getApplicationContext();

Log.d("TAG","myApp is "+ myApp);

}

}

也就是说,getApplicationContext()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到我们的Application对象。

那么更加细心的朋友会发现,除了这两个方法之外,其实还有一个getBaseContext()方法,这个baseContext又是什么东西呢?我们还是通过打印的方式来验证一下:

美高梅棋牌游戏 9

哦?这次得到的是不同的对象了,getBaseContext()方法得到的是一个ContextImpl对象。这个ContextImpl是不是感觉有点似曾相识?回去看一下Context的继承结构图吧,ContextImpl正是上下文功能的实现类。也就是说像Application、Activity这样的类其实并不会去具体实现Context的功能,而仅仅是做了一层接口封装而已,Context的具体功能都是由ContextImpl类去完成的。那么这样的设计到底是怎么实现的呢?我们还是来看一下源码吧。因为Application、Activity、Service都是直接或间接继承自ContextWrapper的,我们就直接看ContextWrapper的源码,如下所示:

[java]view plaincopy

/**

* 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.

*/

publicclassContextWrapperextendsContext {

Context mBase;

/**

* Set the base context for this ContextWrapper.  All calls will then be

* delegated to the base context.  Throws

本文由美高梅游戏网站登录发布于美高梅棋牌游戏,转载请注明出处:ContextWrapper是上下文功能的封装类

您可能还会对下面的文章感兴趣: