Context详解

一、Context 概念html

 Context是个抽象类,经过类的结构能够看到:Activity、Service、Application都是Context的子类;java

从Android系统的角度来理解:Context是一个场景,描述的是一个应用程序环境的信息,即上下文,表明与操做系统的交互的一种过程。android

从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。设计模式

查看类的继承关系:ctrl + H         (Windows系统)安全

应用在三种状况下会建立Context对象(即一般说的context): 
1> 建立Application 对象时,即第一次启动app时。 整个App共一个Application对象,因此也只有一个Application 的Context,Application销毁,它也销毁; 
2> 建立Activity对象时。Activity销毁,它也销毁; 
3> 建立Service对象时。Service销毁,它也销毁。app

由此能够获得应用程序App能够建立的Context(Activity和Service没启动就不会建立)个数公式通常为: 
总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context对象) eclipse

 

2.Context 继承结构异步

Context的继承结构仍是稍微有点复杂的,能够看到,直接子类有两个,一个是ContextWrapper,一个是ContextImpl。那么从名字上就能够看出,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity。在这里咱们看到了几个所比较熟悉的面孔,Activity、Service、还有Application。由此,咱们能够大体得出结论,Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各类承担着不一样的做用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的。其实Context还有一个直接子类MockContext,,该类能够理解为模拟context,源码位于android.test.mock包中,API文档中找不到。当咱们要测试一个模块A,他依赖于其它模块B,可是模块B还没实现或如今根本没有,这时就要使用MockContext和其余一样位于android.test.mock包中的类。经过它能够注入其余依赖,模拟Context,或者监听测试的类。主要是在TDD中使用这些MOCK类来代替真实的类,用法可参考Mock在Android TDD中的使用想查看ContextImpl的源码时,没法找到ContextImpl这个类。因为ContextImpl是抽象类Context的实现类。然而查看Context类的继承结构,以下图:没有发现ContextImpl。后来查到缘由是:这个文件是保护文件,就是注解了是内部保护文件,因此在eclipse,Androidstudio中都是不显示的。因此能够去SDk的安装目录中的sources文件夹中直接找那个Java文件,/android-sdk/sources/android-19/android/app/ContextImpl.java。Mockcontext同理。ide

 

三、Context 经常使用方法函数

// 获取应用程序包的AssetManager实例
public abstract AssetManager getAssets();

// 获取应用程序包的Resources实例
public abstract Resources getResources();

// 获取PackageManager实例,以查看全局package信息
public abstract PackageManager getPackageManager();

// 获取应用程序包的ContentResolver实例
public abstract ContentResolver getContentResolver();

// 它返回当前进程的主线程的Looper,此线程分发调用给应用组件(activities, services等)
public abstract Looper getMainLooper();

// 返回当前进程的单实例全局Application对象的Context
public abstract Context getApplicationContext();

// 从string表中获取本地化的、格式化的字符序列
public final CharSequence getText(int resId) {
return getResources().getText(resId);
}

// 从string表中获取本地化的字符串
public final String getString(int resId) {
return getResources().getString(resId);
}

public final String getString(int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}

// 返回一个可用于获取包中类信息的class loader
public abstract ClassLoader getClassLoader();

// 返回应用程序包名
public abstract String getPackageName();

// 返回应用程序信息
public abstract ApplicationInfo getApplicationInfo();

// 根据文件名获取SharedPreferences
public abstract SharedPreferences getSharedPreferences(String name,
int mode);

// 其根目录为: Environment.getExternalStorageDirectory()
public abstract File getExternalFilesDir(String type);

// 返回应用程序obb文件路径
public abstract File getObbDir();

// 启动一个新的activity
public abstract void startActivity(Intent intent);

// 启动一个新的activity
public void startActivityAsUser(Intent intent, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}

// 启动一个新的activity
// intent: 将被启动的activity的描述信息
// options: 描述activity将如何被启动
public abstract void startActivity(Intent intent, Bundle options);

// 启动多个新的activity
public abstract void startActivities(Intent[] intents);

// 启动多个新的activity
public abstract void startActivities(Intent[] intents, Bundle options);

// 广播一个intent给全部感兴趣的接收者,异步机制
public abstract void sendBroadcast(Intent intent);

// 广播一个intent给全部感兴趣的接收者,异步机制
public abstract void sendBroadcast(Intent intent,String receiverPermission);
//发送有序广播
public abstract void sendOrderedBroadcast(Intent intent,String receiverPermission);

public abstract void sendOrderedBroadcast(Intent intent,
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras);

