程序与技术分享:Context详解

简介: 程序与技术分享:Context详解

1、Context 概念


Context是个抽象类,通过类的结构可以看到:Activity、Service、Application都是Context的子类;


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


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


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


应用在三种情况下会创建Context对象(即通常说的context):


1> 创建Application 对象时,即第一次启动app时。 整个App共一个Application对象,所以也只有一个Application 的Context,Application销毁,它也销毁;


2> 创建Activity对象时。Activity销毁,它也销毁;


3> 创建Service对象时。Service销毁,它也销毁。


由此可以得到应用程序App可以创建的Context(Activity和Service没启动就不会创建)个数公式一般为:


总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context对象)


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同理。


3、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 //代码效果参考:http://hnjlyzjd.com/hw/wz_24201.html

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)直接调用就可以了。


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也可以。


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(AdapterViewarg0, 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引用的持有,防止内存泄漏。


6、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);


}


参考博文:

相关文章
|
5月前
|
消息中间件 Java 测试技术
技术分享:探讨@Transactional与@Async的共舞——能否同时使用及最佳实践
【8月更文挑战第13天】在Java的Spring框架中,@Transactional和@Async是两个非常强大的注解,它们分别用于控制事务的边界和优化应用程序的性能通过异步执行。然而,当这两个注解碰撞在一起时,是否能够和谐共存,成为了很多开发者在设计和构建高性能、高可靠性的应用程序时面临的一个关键问题。本文将深入探讨@Transactional与@Async的联合使用场景、潜在问题以及最佳实践。
247 0
|
5月前
|
存储 缓存 搜索推荐
Flutter开发者必读:sp_util - SharedPreferences的终极解决方案
Flutter开发者必读:sp_util - SharedPreferences的终极解决方案
103 0
|
8月前
|
数据库 Android开发
intent的使用小结--持续更新中
intent的使用小结--持续更新中
32 1
|
设计模式 安全 Java
重温Context
从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。
|
设计模式 XML 缓存
Android体系课学习 之 网络请求库Retrofit源码分析-看这一篇就够了
- 网络请求在我们开发中起的很大比重,有一个好的网络框架可以节省我们的开发工作量,也可以避免一些在开发中不该出现的bug - *Retrofit*是一个轻量级框架,基于*OkHttp*的一个*Restful*框架
|
存储 缓存 Java
Android体系课-开源框架-这是一份详细的Glide源码分析文章
最近在`组件化`开发中准备封装一个`图片加载库`,于是乎就有了这篇文章 本篇文章对`Glide`源码过程做了一个详细的讲解,也是为了记录下自己对`Glide`的理解,以后忘记还可以从这里查找。
|
消息中间件 存储 IDE
Android体系课--Handler-Handler面试题
面试官:说说Handler基本使用原理
|
监控 安全 Go
Go语言核心手册-11.context.Context
我们今天主要讨论的是context包中的函数和Context类型,该包中的函数都是用于产生新的Context类型值的,Context类型是一个可以帮助我们实现多goroutine 协作流程的同步工具,不但如此,我们还可以通过此类型的值传达撤销信号或传递数据。
110 0
|
JavaScript 搜索推荐
BackboneJs入门学习[11]—View实践
BackboneJs入门学习[11]—View实践
138 0
BackboneJs入门学习[11]—View实践