本帖最后由 就业部_安卓组 于 2016-11-25 16:05 编辑
有很多人包括我自己有种习惯,就是先存书签以后再看。为了方便各位随时随地,有网没网都可以查看,所以我在文末上传了本文完整附件下载,有需要的同学可以下载到电脑里,方便以后阅览。(快给我个好评)
不同 Context 的分析
Application 中的 Context ActivityThread 源码
[Java] 纯文本查看 复制代码 public static void main(String[] args) {
...
//初始化Looper
Looper.prepareMainLooper();
//创建一个APP主线程ActivityThread对象
ActivityThread thread = newActivityThread();
//初始化App应用信息
thread.attach(false);
//获得主线程也就是UI线程的handler对象
if (sMainThreadHandler == null) {
sMainThreadHandler =thread.getHandler();
}
//Android4.1版本之后添加了这么一个方法,目的就是为了能让AsyncTask能在子线程创建,
//在之前的版本是不能在子线程中创建初始化AsyncTask的。
AsyncTask.init();
//启动Looper循环,进入消息循环。
Looper.loop();
}
main 方法主要创建程序主线程ActivityThread 并初始化,并调用 attach 来初始化
[Java] 纯文本查看 复制代码 private void attach(boolean system) {
//整个应用的Application对象
Application mInitialApplication;
//整个应用的后台管家
Instrumentation mInstrumentation;
...
try {
mInstrumentation = newInstrumentation();
ContextImpl context =ContextImpl.createAppContext(
this,getSystemContext().mPackageInfo);
//利用ContextImpl创建整个应用的Application对象
mInitialApplication =context.mPackageInfo.makeApplication(true, null);
//调用Application对象的onCreate方法
mInitialApplication.onCreate();
}
...
}
context.mPackageInfo.makeApplication(true,null); 创建了应用的 Application 对象。跟进去看看
[Java] 纯文本查看 复制代码 public Application makeApplication(booleanforceDefaultAppClass,
Instrumentationinstrumentation) {
//第一次进来mApplication==null条件不满足,之后创建Activity的时候条件满足直接返回当前Application对象
if (mApplication != null) {
return mApplication;
}
Application app = null;
try {
//为Appliaction创建ContextImpl对象
ContextImpl appContext =ContextImpl.createAppContext(mActivityThread, this);
//调用Instrumentation类中的newApplication方法创建Application
app =mActivityThread.mInstrumentation.newApplication(
cl, appClass,appContext);
//给ContextImpl设置外部引用
appContext.setOuterContext(app);
}
...
return app;
}
通过 Instrumentation 的 newApplication 方法来创建 Application 对象。
查看 newApplication 方法 [Java] 纯文本查看 复制代码
public Application newApplication(ClassLoadercl, String className, Context context)
throwsInstantiationException, IllegalAccessException,
ClassNotFoundException {
returnnewApplication(cl.loadClass(className), context);
}
static public Application newApplication(Class<?>clazz, Context context)
throwsInstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app =(Application)clazz.newInstance();
app.attach(context);
return app;
}
通过类加载器来创建 Application ,并且调用 attach 来初始化。进入 Application 查看 attach
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk =ContextImpl.getImpl(context).mPackageInfo;
}
Application 类是继承自 ContextWrapper 类,在 attach 方法中调ContextWrapper 中的 attachBaseContext 方法来对 ContextWrapper 的成员变量 mBase 赋值,而 mBase 就是 ContextImpl,通过委托机制让 ContextImpl 真正实现了 Context 类中的所有抽象方法。
Activity 中的Context
当 Application 创建完成后,AMS 会通过 Binder 机制通知 ActivityThread 去创建需要的 Activity。最终转到 performLaunchActivity 方法来创建 Activity
[Java] 纯文本查看 复制代码 private Activity performLaunchActivity(ActivityClientRecordr, Intent customIntent) {
...
Activity activity = null;
try {
//通过Instrumentation类的newActivity方法来创建一个Activity对象
java.lang.ClassLoader cl =r.packageInfo.getClassLoader();
activity =mInstrumentation.newActivity(
cl,component.getClassName(), r.intent);
...
}
try {
//获取当前应用的Application对象,该对象的唯一作用就是作为参数传递到Activity里,
然后在Activity类中可以获得调用getApplication方法来获取Application对象
Application app =r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
//为Activity创建ContextImpl对象
Context appContext =createBaseContextForActivity(r, activity);
//为Activity赋值初始化
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app,r.intent, r.activityInfo, title, r.parent,
r.embeddedID,r.lastNonConfigurationInstances, config,
r.voiceInteractor);
...
//获取当前应用的主题资源
int theme =r.activityInfo.getThemeResource();
if (theme != 0) {
//设置主题
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
//辗转到Activity,调用Activity的生命周期onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state,r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
r.activity = activity;
r.stopped = true;
if(!r.activity.mFinished) {
//调用Activity的生命周期onStart方法
activity.performStart();
r.stopped = false;
}
...
return activity;
}
由上可见,是通过 createBaseContextForActivity 来创建 ContextImpl 对象,而又将该对象传递到attach 方法中来对 Activity 进行初始化操作。
[Java] 纯文本查看 复制代码 private Context createBaseContextForActivity(ActivityClientRecordr,
final Activity activity) {
ContextImpl appContext =ContextImpl.createActivityContext(this, r.packageInfo, r.token);
// 让 ContextImpl 持有 Activity 对象的引用
appContext.setOuterContext(activity);
Context baseContext = appContext;
...
return baseContext;
}
appContext.setOuterContext(activity);是为了让 ContextImpl 持有 Activity 的引用,目的是在 ContextImpl 类中注册一些服务,设置主题等。而做这些都需要 Activity 对象的引用。 再来看看 Activity 的 attach 方法
[Java] 纯文本查看 复制代码 public final void attach(Context context,ActivityThread aThread,
Instrumentation instr,IBinder token, int ident,
Application application,Intent intent, ActivityInfo info,
CharSequence title, Activityparent, String id,
NonConfigurationInstanceslastNonConfigurationInstances,
Configuration config,IVoiceInteractor voiceInteractor) {
//调用父类方法对mBase变量赋值
attachBaseContext(context);
//创建一个Activity的窗口
mWindow =PolicyManager.makeNewWindow(this);
//给Window窗口设置回调事件
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//设置键盘弹出状态
if (info.softInputMode !=WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread =Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
//此处注意,将整个应用的Application对象赋值给Activity的mApplication成员变量。
//目的是为了能在Activity中通过getApplication方法来直接获取Application对象
mApplication = application;
...
}
//在Activity中返回当前应用的Application对象
/** Return the application that owns this activity. */
public final Application getApplication(){
return mApplication;
}
}
attach 方法进来就调用了父类的attachBaseContext 方法。
attachBaseContext 里面的代码以及剩下的要做的事在前面的环节中已经分析过了,此处就不再分析了。 结论与上述一样,之所以 Activity 可以调用 Context 里面的任何方法,是因为 mBase ,而 mBase 就是一个 ComtextImpl 对象,车轱辘话我就不来回说了。
Service 中的Context
与 Activity 类似,Service 也是由 AMS 通过 Binder 机制通知 ActivityThread 去创建一个 Service ,最后转到 handleCreateService 方法。 [Java] 纯文本查看 复制代码
private void handleCreateService(CreateServiceDatadata) {
LoadedApk packageInfo =getPackageInfoNoCheck(
data.info.applicationInfo,data.compatInfo);
Service service = null;
try {
//通过类加载器创建Service服务
java.lang.ClassLoader cl =packageInfo.getClassLoader();
service = (Service)cl.loadClass(data.info.name).newInstance();
}
...
try {
...
//此处为Service创建一个ContextImpl对象
ContextImpl context =ContextImpl.createAppContext(this, packageInfo);
//同样为ContextImpl类设置外部对象,目的还是让ContextImpl持有外部类的引用
//在ContextImpl类中的许多方法需要使用到外部Context对象引用
context.setOuterContext(service);
...
//获得当前应用的Applicaton对象,该对象在整个应用中只有一份,是共享的。
Application app =packageInfo.makeApplication(false, mInstrumentation);
//将ContextImpl对象和Application对象作为attach方法参数来初始化Service
service.attach(context, this,data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//Service初始化完成之后系统自动调用onCreate生命周期方法
service.onCreate();
...
}
与上述 Application 和 Activity 类似,也是通过类加载器来创建 Service 对象,然后再创建 ContextImpl 对象,而且也是为 ContextImpl 设置 Service 对象 Context 的引用,其目的也是 ContextImpl 类中的许多方法需要使用外部 Context 引用。 接下来的事情都类似了,跟 Activity 差不多,看一下 attach 方法
[Java] 纯文本查看 复制代码 public final void attach(
Context context,
ActivityThread thread, String className,IBinder token,
Application application,Object activityManager) {
//调用父类方法去注册ContextImpl对象
attachBaseContext(context);
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
//将整个应用的Applicaton对象赋值给Service类的成员变量mApplication
mApplication = application;
mActivityManager =(IActivityManager)activityManager;
}
不做累述,分析与前文相似。
创建 Context 的总结
上述分析了这么多,看了这么多源码,其实有关创建 Context 对象的地方其实都是在 ActivityThread 类中,而 ActivityThread 就是整个应用的入口,也是整个应用的主线程。 每个应用首先会创建一个 Application 对象,且只有一个,然后会根据你的代码和需求创建相应的 Activity 和 Service。并且在创建 Activity 或者 Service 的时候都会持有当前应用的 Application 对象,通过 getApplication 方法就可以拿到。 Context 定义了 N 多方法,都是抽象的。需要子类 Context 的子类去实现,Activity 、Application 、Service 创建的时候都会去创建一个 ContextImpl 对象,最终他们都会通过调用 ContextWrapper 里面的 attachBaseContext 方法给 mBase 赋值,也就是 ContextImpl 对象,所以真正的实现体是 ContextImpl。
getApplication 与getApplicationContext 的区别
有时候会发现 getApplicationContext 可以做的事 getApplication 也能做,他们两个有什么区别呢? 查看 getApplicationContext 的源代码
[Java] 纯文本查看 复制代码
public class ContextWrapper extends Context{
...
@Override
public Context getApplicationContext(){
returnmBase.getApplicationContext();
}
...
}
发现是调用了 mBase 的 getApplicationContext 方法,也就是在 ContextImpl 里面实现,追过去看一下
[Java] 纯文本查看 复制代码 class ContextImpl extends Context {
...
@Override
public Context getApplicationContext(){
return (mPackageInfo != null) ?
mPackageInfo.getApplication(): mMainThread.getApplication();
}
...
}
返回值为mPackageInfo.getApplication() 或者mMainThread.getApplication();
类型是 Context 。 再来看看 getApplication
[Java] 纯文本查看 复制代码
public final Application getApplication() {
return mApplication;
}
返回的是当前应用的 Application 对象 所以:
getApplication 和 getApplicationContext 方法返回的对象都是指向当前应用的Application对象,是同一个Application 对象,仅仅是返回值类型不同而已。 getApplication 方法是在 Activity,Service 类中实现的;
getApplicationContext 方法是在 ContextWrapper 类中实现的。
也就是 getApplication 方法是在 ContextWrapper 子类中实现的,而 getApplicationContext 是在父类中实现的,所以两个方法的使用范围是不一样的。比如说你可以 context.getApplicationContext(); 却不能 context.getApplication(); 除非你这个 Context 是 Activity 或者 Service。
Context 的作用域
虽然Context神通广大,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
上图中的 No 上面有些数字1,2,意思如下:
数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。 数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。 数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个 context 用于使用。
Context 引起的问题
使用 Application 的问题
虽然每个项目里面都有 Application ,而且用法虽然简单,但是常在河边走哪有不湿鞋?有些地方需要注意一下,比如说,我们都知道 Context 定义了很多方法,诸如getString(),getPackageName(),startActivity()等等等等,既然 Application 是 Context 的子类,那我就可以调用getPackageName(),快速测试一下
[Java] 纯文本查看 复制代码
package com.xy.bbs;
import android.app.Application;
/**
* Created by mesmerize on 2016/11/1.
*/
public class TestApplication extends Application {
public TestApplication() {
String packageName = this.getPackageName();
System.out.println("ConstructorpackageName = " + packageName);
}
很简单的一段代码,就是在在构造函数中打印包名,然而运行之后,就崩溃了,log 如下:
[Java] 纯文本查看 复制代码 Caused by: java.lang.NullPointerException:Attempt to invoke virtual method 'java.lang.Stringandroid.content.Context.getPackageName()' on a null object reference
at android.content.ContextWrapper.getPackageName(ContextWrapper.java:132)
at com.xy.bbs.TestApplication.<init>(TestApplication.java:13)
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
atcom.android.tools.fd.runtime.BootstrapApplication.createRealApplication(BootstrapApplication.java:215)
atcom.android.tools.fd.runtime.BootstrapApplication.attachBaseContext(BootstrapApplication.java:239)
at android.app.Application.attach(Application.java:189)
...
NullPointerException,哎!?怎么还空指针了?
这点稍后说,先把代码改成这样再试试:
[Java] 纯文本查看 复制代码 package com.xy.bbs;
import android.app.Application;
/**
* Created by mesmerize on 2016/11/1.
*/
public class TestApplication extends Application {
@Override
public void onCreate() {
String packageName = this.getPackageName();
System.out.println("onCreate()packageName = " + packageName);
}
}
然后一切正常,正确打印包名。 这是为什么呢? 之前在谈 ContextWrapper 的时候讲过给 Context 赋值是在 attachBaseContext() 这个方法中赋值,赋值给 mBase ,并且里面所定义的所有方法全是通过 mBase 这个对象来调用的,试想一下,如果 mBase 为 null,调用其中定义的方法时会怎样?肯定就空指针了啊! 所以,这也就引出了 Applicatioin 方法的执行顺序 先走 constructor => 然后 attachBaseContext() => 最后 onCreate() 因此我们在 onCreate() 里面调用 ContextWrapper 定义的方法就不会有问题,而在构造函数中调用就会空指针。 这是一个问题。
当然你也可以重写 attachBaseContext() 这个方法,但是要注意要在 super.attachBaseContext(base); 之后调用,不能在之前。 还有一种空指针的情况,虽说 Google 不建议我们自定义 Application,更推崇使用单例,但是身边的人几乎都是在用自定义 Application,不谈这个。 有些人写单例习惯了,有时候可能写出这样的代码:
[Java] 纯文本查看 复制代码 public class TestApplication extends Application{
private static TestApplicationapplicatioin;
public static TestApplication getInstance(){
if (applicatioin == null) {
applicatioin = newTestApplication();
}
return applicatioin;
}
}
有问题吗?
肯定是有问题的! Application 是系统的组件,系统组件就要让系统自己去创建,这样new出来,就只是一个普通的 java 对象,并不具备 Context 的能力,拿这样一个对象去调用 Context 的方法,还是空指针。 正确写法如下:
[Java] 纯文本查看 复制代码 public class TestApplication extends Application{
private static TestApplicationapplication;
public static TestApplication getInstance(){
return application;
}
@Override
public void onCreate() {
super.onCreate();
application = this;
}
}
浅显易懂,我就不多做解释了。
内存泄露的问题
首先看一段代码:
[Java] 纯文本查看 复制代码 public class CustomManager {
private static CustomManagersInstance;
public static CustomManager getInstance(Contextcontext) {
if (sInstance == null) {
sInstance = newCustomManager(context);
}
return sInstance;
}
private Context mContext;
private CustomManager(Contextcontext) {
mContext = context;
}
}
这就是一个很普通的单例,我们在项目中经常会编写一些工具类等,可能会写成单例的模式,比如上述代码。这就可能造成一些问题。 可以看到上述代码中需要一个 Context,由于工具类大多需要访问资源,所以需要 Context ,这无可厚非,但是有一种可能,就是 Context 的类型。如果是在 Activity 中或者是 Service 中呢? sInstance 可是一个静态的强引用的啊!而我们都知道static 的生命周期是伴随整个项目的生命周期而存活的,静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放static静态变量的内存。如果你用 Activity 作为 Context传了进来。那也就是说,此 Activity 会一直活下去,直到项目退出。否则的话,这个 Activity 是没办法被回收的。而 Activity 生命周期肯定没这么长,所以就造成了内存泄露了! 所以,遇到这种问题的时候,尽可能改成跟整个应用的生命周期一样的 Context,比如 ApplicationContext。 改动代码如下:
[Java] 纯文本查看 复制代码 public class CustomManager {
private static CustomManagersInstance;
public static CustomManager getInstance(Contextcontext) {
if (sInstance == null) {
//Always pass in theApplication Context
sInstance = newCustomManager(context.getApplicationContext());
}
return sInstance;
}
private Context mContext;
private CustomManager(Contextcontext) {
mContext = context;
}
}
即可。 再来看看另一种 View 持有引用导致的内存泄露
[Java] 纯文本查看 复制代码 public class CustomResource {
//静态变量drawable
private static Drawable mDrawable;
private View view;
public CustomResource(Contextcontext) {
Resources resources =context.getResources();
mDrawable =resources.getDrawable(R.drawable.ic_launcher);
view = new View(context);
view.setBackgroundDrawable(mDrawable);
}
}
这段代码也会内存泄露,查阅 setBackgroundDrawable() 的源码可以看到:
[Java] 纯文本查看 复制代码 publicvoid setBackgroundDrawable(Drawable background) {
...
/**此处的this就是当前View对象*/
// Set callback last, since theview may still be initializing.
background.setCallback(this);
...
}
而View对象哪来的?是通过Context对象获得的。
因此,变量 background 持有View对象的引用,View持有Context的引用,所有background间接持有Context对象的引用了。这也就意味着,如果该 Context 对应的 Activity 退出 finish 掉的时候,该 Activity 是不能完全释放的,因为 static 的 drawable 持有该 Activity 的 Context 对象的间接引用,从而导致该 Activity 内存无法回收,导致内存泄露隐患。 但是起因是由于 static drawable 持有 View 对象的引用导致内存泄露的,并不是由于 context.getResource 导致的,因此并不能用 context.getApplicationContext 来解决。Android 3.0之后修改了setBackgroundDrawable 方法的 setCallback(this)方法,里面实现使用了弱引用来持有 View 对象的引用,从而避免了内存泄露隐患。 所以,尽量避免使用静态资源,或者直接使用弱引用来解决也是可以的。 一般而言,由 Context 所造成的内存泄露,几乎都是当 Context 销毁的时候,却由于被引用而导致销毁失败,所以我们应该记住正确使用 Context 的姿势: 当 Application 的 Context 能搞定的情况下,并且生命周期长的对象,优先使用 Application 的Context。 不要让生命周期长于 Activity 的对象持有 Activity 的引用。 尽量不要在 Activity 中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,而静态内部类不会持有外部类的引用,外部类会以正常的方式回收。如果静态内部类想使用外部类的属性或方法,可以将外部实例引用作为弱引用持有。
|