首页 > 其他分享 >Android开发 - Context解析

Android开发 - Context解析

时间:2024-07-20 12:09:00浏览次数:12  
标签:Service ContextWrapper Application 对象 Context Activity Android 解析

Context是什么

  • Context的中文翻译为:语境; 上下文; 背景; 环境,在开发中我们经常说称之为“上下文”,那么这个“上下文”到底是指什么意思呢?在语文中,我们可以理解为语境,在程序中,我们可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程。比如微信聊天,此时的“环境”是指聊天的界面以及相关的数据请求与传输,Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与

  • Context到底是什么呢?一个Activity就是一个Context,一个Service也是一个ContextAndroid程序员把“场景”抽象为Context类,他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。一个应用程序可以认为是一个工作环境,用户在这个环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客人,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可以称之为一个应用程序

  • 源码中的注释是这么来解释Context的:Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类Android提供了该抽象类的具体实现类通过它我们可以获取应用程序的资源和类包括应用级别操作如启动Activity发广播接受Intent等)。既然上面Context是一个抽象类,那么肯定有他的实现类咯,我们在Context的源码中通过IDE可以查看到他的子类最终可以得到如下关系图:

image-20240720094349605

  • Context类本身是一个纯abstract类,它有两个具体的实现子类:ContextImplContextWrapper。其中ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。ContextThemeWrapper类,如其名所言,其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:themeApplication元素或者Activity元素指定的主题。当然,只有Activity才需要主题Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承ContextWrapperApplication同理而ContextImpl类则真正实现了Context中的所有函数应用程序中所调用的各种Context类的方法其实现均来自于该类一句话总结Context的两个子类分工明确其中ContextImpl是Context的具体实现类ContextWrapper是Context的包装类ActivityApplicationService虽都继承自ContextWrapperActivity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象由ContextImpl实现Context中的方法

Context能干什么

  • Context到底可以实现哪些功能呢?这个就实在是太多了:弹出Toast启动Activity启动Service发送广播操作数据库等等都需要用到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); 
    

Context作用域

  • 虽然Context神通广大,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,ActivityServiceApplication这三种类型的Context都是可以通用的。不过有几种场景比较特殊比如启动Activity还有弹出Dialog出于安全原因的考虑Android是不允许Activity或Dialog凭空出现的一个Activity的启动必须要建立在另一个Activity的基础之上也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错

  • 其中Activity所持有的Context的作用域最广,无所不能。因为Activity继承自ContextThemeWrapper,而ApplicationService继承自ContextWrapper,很显然ContextThemeWrapperContextWrapper的基础上又做了一些操作使得Activity变得更强大。以下说明Application和Service所不推荐的两种使用情况

    1. 如果我们用ApplicationContext去启动一个LaunchModestandardActivity的时候会报错android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用ServiceApplication

    2. ApplicationService中去layout inflate也是合法的,但是会使用系统默认的主题样式如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用

    • 一句话总结:凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,ServiceActivityApplication等实例都可以,当然了,注意Context引用的持有防止内存泄漏

如何获取Context

获取Context对象

  • 主要提供以下2种方法

    1. getView().getContext():返回当前View对象的Context对象,通常是当前正在展示的Activity对象

    2. getActivity().getApplicationContext():获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context

getApplication()getApplicationContext()的区别

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

    public classMyReceiver extends BroadcastReceiver{  
        @Override  
        public void onReceive(Context context,Intent intent){  
            //第一种方法
            Application myApp = (Application)context.getApplicationContext();	
            ---
            //第二种方法
            context = getActivity().getApplicationContext();
            Application myApp = (Application)context; 
        }  
    } 
    

Context引起的内存泄露

  • 但Context并不能随便乱用,用的不好有可能会引起内存泄露的问题,下面就示例两种错误的引用方式

    1. 错误的单例模式

      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 AgetInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC(咔擦)掉,这样就导致了内存泄漏
    2. 错误的View持有Activity引用

      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传入的thisMainActivitymContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC(咔擦)掉,所以造成内存泄漏

正确使用Context

  • 一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而ApplicationContext对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
    1. ApplicationContext能搞定的情况下,并且生命周期长的对象,优先使用ApplicationContext
    2. 不要让生命周期长于Activity的对象持有到Activity的引用
    3. 尽量不要在Activity中使用非静态内部类因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,外部实例将引用作为弱引用持有

