Android 中 上下文Context理解

Context

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

以上是api-28中google官方对context的注释,大概翻译如下:
1.是对应用上下文的描述;
2.是抽象类;
3、通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作。

Context类它有两个具体的实现子类:ContextImpl和ContextWrapper。其中ContextWrapper类,如其名所言,这只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。ContextThemeWrapper类,其内部包含了与主题(Theme)相关的接口,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper,Application同理。而ContextImpl类则真正实现了Context中的所有函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类。

一、Context相关类的继承关系
源代码(部分)如下:

public abstract class Context {  
         ...  
         //获得系统级服务  
         public abstract Object getSystemService(String name);  
          //通过一个Intent启动Activity  
         public abstract void startActivity(Intent intent);    
         //启动Service 
         public abstract ComponentName startService(Intent service);   
         //根据文件名得到SharedPreferences对象  
         public abstract SharedPreferences getSharedPreferences(String name,int mode); //Activity token  弹框中有用到
          public IBinder getActivityToken();
         //
         public ApplicationInfo getApplicationInfo();
         ...  
    }

ContextImpl.java类,该类实现了Context类的功能。请注意,该函数的大部分功能都是直接调用其属性mPackageInfo去完成.
源代码(部分)如下:

class ContextImpl extends Context{  
        //所有Application程序公用一个mPackageInfo对象  
       ActivityThread.PackageInfo mPackageInfo;  
          
        @Override  
        public Object getSystemService(String name){  
            ...  
            else if (ACTIVITY_SERVICE.equals(name)) {  
                return getActivityManager();  
            }   
            else if (INPUT_METHOD_SERVICE.equals(name)) {  
                return InputMethodManager.getInstance(this);  
            }  
        }   
        @Override  
        public void startActivity(Intent intent) {  
            ...  
            //开始启动一个Activity  
            mMainThread.getInstrumentation().execStartActivity(  
                getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1);  
        }  
    }

ContextWrapper类是对Context类的一种包装,该类的构造函数包含了一个真正的Context引用,即ContextIml对象。

public class ContextWrapper extends Context {  
        Context mBase;  //该属性指向一个ContextIml实例,一般在创建Application、Service、Activity时赋值  
          
        //创建Application、Service、Activity,会调用该方法给mBase属性赋值  
        protected void attachBaseContext(Context base) {  
            if (mBase != null) {  
                throw new IllegalStateException("Base context already set");  
            }  
            mBase = base;  
        }  
        @Override  
        public void startActivity(Intent intent) {  
            mBase.startActivity(intent);  //调用mBase实例方法  
        }  
    }

ContextThemeWrapper类包含了主题(Theme)相关的接口,即android:theme属性指定的。只有Activity需要主题,Service不需要主题,所以Service直接继承于ContextWrapper类。

public class ContextThemeWrapper extends ContextWrapper {  
         //该属性指向一个ContextIml实例,一般在创建Application、Service、Activity时赋值  
         private Context mBase;  
        //mBase赋值方式同样有一下两种  
         public ContextThemeWrapper(Context base, int themeres) {  
                super(base);  
                mBase = base;  
                mThemeResource = themeres;  
         }  
      
         @Override  
         protected void attachBaseContext(Context newBase) {  
                super.attachBaseContext(newBase);  
                mBase = newBase;  
         }  
    }

应用程序创建Context实例的:

  • 创建Application对象时,而且整个App共一个Application对象
  • 创建Service对象时
  • 创建Activity对象时

所以App共有的Context数目公式为:
总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context实例)

1、创建Application对象
  每个应用程序在第一次启动时,都会首先创建Application对象。如果对应用程序启动一个Activity(startActivity)流程比较清楚的话,创建Application的时机在创建handleBindApplication()方法中,该函数位于 ActivityThread.java类中 ,如下:
    //创建Application时同时创建的ContextIml实例  
    private final void handleBindApplication(AppBindData data){  
        ...  
        ///创建Application对象  
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);  
        ...  
    }  
      
    public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {  
        ...  
        try {  
            java.lang.ClassLoader cl = getClassLoader();  
            ContextImpl appContext = new ContextImpl();//创建一个ContextImpl对象实例  
            appContext.init(this, null, mActivityThread);//初始化该ContextIml实例的相关属性  
            ///新建一个Application对象   
            app = mActivityThread.mInstrumentation.newApplication(  
                    cl, appClass, appContext);  
           appContext.setOuterContext(app);  //将该Application实例传递给该ContextImpl实例           
        }   
        ...  
    }
2、创建Activity对象
  通过startActivity()或startActivityForResult()请求启动一个Activity时,如果系统检测需要新建一个Activity对象时,就会回调handleLaunchActivity()方法,该方法继而调用performLaunchActivity()方法,去创建一个Activity实例,并且回调onCreate(),onStart()方法等,函数都位于 ActivityThread.java类 ,如下:
    private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {  
        ...  
        Activity a = performLaunchActivity(r, customIntent);  //启动一个Activity  
    }  
    private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {  
        ...  
        Activity activity = null;  
        try {  
            //创建一个Activity对象实例  
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();  
            activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);  
        }  
        if (activity != null) {  
            ContextImpl appContext = new ContextImpl();      //创建一个Activity实例  
            appContext.init(r.packageInfo, r.token, this);   //初始化该ContextIml实例的相关属性  
            appContext.setOuterContext(activity);            //将该Activity信息传递给该ContextImpl实例  
            ...  
        }  
        ...      
    }
3、创建Service对象
     通过startService或者bindService时,如果系统检测到需要新创建一个Service实例,就会回调handleCreateService()方法完成相关数据操作。handleCreateService()函数位于 ActivityThread.java类,如下:
    //创建一个Service实例时同时创建ContextIml实例  
    private final void handleCreateService(CreateServiceData data){  
        ...  
        //创建一个Service实例  
        Service service = null;  
        try {  
            java.lang.ClassLoader cl = packageInfo.getClassLoader();  
            service = (Service) cl.loadClass(data.info.name).newInstance();  
        } catch (Exception e) {  
        }  
        ...  
        ContextImpl context = new ContextImpl(); //创建一个ContextImpl对象实例  
        context.init(packageInfo, null, this);   //初始化该ContextIml实例的相关属性  
        //获得我们之前创建的Application对象信息  
        Application app = packageInfo.makeApplication(false, mInstrumentation);  
        //将该Service信息传递给该ContextImpl实例  
        context.setOuterContext(service);  
        ...  
    }

通常我们想要获取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也可以。
    区别:getApplication()和getApplicationContext()

Context引起的一些问题(网络上的例子)
错误的单例模式

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

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传入的this是MainActivity的mContext,因为被static修饰的mDrawable是常驻内存的,MainActivity是它的间接引用,MainActivity被销毁时,也不能被GC掉,所以造成内存泄漏。
正确使用Context
一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
2:不要让生命周期长于Activity的对象持有到Activity的引用。
3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
自己的经验:
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引用的持有,防止内存泄漏。

猜你喜欢

转载自blog.csdn.net/JiKaTogether/article/details/90949141