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@841a27
    MainActivity: 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();
        }
    }
    
  • 这里mPackageInfoLoadedApk, 这里肯定不为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方法仅仅限于ActivityService中有,其它的类没有,那么如果其它的类想要获取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一系列就全部讲完了。

你可能感兴趣的:(源码分析)