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

Android开发 - Context解析

时间:2024-07-23 09:09:18浏览次数: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/18317509

相关文章

  • Android开发 - Bundle传值的理解与使用
    什么是BundleBundle经常出现在以下场合:Activity状态数据的保存与恢复涉及到的两个回调:voidonSaveInstanceState(BundleoutState)voidonCreate(BundlesavedInstanceState)Fragment的setArguments方法:voidsetArguments(Bundleargs)消息机制中的Message的setData......
  • python selenium 行为错误:AttributeError:“Context”对象没有属性“driver”
    我正在使用pythonselenium与Behavior包一起工作。这是代码:@given('theuserisontheloginpage')defstep_given_user_on_login_page(context):PATH='C:/Users/PycharmProjects/ui_test/chromedriver-win32/chromedriver.exe'context.driver=......
  • Gson的基本使用:解析Json格式数据 序列化与反序列化
    目录一,Gson和Json1,Gson2,Json3,Gson处理对象的几个重要点4,序列化和反序列化二,Gson的使用1,Gson的创建2,简单对象序列化3,对象序列化,格式化输出日期4,嵌套对象序列化5,对象数组序列化6,对象集合序列化一,Gson和Json1,Gson        Gson是Google发布的一个Java库,可......
  • Android 常见面试题(一)
    Android常见面试题(一)1、java中==和equals和hashCode的区别基本数据类型的==比较的值相等.类的==比较的内存的地址,即是否是同一个对象。在不重写equals方法的情况下,equals同比较内存地址,原实现也为==,如String等重写了equals方法,会判断字符串里的值是否相等......
  • 数据库迁移的神秘面纱:Liquibase异常全解析与解决之道
    哈喽,大家好,我是木头左!一、Liquibase简介与重要性Liquibase,这个名字听起来就像是液体的魔法。在软件开发的世界里,它确实是一种魔法,一种让数据库变更变得清晰、可控甚至可逆的魔法。但就像所有魔法一样,掌握它需要了解它的规则和秘密。今天,就来揭开Liquibase的神秘面纱,特别是那些......
  • Android4.4.4双声卡同时出声
    在调试RK3288Android4.4.4,该方案默认配置上SPDIF时HDMI就没声音,但客户需求是同时要有声音的,于是驱动配置上后,呈现两个声卡状态,此时需要通过修改HAL层来处理(RK3288Android4.4.4对应的HAL层源码为hardware/rk29/audio目录),修改如下:1.修改AudioHardware.cpp文件,在AudioHardware:......
  • 20-c语言main函数参数`argc` 和 `argv[]` 解析
    argc和argv[]解析argc和argv[]是main函数的参数,用于处理命令行参数。一、示例命令行调用./a.out123345解释:./a.out是程序名,也是第一个参数。123和345是运行时传递的额外参数。二、main函数定义intmain(intargc,charconst*argv[]){re......
  • 深入理解 Java 类加载机制:Arthas classloader 命令解析
    引言Java虚拟机(JVM)的类加载机制是Java应用运行的基础。了解类加载器(ClassLoader)的工作原理对于解决类冲突、热部署、资源查找等问题至关重要。Arthas,作为一个强大的Java诊断工具,提供了classloader命令,帮助开发者深入理解JVM的类加载机制。本文将详细介绍classloa......
  • PD还是QC?快充协议全解析
    什么是快充协议快充协议是一种通过提高充电效率来缩短设备充电时间的电池充电技术。它是通过在充电器和设备之间建立一种沟通机制,充电器能够根据设备的需求和状态,调整输出的电压和电流。这种沟通机制由快充协议定义,它决定了设备和充电器如何互相识别和交流,以及如何调整电力输出。......
  • Qt实现简易CAD软件的开发:技术解析与实现
    文章目录简易CAD软件的开发:技术解析与实现引言项目概述程序入口主窗口的实现主窗口类定义(mainwindow.h)主窗口类实现(mainwindow.cpp)自定义绘图视图自定义绘图视图类定义(myqgraphicsview.h)自定义绘图视图类实现(myqgraphicsview.cpp)用户界面资源管理实现细节1.处理用户绘......