从getApplicationContext和getApplication再次梳理Android的Application正确用法
原文地址http://blog.csdn.net/ly502541243/article/details/52105466
原文地址http://blog.csdn.net/ly502541243/article/details/52105466
Context
在Android开发的时候,很多地方我们都会用上Context这个东西,比如我们最常用的startActivity,以前也没怎么在意这个到底有什么用,方法要参数就直接传过去,今天看到getApplicationContext和getApplication有点懵逼,我觉得有必要去一探究竟了,首先看看什么是Context:
Context,翻译为上下文,环境。不过又想问啥又是上下文,啥又是环境,程序还有上下文。。。为了不误人子弟,来Google的官方说法:
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
翻译下:它是一个应用程序的全局环境,是Android系统的一个抽象类,可以通过它获取程序的资源,比如:加载Activity,广播,接收Intent信息等等。
总的来说它就像是一个程序运行的时候的环境,如果Activity,Service这些是水里的鱼,那它就是水?(原谅我的理解能力,不知道怎么形容),好吧,理解不透就看代码(以下代码来自API-23):
public abstract class Context {}
- 1
首先它是个抽象类,那它提供了哪些方法,哎,太多了,随便看几个吧:
//[这个可以看看我的博客另外一篇专门讲消息机制的](http://blog.csdn.net/ly502541243/article/details/52062179/)
public abstract Looper getMainLooper();
//获取当前应用上下文
public abstract Context getApplicationContext();
//开启activity
public abstract void startActivity(Intent intent);
//获取valus/strings.xml声明的字符串
public final String getString(@StringRes int resId) {
return getResources().getString(resId);
}
//获取valus/colors.xml声明的颜色
public final int getColor(int id) {
return getResources().getColor(id, getTheme());
}
//发送广播
public abstract void sendBroadcast(Intent intent);
//开启服务
public abstract ComponentName startService(Intent service);
//获取系统服务(ALARM_SERVICE,WINDOW_SERVICE,AUDIO_SERVICE、、、)
public abstract Object getSystemService(@ServiceName @NonNull String name);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
我们发现Context这个抽象类里面声明了很多我们开发中常用一些方法,那有哪些类实现了这个抽象类呢(普及一个快捷键,eclipse下点击类后按F4可以看这个类的继承结构,AndroidStudio我设置的是eclipse的快捷键),结构如下
- Context
- ContextWrapper
- TintContextWrapper
- ContextThemeWrapper
- IsolatedContext
- MutableContextWrapper
- ContextThemeWrapper
- Activity
- Service
- RenamingDelegatingContext
- Application
- BackupAgent
我们主要关注一下:ContextWrapper,Activity,Service,Application,先来看看Context的主要实现类ContextWrapper(剧透:其实这并不是真正的实现类):看下官方注释,意思就是这是个简单的实现:
Proxying implementation of Context that simply delegates all of its calls to another Context.
构造方法:
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
//设置BaseContext,同构造方法,多了个不为空的判断
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
.....
.....
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
@Override
public Looper getMainLooper() {
return mBase.getMainLooper();
}
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
注意方法是public的,所以继承类可以直接访问,看了方法的实现,我们发现真是simply,就是都交给mBase来做相应的处理,关键就是构造方法或者attachBaseContext方法设置mBase并且进行操作。
来看看我们最常用的Activity,主要看看getApplication:
public class Activity extends ContextThemeWrapper implements ... {
private Application mApplication;
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
...
mApplication = application;
}
}
public final Application getApplication() {
return mApplication;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
我们看到了在attach调用了我们刚才说的attachBaseContext,还有给mApplication赋值。这里出现了另外一个我们关注的Application,到源码看看:
//构造方法传了个空,貌似没什么用
public Application() {
super(null);
}
//同样在attach中我们看到了具体的东西
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Application和Activity的attach方法感觉都差不多,都调用了attachBaseContext(context),成为了一个Context。
这里还看到了ContextImpl,其实它才是Context的真正实现类(看名字也看出来了),可是刚才我们看Context的继承结构时没看到这个类啊,原来它跟ActivityThread一样,并没有在sdk中,所以是看不到的。这个类的具体实现就不仔细看了,再看要晕了,后面有时间再细细品味…
但是我们知道了mApplication和context是两个不同的东西,所以严格意义上来说getApplicationContext和getApplication是不一样的,虽然很多时候他们返回的都是同一个对象,但是getApplication只存在于Activity或者Service中,我们要注意具体的情况,这个我们后面再说
Application
为何物
看到这里我们发现,Application和Activity都继承自Context,他们都是环境,只不过Application是随着我们的应用(或者包)启动的时候就存在的环境,Activity是一个界面的环境
使用方法
既然Application是在应用一创建就初始化了,而且是在应用运行时一直存在的,那我们可以把它当做是一个全局变量来使用,可以保存一些共享的数据,或者说做一些工具类的初始化工作。要自己来使用Application的话我们需要先新建一个类来继承Application
public class MyApplication extends Application {}
- 1
然后重写它的onCreate做一些工具的初始化:
@Override
public void onCreate() {
super.onCreate();
ToastUtils.register(this);
//LeakCanary检测OOM
LeakCanary.install(this);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
最后一个关键的工作是要在manifest里面做一下声明(无关代码我忽略了)
<application
android:name=".MyApplication"
...
</application>
- 1
- 2
- 3
- 4
然后说说Application的获取问题,一个方法是我们直接 (MyApplication)getApplication(),但是还有一种更常见的做法,要在其他没有Context的地方也能拿到怎么办呢?可以这样,仿照单例的做法(只是仿照!),在MyApplication声明一个静态变量
public class MyApplication extends Application {
private static MyApplication instance;
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
}
// 获取ApplicationContext
public static Context getMyApplication() {
return instance;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
至此我们拿到了MyApplication实例,注意这跟我们常见的单例不一样,不要自作聪明去在getMyApplication里面做一下空的判断,Application在应用中本来就是一个单例,所以每次返回的都是同一个实体,原文如下:
There is normally no need to subclass Application. In most situation, static singletons can provide the same functionality in a more modular way.
总结
Application,Activity,Service都是继承自Context,是应用运行时的环境,我们可以把Application看做是应用,Activity看做是一个界面,至于getApplicationContext和getApplication,他们返回的对象有可能不一样(虽然大部分时间是一样的,都是整个应用的上下文),如果想要拿到在manifest里面声明的那个Application,务必用getApplication,贴下原文:
getApplication() is available to Activity and Services only. Although in current Android Activity and Service implementations, getApplication() and getApplicationContext() return the same object, there is no guarantee that this will always be the case (for example, in a specific vendor implementation). So if you want the Application class you registered in the Manifest, you should never call getApplicationContext() and cast it to your application, because it may not be the application instance (which you obviously experienced with the test framework).
还有就是因为他们都继承自Context,比如在打开Dialog的时候好像是都可以,其实不然,比如我们大多数情况:
AlertDialog.Builder builder = new Builder(Activity.this);//可以
AlertDialog.Builder builder = new Builder(getApplicationContext());//内存泄漏
- 1
- 2
如果把this换成getApplicationContext(),不会报错,但是就如我们刚才所说,getApplicationContext() 返回的上下文会随着应用一直存在,而这里的Dialog应该属于Activity,Activity关闭了我们无法销毁上下文(Dialog持有全局的上下文)。
所以在使用的时候要注意具体的使用场景,避免内存泄漏问题。
=====================================================================================================
=====================================================================================================
深入探究getApplicationContext和getApplication是不是返回同一个对象?
在上篇文章从getApplicationContext和getApplication再次梳理Android的Application正确用法中,我提到
但是我们知道了mApplication和context是两个不同的东西,所以严格意义上来说getApplicationContext和getApplication是不一样的,虽然很多时候他们返回的都是同一个对象
注意到我这里说的是这两个方法返回的对象是不一样的,因为我看到Activity中这两个方法返回了两个对象,就单纯的以为他们真的是不一样的,看来真是浅尝辄止了,做了个错误示范,代码还是要刨根问底啊。
找不同
今天来做一个纠正和补充,我们来继续往下看代码,看看他们是不是真的不一样,还是有相似之处:
public abstract Context getApplicationContext();
- 1
getApplicationContext我们知道是一个抽象方法,他的真正实现是在ContextImpl中:
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
- 1
- 2
- 3
- 4
- 5
再来看看getApplication方法(只存在于Activity和Service中):
public final Application getApplication() {
return mApplication;
}
- 1
- 2
- 3
那mApplication的赋值在哪?搜索一下,只有一个地方有赋值:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
.......
mApplication = application;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
在上篇文章中,我看到了这里就觉得这两个方法返回的对象不一样,可是我们忽略了getApplicationContext这个方法,当mPackageInfo不为空和为空是分别调用了mPackageInfo.getApplication()和mMainThread.getApplication(),那getApplicationContext到底返回的东西跟mApplication有什么不同,来看看这两个方法,在LoadedApk.java中看到mPackageInfo.getApplication():
Application getApplication() {
return mApplication;
}
- 1
- 2
- 3
在LoadedApk也有一个mApplication,这个mApplication的赋值在LoadedApk的makeApplication:
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
...
if (mApplication != null) {
return mApplication;
}
Application app = null;
...
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
...
mActivityThread.mAllApplications.add(app);
mApplication = app;
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
看到首先是一个空判断(单例),为空的话新建了一个Application然后赋值给mApplication,我们再看看mMainThread.getApplication()返回了什么,在ActivityThread.java中:
public Application getApplication() {
return mInitialApplication;
}
- 1
- 2
- 3
再来看看mInitialApplication的赋值在哪里:
private void handleBindApplication(AppBindData data) {
...
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
我们又看到了makeApplication,至于data.info也是LoadedApk这个类,看到这里我们就一目了然了,绕来绕去结果都是同一个东西,只是可能创建的时机不同,一个是在LoadedApk,一个是在ActivityThread,不过最后我们发现这个getApplicationContext()返回的都是mApplication。
真相大白
这个命名就很有意思了,在LoadedApk我们看到了一个叫mApplication的东西,在Activity也有一个叫mApplication,那他们是不是有什么联系呢?来看看在Activity中mApplication的赋值,在attach方法中找到了它(方法中的其他参数我去掉了):
final void attach(Application application) {
mApplication = application;
}
- 1
- 2
- 3
也就是说等于调用attach方法时传入的application,那Activity的attach是在哪里调用呢,我们要来到反复提到的一个应用程序入口类ActivityThread,它有一个performLaunchActivity的方法,用来加载一个Activity,这里就有attach()的调用(我去掉了其他参数):
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
activity.attach(app);
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
我们发现又来了。。。熟悉的makeApplication(),r.packageInfo果然是LoadedApk类,最后殊途同归,又来到了这个单例,返回程序唯一的mApplication,还是一样的配方。。。
结果
结果很明显了,标题的问题已解,getApplicationContext和getApplication返回的是不是同一个对象?答:是的!
当然话不能说的那么死,他们相同的前提是mApplication不为空,话又说回来,这个是全局的上下文,程序都启动了他怎么会为空呢,至于它到底什么情况会为空造成返回的对象不一样呢,待武功精进了继续分解。。。
从getApplicationContext和getApplication再次梳理Android的Application正确用法的更多相关文章
- 【转】Android中Application类用法
转自:http://www.cnblogs.com/renqingping/archive/2012/10/24/Application.html Application类 Application和A ...
- Android中Application类用法
Application类 Application和Activity,Service一样是Android框架的一个系统组件,当Android程序启动时系统会创建一个Application对象,用来存储系 ...
- Android之Application类用法
Application和Activity,Service一样是Android框架的一个系统组件,当Android程序启动时系统会创建一个Application对象,用来存储系统的一些信息. Andro ...
- Android 中this、getContext()、getApplicationContext()、getApplication()、getBaseContext() 之间的区别
: 知之为知之,不知为不知是知也! 使用this, 说明当前类是context的子类,一般是activity application等; this:代表当前,在Activity当中就是代表当前的Act ...
- [转]Android中Application类的用法
原文链接:http://www.cnblogs.com/renqingping/archive/2012/10/24/Application.html Application类 Application ...
- Android(java)学习笔记120:Android中的Application类用法
1.简介 如果想在整个应用中使用全局变量,在java中一般是使用静态变量,public类型:而在android中如果使用这样的全局变量就不符合Android的框架架构,但是可以使用一种更优雅的方式就是 ...
- Android(java)学习笔记61:Android中的 Application类用法
1. 简介 如果想在整个应用中使用全局变量,在java中一般是使用静态变量,public类型:而在android中如果使用这样的全局变量就不符合Android的框架架构,但是可以使用一种更优雅的方式就 ...
- Android中Application类的详解:
Android中Application类的详解: 我们在平时的开发中,有时候可能会须要一些全局数据.来让应用中的全部Activity和View都能訪问到.大家在遇到这样的情况时,可能首先会想到自定义一 ...
- Android使用Application总结
对于application的使用,一般是 在Android源码中对他的描述是; * Base class for those who need to maintain global applicati ...
随机推荐
- Python 入门(二)Unicode字符串
Unicode字符串 字符串还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节 (byte),所以,一 ...
- vuejs开发环境搭建
前言:现在前端最火的是3个框架:react,vue,angular.可以说着是哪个框架大大改变了前端的地位.相对于angular来说.vue同样拥有丰富的指令,并且都是典型的MVC框架,但是vue比较 ...
- 解决Xcode "The selected destination does not support the architecture " 错误错误
XCODE编译运行项目后,发现工程编译后无法运行,出现:"The selected destination does not support the architecture for whi ...
- MyEclipse10配置PyDev进行Python开发
MyEclipse10配置PyDev进行Python开发 1.下载PyDev 2.7.1 链接如下: http://jaist.dl.sourceforge.net/project/pydev ...
- Android之ListView分页数据加载
1.效果如下: 实例如下: 上图的添加数据按钮可以换成一个进度条 因为没有数据所以我加了一个按钮添加到数据库用于测试:一般在服务器拉去数据需要一定的时间,所以可以弄个进度条来提示用户: 点击加载按 ...
- 17,UC(06)
/* 达内学习 UC day06 2013-10-10 */ 回忆过去: 系统调用 - UNIX操作系统提供的一些列函数皆苦,用于访问内核空间,遵循posix规范 文件操作:open()\rea ...
- FZU 2082(过路费)
题目链接:传送门 题目大意:中文题,略 题目思路:树链剖分(注意要把边上的权值转移到深度较大的点上来维护) 最后当top[x]==top[y]注意id[x]+1因为是维护的点而题目是边 如果不+可能会 ...
- WEB安全番外第六篇--关于通过记录渗透工具的Payload来总结和学习测试用例
背景: 在WEB安全的学习过程中,了解过了原理之后,就是学习各种Payload,这里面蕴藏着丰富的知识含量,是在基本上覆盖了漏洞原理之后的进一步深入学习的必经之路.无理是Burpsuite还是Sqlm ...
- Egret打包App 修改App名称和图标 (Egret4.1.0)
图标替换位置在项目res下的drawable这些目录下,用新图标覆盖即可. 这里我用白色图片替换了白鹭默认的图片ic_launcher.png 修改App名字,在res->value->s ...
- oracle rank over partition by
转自:https://www.cnblogs.com/wingsless/archive/2012/02/04/2338292.html rank() over(partition)的使用 有的时 ...