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

以上是Android源码对Context的描述:

  • 它是应用程序环境的全局信息的接口。
  • 这是一个抽象类,由Android系统提供。
  • 它允许访问特定于应用程序的资源和类,以及调用应用程序级操作,如启动活动,广播和接收意图等。

综上:Context的第一个作用是获取应用程序的资源和类,第二个作用是调用应用程序级操作,比如启动活动、广播和接收意图等。

补充:理解以上内容的朋友,下面的内容可以忽略。下面的内容都是对Context概念和作用有一个更好的理解所做的补充。

2. 如何理解Context是一个环境

Context是维持Android程序各组件能够正常工作的一个核心功能类,而这个核心功能类相当于一个大的环境,只有在这个环境下,Android的资源才能被获取以及Android的各项组件才能被调用。

3. 如何理解Context环境下各项组件才能被调用

Android虽然采用java语言开发,但是Android程序并非和java程序那样使用main()方法就可以运行。Android应用模型是基于组件的应用设计模式,组件的运行一定要有一个完整的Android环境。在这个环境下:Actvity、Service、BroadCast、ContentProvider才可以正常创建和运行。所以这些组件并不能按照java对象创建方式,new一下就能创建实例!而是需要一个环境,而这个环境就是我们所需要了解的Context。

通俗的方式来说:Android应用程序就像一部电影,Android的四大组件相当于电影的四大主角,而Context就相当于是摄像镜头,只有通过摄像镜头我们才能看到四大主角。主角是被内定好的,不能随便new出来。而其他跑龙套的并不是内定的,也就是说不是那么重要,他们可以被new出来,但是他们也需要通过摄像镜头才可以看到,所以才有了Button mButton=new Button(Context)。

4. Context作用的具体体现

有了Context这个环境,Android组件才可以正常被创建和调用,所以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);

5. Context的继承结构

image
  • 从继承结构可以看出:Context有两个子类:ContextWrapper和ContextImpl。
  • 从名称上可以看出:ContextWrapper是Context的封装类;ContextImpl是Context的实现类。
  • ContextWrapper有三个子类:Application、Service和ContextThemeWrapper。
  • ContextThemeWrapper是一个带主题的封装类,它的直接子类就是Activity。
  • Context环境一共有三种类型:Activity、Service和Application,它们承担不同的作用,而具体Context功能由ContextImpl类实现。

综上:

  • Activity、Service和Application这三种类型的Context,在多数情况下,都是可以通用的,而为了安全考虑,在某些情况下,是不可以通用的,诸如启动Activity、弹出Dialog等。一个Activity的启动必须建立在另一个Actvity的基础上;Dialog也必须在Activity上面弹出,这些情况下,也只能使用Activity的Context

  • 也就是说:凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,如下图所示:


    image

6. Context的数量

Context数量 = Activity数量 + Service数量 + 1(Application)

7. Application入口类作为工具类,这种做法是否可行?

强力不推荐!Application入口类的职责就是为了初始化!根据面向对象的单一职责原则,Application不推荐作为工具类使用。

8. getApplication()、getApplicationContext()和getBaseContext()的关系

我们在Activity中获取分别打印这个三个方法:

MyApplication myApp = (MyApplication) getApplication();  Context appContext = getApplicationContext();  Context baseContext = getBaseContext();

打印结果:

getApplication::::com.beidou.mvptest.MyApplication@53502facgetApplicationContext::::com.beidou.mvptest.MyApplication@53502facbaseContext::::android.app.ContextImpl@53505ce4

结论:

  • getApplication和getApplicationContext得到的是一个对象MyApplication。
  • getBaseContext得到的是ContextImpl。

疑问:

  • 第一问:getApplication和getApplicationContext得到的对象是一样的,那为何设计两个方法呢?
  • 答:两者范围不同,后者比前者适用范围更广。getApplication只适用于Activity和Service,而getApplicationContext还用于其他场景,比如BroadcastReceiver中。
  • 第二问:ContextImpl是什么东东?
  • 答:

①:ContextImpl是Context功能的实现类。Application和Service、Activity并不会去实现Context的功能,只是做了接口的封装,具体的功能由ContextImpl完成。

②:因为Application、Activity、Service都是直接或间接继承自ContextWrapper的,我们就直接看ContextWrapper的源码,就会发现所有ContextWrapper中方法的实现都非常统一,就是调用了mBase对象中对应当前方法名的方法。

③:那么这个mBase对象又是什么呢?我们来看第16行的attachBaseContext()方法,这个方法中传入了一个base参数,并把这个参数赋值给了mBase对象。而attachBaseContext()方法其实是由系统来调用的,它会把ContextImpl对象作为参数传递到attachBaseContext()方法当中,从而赋值给mBase对象,之后ContextWrapper中的所有方法其实都是通过这种委托的机制交由ContextImpl去具体实现的,所以说ContextImpl是上下文功能的实现类。

④:再看一下我们刚刚打印的getBaseContext()方法,在第26行。这个方法只有一行代码,就是返回了mBase对象而已,而mBase对象其实就是ContextImpl对象,因此刚才的打印结果也得到了印证。