总结

  • Context在Android系统中的地位很重要,它几乎无所不能,但它也不是你想用就能随便用的,谨防使用不当引起的内存问题

标签:Service,ContextWrapper,Application,对象,Context,Activity,Android,解析
From: https://www.cnblogs.com/ajunjava/p/18312938

相关文章

  • Python中的`@property`装饰器:深入解析与实战应用
    Python中的@property装饰器:深入解析与实战应用在Python中,@property装饰器是一种强大的工具,它允许类的方法被当作属性来访问。这一特性极大地增强了类的封装性和易用性,使得类的外部使用者可以像访问普通属性一样访问由方法计算或处理过的数据,而无需直接调用这些方法。本文将......
  • android audio不同音频流,(三)各音频流默认音量加载过程
    各音频流默认值,定义文件路径:frameworks/base/media/java/android/media/AudioSystem.java默认音量定义数组: /**@hide*/ publicstaticint[]DEFAULT_STREAM_VOLUME=newint[]{     4, //STREAM_VOICE_CALL     7, //STREAM_SYSTEM ......
  • 掌握Python中的文件序列化:Json和Pickle模块解析
    Python文件操作与管理:Open函数、Json与Pickle、Os模块在Python中,文件是一个重要的数据处理对象。无论是读取数据、保存数据还是进行数据处理,文件操作都是Python编程中不可或缺的一部分。本文将详细介绍Python中文件操作的几种常用方法,包括open函数的使用、数据序列化与反......
  • 简化Android数据管理:深入探索SQLite数据库
    SQLite数据库在Android中的使用SQLite是一种精巧的、轻量级的、无服务器的、零配置的、事务性SQL数据库引擎。相较于其他数据库系统,SQLite更适用于需要轻量级解决方案的移动应用场景。本文将详细介绍SQLite数据库在Android中的使用,包括数据库的创建、表的建立、数据的增删......
  • Android开发 - inflate方法与创建视图解析
    简介在Android开发过程中,很多地方都不可避免的使用到inflate方法,如在给Fragment进行CreateView(创建视图)时,我们通常是inflater.inflate(R.layout.xxx,container,false)来调用inflate方法的,不难发现,inflate方法的作用是将一个xml布局文件变成一个view对象。注意事项......
  • 【Rust光年纪】解锁Rust语言核心库奥秘:加密、数字签名和数据库操作全面解析
    从加密到数据库:探索Rust语言丰富的工具库生态系统前言在Rust语言开发中,使用合适的库可以极大地提高代码的安全性和效率。本文将介绍一些用于加密、数字签名、数据库连接等功能的Rust语言库,帮助读者快速了解其核心功能、使用场景以及安装配置等方面的信息。欢迎订阅专栏:R......
  • 全网最详细保姆式讲解-汽车电子测试和认证标准科普解析(二)
    这些是汽车电子设备的常见电磁兼容性(EMC)和环境测试标准。以下是每个标准的简要说明:RE,CE(CISPR25,GB18655)RE(RadiatedEmission,辐射发射):测量设备辐射的电磁能量,防止其干扰其他电子设备。CE(ConductedEmission,传导发射):测量设备通过导线传导的电磁能量,防止其干扰......
  • Android笔试面试题AI答之Activity(2)
    答案仅供参考,大部分为文心一言AI作答目录1.请介绍一下Activity生命周期?1.完全生命周期2.可见生命周期3.前台生命周期4.配置更改5.特殊场景2.请介绍一下横竖屏切换时Activity的生命周期变化?1.默认行为(未设置`android:configChanges`)2.设置`android:configChang......
  • Java关于注解的使用、如何自定义注解、如何通过元注解解析注解
    注解的介绍Java中总共有五大引用数据类型:类、数组、接口、枚举、注解。其中注解是在jdk1.5版本中加进来的特性,和类,接口,枚举是同一个层次的。注解应用:说明:一般用来对代码进行说明,方便生成doc文档(API文档)检查:检查代码是否符合条件@Override(检查重写方法)@FunctionalInter......
  • Android 14 适配之 - 隐式/显示 Intent 和 广播适配
    隐式Intent对隐式Intent限制:对Android14(API级别34)或更高版本为目标平台的应用,Android会限制应用向内部应用组件发送隐式intent:1.即隐式intent只能发送给导出的组件。在应用必须使用显式intent来发送组件,且被发送的组件是未被导出的属性配置。2.如果被发出的......