Android深入理解Context–Context使用的误区
本系列分四篇文章详细介绍Context, 这是第四篇
- Android深入理解Context–Application中Context的创建过程
- Android深入理解Context–Activity中Context的创建过程
- Android深入理解Context–Service中Context的创建过程
- Android深入理解Context–Context使用的误区
getApplication()和getApplicationContext()的区别?
-
我们先在我们的Activity中打印信息,代码如下(代码使用的是Kotlin,比较简单)
class MainActivity : AppCompatActivity() { val TAG: String by lazy { MainActivity::class.java.simpleName } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.i(TAG, "" + application) Log.i(TAG, "" + applicationContext) } }
-
结果如下:
MainActivity: com.oman.dependency.MyApplication@841a27MainActivity: com.oman.dependency.MyApplication@841a27
-
我们可以看到
getApplication()
和getApplicationContext()
返回的对象是一样的,地址值都一样。 -
我们现在分析
getApplication()
:public final Application getApplication() { return mApplication;}
-
这里的
mApplication
由前面第二篇 Android深入理解Context 知道,它其实是由ActivityThread.performLaunchActivity
中的makeApplication
创建的,并且由activity.attach(...,application,...)
传递到Activity的。 -
我们接着分析
Activity.getApplicationContext()
(其实在Activity启动流程源码分析中分析过这个), 因为Activity继承ContextWrapper, 发现最终会调用如下:public class ContextWrapper extends Context { @Override public Context getApplicationContext() { return mBase.getApplicationContext(); }}
-
这里的
mBase
,如果你看了之前的几篇文章,很清楚这里的mBase
其实就是ContextImpl,所以我们进入如下:class ContextImpl extends Context { @Override public Context getApplicationContext() { return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication(); }}
-
这里
mPackageInfo
是LoadedApk
, 这里肯定不为null, 因为在ActivityThread.performLaunchActivity
中开始部分,先判断if (r.packageInfo == null)
的话就会给其赋值,而这里的mPackageInfo
就是在创建ContextImpl
的时候从那里传过来的。这样我们就进入到mPackageInfo.getApplication()
中:Application getApplication() { return mApplication;}
-
这里的
mApplication
就是我们在LoadedApk.makeApplication
中创建的application,这样我们通过getApplicationContext()就得到了全局唯一的mApplication
。 -
也就是说
LoadedApk.makeApplication
中创建的application,首先在LoadedApk
中将其赋值给LoadedApk的成员变量mApplication
, 还是同一个application对象通过activity.attach
往activity中存了一份,所以获取的结果自然是一样的(Service中这两个方法获取的值也是一样的
)。 -
那为什么既然结果一样,还要存在两个API供我们使用呢?其实这是因为
getApplication
方法仅仅限于Activity
和Service
中有,其它的类没有,那么如果其它的类想要获取Application的实例的话,这么办呢?就可以通过context.getApplicationContext
来获取了,也就是说这个方法的使用范围更加大,便于使用而已。
Context的使用误区:
-
首先我们在自定义的MyApplication中写如下代码:运行后会crash:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
;class MyApplication : Application() { init { Log.i("aaa", packageName) }}
-
经过调查发现是因为
ContextWrapper
中的mBase
没有初始化导致的。那么mBase
是什么时候初始化的呢,看过前面的文章,我想你应该很清楚了,下面根据源码再具体分析一下:@UnsupportedAppUsage public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } try { java.lang.ClassLoader cl = getClassLoader(); if (!mPackageName.equals("android")) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "initializeJavaContextClassLoader"); initializeJavaContextClassLoader(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); //-----1----- app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { if (!mActivityThread.mInstrumentation.onException(app, e)) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); throw new RuntimeException( "Unable to instantiate application " + appClass + ": " + e.toString(), e); } } mActivityThread.mAllApplications.add(app); mApplication = app; if (instrumentation != null) { try { //-----2----- instrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!instrumentation.onException(app, e)) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } } return app; }
-
上面
1
的代码会进入newApplication
,如下:public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = getFactory(context.getPackageName()) .instantiateApplication(cl, className); app.attach(context); return app;}
-
接着会调用
app.attach
:@UnsupportedAppUsage/* package */ final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;}
-
接着会调用
attachBaseContext(context)
,到这里为mBase赋值了,然后才会调用上面注释2
处的方法,进入application.onCreate()
。protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base;}
-
也就是说之所以上面会抛出异常,肯定是因为我们的
mBase
还没有初始化完成,就开始执行任务,所以想要保证不出问题,就最少要在attachBaseContext
之后调用。class MyApplication : Application() { override fun attachBaseContext(base: Context) { // Log.i("aaa", packageName) 会报错 super.attachBaseContext(base) Log.i("aaa", packageName) }}
关于Application的单例
- 其实从源码中我们就能看到Application是一个全局唯一的存在,如果我们想在应用中获取它的实例,直接像下面一样就可以了, 不能像我们常规的单例模式去判断为空然后new一个对象,那样的话就不具备我们的Context功能,所以这一点一定要注意。
class MyApplication : Application() { override fun onCreate() { super.onCreate() app = this } companion object { private lateinit var app: MyApplication fun getApplication(): MyApplication { return app } }}
好了,到此为止,关于Context一系列就全部讲完了。
更多相关文章
- Android Studio系列(二)使用Android Studio开发/调试整个android系
- Android开发常用代码片段(三)
- Android 性能优化之Java(Android)代码优化 (三)
- Android常用代码之普通及系统权限静默安装APK
- android 常用代码
- 常用的android权限配置和常用工具代码
- Android终于公布源代码
- Android常用代码
- android 蓝牙打印程序源代码