美文网首页Android 基础
Android中的context对象

Android中的context对象

作者: 风一样的行者 | 来源:发表于2018-04-26 00:13 被阅读0次

    今天来聊一聊Android中的context对象

    1.Context是什么

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

    context的类图结构.jpg

    从类图中我们可以得出一个进程中context的总体数量为 : 注意 这里只是应用级的context对象
    总体数量 = service数量 + activity 数量 + 1(application数量)//一个进程中只能存在一个application对象 故application的数量为 1

    从类图中可以看出,context结构组成用的是典型的 装饰者模式ContextImpl 是 context 抽象的具体实现(被装饰者对象),context的大部分功能都是在这个类中实现,ContextWrapper是context的扩展(装饰者对象)。下面来看看 ContextWrapper 的具体代码:

        public ContextWrapper(Context base) {//这里传进来的其实是contextImpl对象
            mBase = base;
        }
        
        /**
         * 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;//这里传进来的其实是contextImpl对象
        }
    

    有了mBase这个context对象(实际是contextImpl),contextWrapper中大部分的操作都是调用该对象中的方法完成。

    2.Context的作用

    context中定义了一系列Android引用中相当牛逼的方法,如资源文件获取,文件管理,包管理,类加载,权限管理,系统级别服务获取等。下面列举一部分:

    获取AssetManager:getAssets();
    获取Resources:getResources();
    获取PackageManager:getPackageManager();
    获取ContentResolver:getContentResolver();
    获取主线程Looper:getMainLooper();
    获取Application的Context:getApplicationContext();
    获取资源文件:getText,getString,getColor,getDrawable,getColorStateList;
    设置主题,获取主题资源id:setTheme,getThemeResId;
    获取样式属性TypedArray:obtainStyledAttributes();
    获取类加载器ClassLoader:getClassLoader();
    获取应用信息对象ApplicationInfo:getApplicationInfo();
    获取SharedPreferences:getSharedPreferences();
    打开文件FileInputStream:openFileInput();
    删除文件:deleteFile();
    获取文件File:getFileStreamPath();
    打开或者创建数据库:openOrCreateDatabase();
    移除或者删除数据库:moveDatabaseFrom(),deleteDatabase();
    **启动Activity:startActivity(), startActivityAsUser(), startActivityForResult(), startActivities(); **
    注册、发送、注销广播:registerReceiver(), sendBroadcast(), sendOrderedBroadcast(), unregisterReceiver();

    启动、绑定、解除绑定、停止服务:startService(), bindService(), unbindService(), stopService();
    获取系统服务:getSystemService();
    检查权限(Android 6.0以上):checkPermission();
    根据应用名创建Context:createPackageContext();
    根据应用信息创建Context:createApplicationContext();
    获取显示信息对象Display:getDisplay();

    高亮显示的是我们常用的几个context的方法,其具体实现在我们的劳动力 ContextImpl

    application与activity的创建都是在activity启动的时候,service的创建过程与这个过程差不多,只不过是在启动service中创建而已

    3.Context的作用域

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

    Context的作用域.png

    这里要注意两点:

    1:如果我们用ApplicationContext去启动一个LaunchMode 为 standard的Activity 的时候会报错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的方式不推荐使用,Service同Application。
    2:在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。
    一句话总结:凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。

    4.如何获取Context

    通常我们想要获取Context对象,主要有以下四种方法
    1:View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
    2:Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
    3:ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
    4:Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

    5.Context引起的内存泄露

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

    错误的单例模式

    错误的单例模式.png

    这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity A去getInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。

    View持有Activity引用

    view持有context的引用.png

    有一个静态的Drawable对象当ImageView设置这个Drawable时,ImageView保存了mDrawable的引用,而ImageView传入的this是MainActivity的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。

    6.正确使用Context

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

    1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
    2:不要让生命周期长于Activity的对象持有到Activity的引用。
    3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

    相关文章

      网友评论

        本文标题:Android中的context对象

        本文链接:https://www.haomeiwen.com/subject/mckulftx.html