public abstract void sendBroadcastAsUser(Intent intent, UserHandle user);

public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission);

// 注册一个BroadcastReceiver,且它将在主activity线程中运行
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter);
//取消注册BroadcastReceiver
public abstract Intent registerReceiver(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission, Handler scheduler);

public abstract void unregisterReceiver(BroadcastReceiver receiver);

// 请求启动一个application service
public abstract ComponentName startService(Intent service);

// 请求中止一个application service
public abstract boolean stopService(Intent service);

// 链接一个应用服务,它定义了application和service间的依赖关系
public abstract boolean bindService(Intent service, ServiceConnection conn,
int flags);

// 断开一个应用服务,当服务从新开始时,将再也不接收到调用,
// 且服务容许随时中止
public abstract void unbindService(ServiceConnection conn);

// 返回系统级service
public abstract Object getSystemService(String name);
//检查权限
public abstract int checkPermission(String permission, int pid, int uid);

// 返回一个新的与application name对应的Context对象
public abstract Context createPackageContext(String packageName,
int flags) throws PackageManager.NameNotFoundException;

// 返回基于当前Context对象的新对象,其资源与display相匹配
public abstract Context createDisplayContext(Display display);

Context的主要功能为:

1)启动Activity 2)启动和中止Service 3)发送广播消息(Intent) 4)注册广播消息(Intent)接收者 5)能够访问APK中各类资源(如Resources和AssetManager等) 6)能够访问Package的相关信息 7)APK的各类权限管理

Context几乎算是对APK包无所不知的大管家,你们须要什么,Context子类里(一般在Activity和Service)直接调用就能够了。

 

四、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也能够。

public class MyActivity extends Activity {
    Context mContext;
    public void method() {

        mContext = this; //获取当前Activity的上下文,若是须要绑定Activity的生命周期,使用它

        mContext=MyActivity.this;//获取当前MyActivity的上下文,不方便使用this的时候推荐使用这种方式

        //调用Activity.getApplicationContext()
        mContext = getApplicationContext();//获取当前Application的上下文,若是须要绑定应用的生命周期,使用它

        //Activity.getApplication()
        mContext = getApplication();//获取当前Application的上下文,

        //调用ContextWrapper.getBaseContext()
        mContext = getBaseContext();//从上下文A内上下文访问上下文A,不建议使用,若是须要,推荐使用XxxClass.this直接指出上下文
    }
}


public class MyView extends View {
    Context mContext;
    public void method() {

        //调用View.getContext()
        mContext = getContext(); //获取这个View运行所在地的上下文
    }
}

 

1)this和getBaseContext()

  • this:表明当前,在Activity当中就是表明当前的Activity,换句话说就是Activity.this在Activity当中能够缩写为this。Activity.this的context 返回当前activity的上下文,属于activity ,activity 摧毁他就摧毁。
  • getBaseContext() 返回由构造函数指定或setBaseContext()设置的上下文。
Spinner spinner = (Spinner) findViewById(R.id.spinner);
spinner.setAdapter(adapter);            
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?>arg0, View arg1, int arg2, long arg3){

      //Toast.makeText(getBaseContext(),"SELECTED", Toast.LENGTH_SHORT).show();//可使用它,可是不建议
        Toast.makeText(MainActivity.this,"SELECTED", Toast.LENGTH_SHORT).show();//推荐使用XxxClass.this直接指出了使用的是谁的上下文,更简捷。
      //Toast.makeText(this,"SELECTED", Toast.LENGTH_SHORT).show();//不可用,这里this指的不是Activity,而是spinner这个类。

    }
}

 

2)getApplicationContext()和getApplication()

  • getApplicationContext 取得的是当前app所使用的application,这在AndroidManifest中惟一指定。意味着,在当前app的任意位置使用这个函数获得的是同一个Context,getApplicationContext(): 返回应用的上下文,生命周期是整个应用,应用摧毁,它才摧毁。
  • getApplication():andorid 开发中共享全局数据;

getApplication()只能在Activity和Service里使用,指向的是Application对象,由于Application也是Context的一个子类,因此getApplication()能够被用来指向Context。

好比若是想要获取在应用清单文件中声明的类,最好不要使用getApplicationContext(),而且最好使用强制转换为本身自定义的Application,由于那样可能会得不到Application对象。

  Log.i("dyl", "getApplication is = " + myApp);
  Log.i("dyl", "getApplicationContext is = " + appContext);

