哥白尼·罗斯福·马丁路德·李开复·嫁衣曾经说过java
Where there is an Android App, there is an Application context.android
没毛病,扎心了。App运行的时候,确定是存在至少一个Application实例的。同时,Context咱们再熟悉不过了,写代码的时候常常须要使用到Context实例,它通常是经过构造方法传递进来,经过方法的形式参数传递进来,或者是经过attach方法传递进咱们须要用到的类。Context实在是过重要了,以致于我常常巴不得着藏着掖着,随身带着,这样须要用到的时候就能马上掏出来用用。可是换个角度想一想,既然App运行的时候,Application实例老是存在的,那么为什么不设置一个全局能够访问的静态方法用于获取Context实例,这样以来就不须要上面那些繁琐的传递方式。git
说到这里,有的人可能说想这不是咱们常常干的好事吗,有必要说的这么玄乎?少侠莫急,请听吾辈徐徐道来。github
这再简单不过了。app
public static class Foo1 { public Foo1(Context context) { // 1. 在构造方法带入 } } public static class Foo2 { public Foo2 attach(Context context) { // 2. 经过attach方法带入 return this; } } public static class Foo2 { public void foo(Context context) { // 3. 调用方法的时候,经过形参带入 } }
这种方式应该是最多见的获取Context实例的方式了,优势就是严格按照代码规范来,不用担忧兼容性问题;缺点就是API设计严重依赖于Context这个API,若是早期接口设计不严谨,后期代码重构的时候可能很要命。此外还有一个比较有趣的问题,咱们常用Activity或者Application类的实例做为Context的实例使用,而前者自己又实现了别的接口,好比如下代码。异步
public static class FooActivity extends Activity implements FooA, FooB, FooC { Foo mFoo; public void onCreate(Bundle bundle) { // 禁忌·四重存在! mFoo.foo(this, this, this, this); } ... } public static class Foo { public void foo(Context context, FooA a, FooB b, FooC c) { ... } }
这段代码是我许久前看过的代码,自己不是什么厉害的东西,不过这段代码段我至今印象深入。设想,若是Foo的接口设计能够不用依赖Context,那么这里至少能够少一个this
不是吗。ide
如今许多开发者喜欢设计一个全局能够访问的静态方法,这样以来在设计API的时候,就不须要依赖Context了,代码看起来像是这样的。oop
/* * 全局获取Context实例的静态方法。 */ public static class Foo { private static sContext; public static Context getContext() { return sContext; } public static void setContext(Context context) { sContext = context; } }
这样在整个项目中,均可以经过Foo#getContext()
获取Context实例了。不过目前看起来好像还有点小缺陷,就是使用前须要调用Foo#setContext(Context)
方法进行注册(这里暂不讨论静态Context实例带来的问题,这不是本篇幅的关注点)。好吧,以个人聪明才智,很快就想到了优化方案。测试
/* * 全局获取Context实例的静态方法(改进版)。 */ public static class FooApplication extends Application { private static sContext; public FooApplication() { sContext = this; } public static Context getContext() { return sContext; } }
不过这样又有带来了另外一个问题,通常状况下,咱们是把应用的入口程序类FooApplication
放在App模块下的,这样一来,Library模块里面代码就访问不到FooApplication#getContext()
了。固然把FooApplication
下移到基础库里面也是一种办法,不过以个人聪明才智又马上想到了个好点子。优化
/* * 全局获取Context实例的静态方法(改进版之再改进)。 */ public static class FooApplication extends BaseApplication { ... } /* * 基础库里面 */ public static class BaseApplication extends Application { private static sContext; public BaseApplication() { sContext = this; } public static Context getContext() { return sContext; } }
这样以来,就不用把FooApplication
下移到基础库里面,Library模块里面的代码也能够经过BaseApplication#getContext()
访问到Context实例了。嗯,这看起来彷佛是一种神奇的膜法,因吹斯听。然而,代码写完还没来得及提交,包工头打了个电话来和我说,因为项目接入了第三发SDK,须要把FooApplication
继承SdkApplication
。
…… 有没有什么办法能让FooApplication
同时继承BaseApplication
和SdkApplication
啊?(场面一度很尴尬,这里省略一万字。)
以上谈到的,都是之前咱们在获取Context实例的时候遇到的一些麻烦:
类API设计须要依赖Context(这是一种好习惯,我可没说这很差);
持有静态的Context实例容易引起的内存泄露问题;
须要提注册Context实例(或者释放);
污染程序的Application类;
那么,有没有一种方式,可以让咱们在整个项目中能够全局访问到Context实例,不要提早注册,不会污染Application类,更加不会引起静态Context实例带来的内存泄露呢?
回到最开始的话,App运行的时候,确定存在至少一个Application实例。若是咱们可以在系统建立这个实例的时候,获取这个实例的应用,是否是就能够全局获取Context实例了(由于这个实例是运行时一直存在的,因此也就不用担忧静态Context实例带来的问题)。那么问题来了,Application实例是何时建立的呢?首先先来看看咱们常常用来获取Base Context实例的Application#attachBaseContext(Context)
方法,它是继承自ContextWrapper#attachBaseContext(Context)
的。
public class ContextWrapper extends Context { protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } }
是谁调用了这个方法呢?能够很快定位到Application#attach(Context)
。
public class Application extends ContextWrapper { final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; } }
又是谁调用了Application#attach(Context)
方法呢?一路下来能够直接定位到Instrumentation#newApplication(Class<?>, Context)
方法里(这个方法名很好懂啊,一看就知道是干啥的)。
/** * Base class for implementing application instrumentation code. When running * with instrumentation turned on, this class will be instantiated for you * before any of the application code, allowing you to monitor all of the * interaction the system has with the application. An Instrumentation * implementation is described to the system through an AndroidManifest.xml's * <instrumentation>. */ public class Instrumentation { static public Application newApplication(Class<?> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); return app; } }
看来是在这里建立了App的入口Application类实例的,是否是想办法获取到这个实例的应用就能够了?不,还别高兴太早。咱们能够把Application实例当作Context实例使用,是由于它持有了一个Context实例(base),实际上Application实例都是经过代理调用这个base实例的接口完成相应的Context工做的。在上面的代码中,能够看到系统建立了Application实例app后,经过app.attach(context)
把context实例设置给了app。直觉告诉咱们,应该进一步关注这个context实例是怎么建立的,能够定位到LoadedApk#makeApplication(boolean, Instrumentation)
代码段里。
/** * Local state maintained about a currently loaded .apk. * @hide */ public final class LoadedApk { public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } Application app = null; String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } 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); } // Context 实例建立的地方,能够看出Context实例是一个ContextImpl。 ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { } ... return app; } }
好了,到这里咱们定位到了Application实例和Context实例建立的位置,不过距离咱们的目标只成功了一半。由于若是咱们要想办法获取这些实例,就得先知道这些实例被保存在什么地方。上面的代码一路逆向追踪过来,好像也没看见实例被保存给成员变量或者静态变量,因此暂时还得继续往上捋。很快就能捋到ActivityThread#performLaunchActivity(ActivityClientRecord, Intent)
。
/** * This manages the execution of the main thread in an * application process, scheduling and executing activities, * broadcasts, and other operations on it as the activity * manager requests. */ public final class ActivityThread { private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... ActivityInfo aInfo = r.activityInfo; ComponentName component = r.intent.getComponent(); Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); r.intent.prepareToEnterProcess(); if (r.state != null) { r.state.setClassLoader(cl); } } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { // 建立Application实例。 Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (activity != null) { ... } r.paused = true; mActivities.put(r.token, r); } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to start activity " + component + ": " + e.toString(), e); } } return activity; } }
这里是咱们启动Activity的时候,Activity实例建立的具体位置,以上代码段还能够看到喜闻乐见的"Unable to start activity"异常,大家猜猜这个异常是谁抛出来的?这里就不发散了,回到咱们的问题来,以上代码段获取了一个Application实例,可是并无保持住,看起来这里的Application实例就像是一个临时变量。没办法,再看看其余地方吧。接着找到ActivityThread#handleCreateService(CreateServiceData)
,不过这里也同样,并无把获取的Application实例保存起来,这样咱们就没有办法获取到这个实例了。
public final class ActivityThread { private void attach(boolean system) { sCurrentActivityThread = this; mSystemThread = system; if (!system) { ... } else { // Don't set application object here -- if the system crashes, // we can't display an alert, we just want to die die die. android.ddm.DdmHandleAppName.setAppName("system_process", UserHandle.myUserId()); try { mInstrumentation = new Instrumentation(); ContextImpl context = ContextImpl.createAppContext( this, getSystemContext().mPackageInfo); mInitialApplication = context.mPackageInfo.makeApplication(true, null); mInitialApplication.onCreate(); } catch (Exception e) { throw new RuntimeException( "Unable to instantiate Application():" + e.toString(), e); } } ... } public static ActivityThread systemMain() { ... ActivityThread thread = new ActivityThread(); thread.attach(true); return thread; } public static void main(String[] args) { ... ActivityThread thread = new ActivityThread(); thread.attach(false); ... } }
咱们能够看到,这里建立Application实例后,把实例保存在ActivityThread的成员变量mInitialApplication
中。不过仔细一看,只有当system == true
的时候(也就是系统应用)才会走这个逻辑,因此这里的代码也不是咱们要找的。不过,这里给咱们一个提示,若是能想办法获取到ActivityThread实例,或许就能直接拿到咱们要的Application实例。此外,这里还把ActivityThread的实例赋值给一个静态变量sCurrentActivityThread
,静态变量正是咱们获取系统隐藏API实例的切入点,因此若是咱们能肯定ActivityThread的mInitialApplication
正是咱们要找的Application实例的话,那就大功告成了。继续查找到ActivityThread#handleBindApplication(AppBindData)
,光从名字咱们就能猜出这个方法是干什么的,直觉告诉咱们离目标不远了~
public final class ActivityThread { private void handleBindApplication(AppBindData data) { ... try { Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; try { mInstrumentation.onCreate(data.instrumentationArgs); } catch (Exception e) { throw new RuntimeException( "Exception thrown in onCreate() of " + data.instrumentationName + ": " + e.toString(), e); } try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } } } }
咱们看到这里一样把Application实例保存在ActivityThread的成员变量mInitialApplication
中,紧接着咱们看看谁是调用了handleBindApplication
方法,很快就能定位到ActivityThread.H#handleMessage(Message)
里面。
public final class ActivityThread { public final void bindApplication(String processName, ApplicationInfo appInfo, List<ProviderInfo> providers, ComponentName instrumentationName, ProfilerInfo profilerInfo, Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, IUiAutomationConnection instrumentationUiConnection, int debugMode, boolean enableBinderTracking, boolean trackAllocation, boolean isRestrictedBackupMode, boolean persistent, Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) { ... sendMessage(H.BIND_APPLICATION, data); } private class H extends Handler { public void handleMessage(Message msg) { switch (msg.what) { ... case BIND_APPLICATION: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); AppBindData data = (AppBindData)msg.obj; handleBindApplication(data); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case EXIT_APPLICATION: if (mInitialApplication != null) { mInitialApplication.onTerminate(); } Looper.myLooper().quit(); break; ... } } } }
Bingo!至此一切都清晰了,ActivityThread#mInitialApplication
确实就是咱们须要找的Application实例。整个流程捋顺下来,系统建立Base Context实例、Application实例,以及把Base Context实例attach到Application内部的流程大体能够概括为如下调用顺序。
ActivityThread#bindApplication (异步) --> ActivityThread#handleBindApplication --> LoadedApk#makeApplication --> Instrumentation#newApplication --> Application#attach --> ContextWrapper#attachBaseContext
源码撸完了,再回到咱们一开始的需求来。如今咱们要获取ActivityThread的静态成员变量sCurrentActivityThread。阅读源码后咱们发现能够经过ActivityThread#currentActivityThread()
这个静态方法来获取这个静态对象,而后经过ActivityThread#getApplication()
方法就可能直接获取咱们须要的Application实例了。啊,这用反射搞起来简直再简单不过了!说搞就搞。
public class Applications { @NonNull public static Application context() { return CURRENT; } @SuppressLint("StaticFieldLeak") private static final Application CURRENT; static { try { Object activityThread = getActivityThread(); Object app = activityThread.getClass().getMethod("getApplication").invoke(activityThread); CURRENT = (Application) app; } catch (Throwable e) { throw new IllegalStateException("Can not access Application context by magic code, boom!", e); } } private static Object getActivityThread() { Object activityThread = null; try { Method method = Class.forName("android.app.ActivityThread").getMethod("currentActivityThread"); method.setAccessible(true); activityThread = method.invoke(null); } catch (final Exception e) { Log.w(TAG, e); } return activityThread; } } // 测试代码 @RunWith(AndroidJUnit4.class) public class ApplicationTest { public static final String TAG = "ApplicationTest"; @Test public void testGetGlobalContext() { Application context = Applications.context(); Assert.assertNotNull(context); Log.i(TAG, String.valueOf(context)); // MyApplication是项目的自定义Application类 Assert.assertTrue(context instanceof MyApplication); } }
这样以来, 不管在项目的什么地方,不管是在App模块仍是Library模块,均可以经过Applications#context()
获取Context实例,并且不须要作任何初始化工做,也不用担忧静态Context实例带来的问题,测试代码跑起来没问题,接入项目后也没有发现什么异常,咱们简直要上天了。不对,哪里不对。不科学,通常来讲不可能这么顺利的,这必定是错觉。果真项目上线没多久后马上原地爆炸了,在一些机型上,经过Applications#context()
获取到的Context恒为null。
(╯>д<)╯⁽˙³˙⁾ 对嘛,这才科学嘛。
经过测试发现,在4.1.1系统的机型上,会稳定出现获取结果为null的现象,看来是系统源码的实现上有一些出入致使,总之先看看源码吧。
public final class ActivityThread { public static ActivityThread currentActivityThread() { return sThreadLocal.get(); } private void attach(boolean system) { sThreadLocal.set(this); ... } }
原来是这么一个幺蛾子,在4.1.1系统上,ActivityThread是使用一个ThreadLocal实例来存放静态ActivityThread实例的。至于ThreadLocal是干什么用的这里暂不展开,简单说来,就是系统只有在UI线程使用sThreadLocal来保存静态ActivityThread实例,因此咱们只能在UI线程经过sThreadLocal获取到这个保存的实例,在Worker线程sThreadLocal会直接返回空。
这样以来解决方案也很明朗,只须要在事先如今UI线程触发一次Applications#context()
调用保存Application实例便可。不过项目的代码一直在变化,咱们很难保证不会有谁不当心触发了一次优先的Worker线程的调用,那就GG了,因此最好在Applications#context()
方法里处理,咱们只须要确保能在Worker线程得到ActivityThread实例就Okay了。不过一时半会我想不出切确的办法,也找不到适合的切入点,只作了下简单的处理:若是是优先在Worker线程调用,就先使用UI线程的Handler提交一个任务去获取Context实例,Worker线程等待UI线程获取完Context实例,再接着返回这个实例。
最终完成的代码能够参考 Applications。
(补充 2017-04-13)
在这里须要特别强调的时候,经过这样的方法获取Context实例,只要在Application#attachBaseContext(Context)
执行以后才能获取到对象,在以前或者以内获取到的对象都是null,具体缘由能够参考上面调用流程中的ActivityThread#handleBindApplication
。因此,膜法什么的,仍是少用为妙吧。