/**  * 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 mBase;            /**      * Set the base context for this ContextWrapper.  All calls will then be      * delegated to the base context.  Throws      * IllegalStateException if a base context has already been set.      *       * @param base The new base context for this wrapper.      */      protected void attachBaseContext(Context base) {          if (mBase != null) {              throw new IllegalStateException("Base context already set");          }          mBase = base;      }        /**      * @return the base context as set by the constructor or setBaseContext      */      public Context getBaseContext() {          return mBase;      }        @Override      public AssetManager getAssets() {          return mBase.getAssets();      }        @Override      public Resources getResources() {          return mBase.getResources();      }        @Override      public ContentResolver getContentResolver() {          return mBase.getContentResolver();      }        @Override      public Looper getMainLooper() {          return mBase.getMainLooper();      }            @Override      public Context getApplicationContext() {          return mBase.getApplicationContext();      }        @Override      public String getPackageName() {          return mBase.getPackageName();      }        @Override      public void startActivity(Intent intent) {          mBase.startActivity(intent);      }            @Override      public void sendBroadcast(Intent intent) {          mBase.sendBroadcast(intent);      }        @Override      public Intent registerReceiver(          BroadcastReceiver receiver, IntentFilter filter) {          return mBase.registerReceiver(receiver, filter);      }        @Override      public void unregisterReceiver(BroadcastReceiver receiver) {          mBase.unregisterReceiver(receiver);      }        @Override      public ComponentName startService(Intent service) {          return mBase.startService(service);      }        @Override      public boolean stopService(Intent name) {          return mBase.stopService(name);      }        @Override      public boolean bindService(Intent service, ServiceConnection conn,              int flags) {          return mBase.bindService(service, conn, flags);      }        @Override      public void unbindService(ServiceConnection conn) {          mBase.unbindService(conn);      }        @Override      public Object getSystemService(String name) {          return mBase.getSystemService(name);      }        ......  }  

9. 使用Application出现的问题和解决办法

9.1 问题一:在Application的构造方法中去获取Context实现类的各种方法

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

运行结果:

空指针:java.lang.RuntimeException: Unable to instantiate application com.example.test.MyApplication: java.lang.NullPointerException

修改代码如下:

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

运行结果正常!发生了什么事情呢?回顾ContextWrapper源码,ContextWrapper中有一个attachBaseContext()方法,这个方法会将传入的一个Context参数赋值给mBase对象,之后mBase对象就有值了。而我们又知道,所有Context的方法都是调用这个mBase对象的同名方法,那么也就是说如果在mBase对象还没赋值的情况下就去调用Context中的任何一个方法时,就会出现空指针异常。Application中方法的执行顺序:


image

在onCreate()方法中去初始化全局的变量数据是一种比较推荐的做法,假如你想把初始化提前到极致,也可以重写attachBaseContext()方法:

public class MyApplication extends Application {            @Override      protected void attachBaseContext(Context base) {          // 在这里调用Context的方法会崩溃          super.attachBaseContext(base);          // 在这里调用Context的方法就没问题    }  }  

9.2 问题二:把Application当做工具类使用时获取实例采用new的方式

public class MyApplication extends Application {            private static MyApplication app;            public static MyApplication getInstance() {          if (app == null) {              app = new MyApplication();          }          return app;      }  }  

我们已经在上文指出了:new MyApplication实例的方式,得到的对象并不具备Context的能力,如果进行Context操作就会报空指针,因为它只是一个java对象。而我们知道Application本身就是一个单例了,所以我们直接返回本身即可,不用再去new对象获取实例,否则弄巧成拙。

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

10. Context乱用导致的内存泄漏的问题和解决办法

public class CustomManager  {      private static CustomManager sInstance;      private Context mContext;        private CustomManager(Context context)      {          this.mContext = context;      }        public static synchronized CustomManager getInstance(Context context)      {          if (sInstance == null)          {              sInstance = new CustomManager(context);          }          return sInstance;      }            //some methods       private void someOtherMethodNeedContext()      {                }  }  

假如:sInstance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity A去getInstance获得sInstance对象,传入this,常驻内存的CustomManager保存了你传入的ActivityA 对象(也就是Context),并一直持有,即使Activity(Context)被销毁掉,但因为它的引用还存在于一个CustomManager中,就不可能被GC掉,这样就导致了内存泄漏。也就是所以解决办法是:

public static synchronized CustomManager getInstance(Context context)      {          if (sInstance == null)          {              sInstance = new CustomManager(context.getApplicationContext());          }          return sInstance;      }  

11. 正确使用Context

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:

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

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

  • 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

12. 参考文章

  • https://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649547681&idx=1&sn=cf600ed5a92ce5fe100e8478ba0c55b4&scene=21#wechat_redirect

  • http://blog.csdn.net/lmj623565791/article/details/40481055

  • http://blog.csdn.net/guolin_blog/article/details/47028975

更多相关文章

  1. Android(安卓)对象序列化 Serializable实现与Parcelabel实现的区
  2. Android实现异步加载
  3. bindService的注意事项
  4. android 联系人搜索
  5. Android:UI更新方法二:View.postInvalidate+Thread+Runnable
  6. 运用BitmapFactory.Options来改善程序加载图片效率和避免内存溢
  7. Android(安卓)ViewPager动画切换
  8. Android(安卓)Uri.getQueryParameter使用注意
  9. 怎样实现android http-post方法

随机推荐

  1. 简单的使用腾讯X5内核浏览器替代Android
  2. 为什么Android没有iOS那么顺滑
  3. 从头学Android之ContentProvider
  4. Android设备FTP服务器搭建
  5. AsyncTask总结
  6. Android(安卓)异步消息处理机制的几种实
  7. Android与服务器端数据交互(http协议整合s
  8. Android开发者如何搭建服务器
  9. 人物角色要怎么画?动漫人物画法技巧
  10. 1年有效期的 jetbrains 全家桶激活码