经过上面的代码,打印得出二者的内存地址都是相同的,看来它们是同一个对象。其实这个结果也很好理解,由于前面已经说过了,Application自己就是一个Context,因此这里获取getApplicationContext()获得的结果就是Application自己的实例。那么问题来了,既然这两个方法获得的结果都是相同的,那么Android为何要提供两个功能重复的方法呢?实际上这两个方法在做用域上有比较大的区别。getApplication()方法的语义性很是强,一看就知道是用来获取Application实例的,可是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数状况下咱们都是在Activity或者Service中使用Application的,可是若是在一些其它的场景,好比BroadcastReceiver中也想得到Application的实例,这时就能够借助getApplicationContext()方法了。

 

5.Context 应用场景

由于Context的具体能力是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是能够通用的。不过有几种场景比较特殊,好比启动Activity,还有弹出Dialog。出于安全缘由的考虑,Android是不容许Activity或Dialog凭空出现的,一个Activity的启动必需要创建在另外一个Activity的基础之上,也就是以此造成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),所以在这种场景下,咱们只能使用Activity类型的Context,不然将会出错。

Context的应用场景图

你们注意看到有一些NO上添加了一些数字,其实这些从能力上来讲是YES,可是为何说是NO呢?下面一个一个解释:

数字1:启动Activity在这些类中是能够的,可是须要建立一个新的task。通常状况不推荐。

数字2:在这些类中去layout inflate是合法的,可是会使用系统默认的主题样式,若是你自定义了某些样式可能不会被使用。

数字3:在receiver为null时容许,在4.2或以上的版本中,用于获取黏性广播的当前值。(能够无视)

注:ContentProvider、BroadcastReceiver之因此在上述表格中,是由于在其内部方法中都有一个context用于使用。

 好了,这里咱们看下表格,重点看Activity和Application,能够看到,和UI相关的方法基本都不建议或者不可以使用Application,而且,前三个操做基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity作为Context来处理;其余的一些操做,Service,Activity,Application等实例均可以,固然了,注意Context引用的持有,防止内存泄漏。

 

六、Context 使用过程当中的注意项

1)Activity mActivity =new Activity()

这样写语法上没有任何错误,Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象。可是,

Android程序不像Java程序同样,随便建立一个类,写个main()方法就能运行,Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境,在这个环境下,Activity、Service等系统组件才可以正常工做,而这些组件并不能采用普通的Java对象建立方式,new一下就能建立实例了,而是要有它们各自的上下文环境,才能使得其正常工做。即走正常的onCreate-onStart-onResume。。。

2)你们在编写一些类时,例如工具类,可能会编写成单例的方式,这些工具类大多须要去访问资源,也就说须要Context的参与。

在这样的状况下,就须要注意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;  
    }  
}  

对于上述的单例,你们应该都不陌生(请别计较getInstance的效率问题),内部保持了一个Context的引用;这么写是没有问题的,问题在于,这个Context哪来的咱们不能肯定,很大的可能性,你在某个Activity里面为了方便,直接传了个this;这样问题就来了,咱们的这个类中的sInstance是一个static且强引用的,在其内部引用了一个Activity做为Context,也就是说,咱们的这个Activity只要咱们的项目活着,就没有办法进行内存回收。而咱们的Activity的生命周期确定没这么长,因此形成了内存泄漏。那么,咱们如何才能避免这样的问题呢?有人会说,咱们能够软引用,嗯,软引用,假如被回收了,你不怕NullPointException么。把上述代码作下修改:

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

这样,咱们就解决了内存泄漏的问题,由于咱们引用的是一个ApplicationContext,它的生命周期和咱们的单例对象一致。

  3)Intent也要求指出上下文,若是想启动一个新的Activity,就必须在Intent中使用Activity的上下文,这样新启动的Activity才能和当前Activity有关联(在activity栈);也可使用application的context,可是须要在Intent中添加 Intent.FLAG_ACTIVITY_NEW_TASK标志,看成一个新任务。ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错,非Activity类型的Context并无所谓的任务栈,因此待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它建立一个新的任务栈,而此时Activity是以singleTask模式启动的。因此这种用Application启动Activity的方式不推荐使用,Service同Application。

public static void openActivity(Context context){
        Intent intent = new Intent(context.getApplicationContext(), SecondActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.getApplicationContext().startActivity(intent);
    }

 

 
 

参考博文:

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

http://www.jianshu.com/p/94e0f9ab3f1d

http://blog.csdn.net/u012585964/article/details/52268235
相关文章
相关标签/搜索