Created
April 24, 2025 11:07
-
-
Save danielesegato/74c1d0c8e14dd5925ec227b89f636827 to your computer and use it in GitHub Desktop.
Accessing the Android Application Context statically - before being horrified, read the comment
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package multiplatform | |
import android.app.Application | |
import android.content.Context | |
import kotlin.reflect.KProperty | |
/** | |
* Hi developer being horrified at this. Please READ ON. This wasn't done lightly. | |
* | |
* There was a post in the long-gone Google+ social by Dianne Hackborn, an Android Framework | |
* engineer. This was the URL: https://plus.google.com/u/0/+AnderWebbs/posts/DsfpW51Vvow | |
* | |
* She said (taken from [this reddit post](https://www.reddit.com/r/androiddev/comments/7zf0pp/where_does_android_community_stands_with_respect/)): | |
* | |
* ================== | |
* | |
* The only reason Application exists as something | |
* you can derive from is because during the pre-1.0 | |
* development one of our application developers was | |
* continually bugging me about needing to have a | |
* top-level application object they can derive from | |
* so they could have a more "normal" to them | |
* application model, and I eventually gave in. | |
* | |
* I will forever regret giving in on that one. :) | |
* | |
* There is no reason to subclass from Application. | |
* It is no different than making a singleton, just | |
* likely to be something you regret in the future | |
* as you find your Application object becoming this | |
* big tangled mess of what should be independent | |
* application logic. | |
* | |
* If what you want is some global state that can be | |
* shared across different parts of your app, use a | |
* singleton. The singleton won't get GCed, because | |
* you have a global static reference on it. And | |
* this leads more naturally to how you should be | |
* managing these things -- initializing them on | |
* demand. | |
* | |
* The framework itself has tons and tons of | |
* singletons for all the little shared data it | |
* maintains for the app, such as caches of loaded | |
* resources, pools of objects, etc. It works great. | |
* | |
* ================== | |
* | |
* They were talking about having global state in an Android application. And not about holding | |
* a static reference to the Application object. | |
* | |
* However, in my opinion, Dianne regrets shouldn't have been making the application extendable. | |
* It should have been not making the application object statically available. | |
* | |
* If you disagree with that opinion you'll disagree with this class. However Dianne confirms that | |
* the Application object is a singleton. All this class does is make it accessible statically. | |
* | |
* It does something more, which is also kind of awful: if it hasn't been explicitly initialized | |
* it try to extract it through reflection from the Android framework actual static singletons | |
* classes. | |
* | |
* That part is what makes it easier but more dangerous to use it. | |
* Those methods are marked with [android.compat.annotation.UnsupportedAppUsage] annotation. | |
* I still suggest you set the application manually. | |
* | |
* I we do not have a static way to access the Application for historically reasons and there's | |
* actually no real reason why that shouldn't be possible. The Android team was afraid making | |
* Application static would cause issues for them down the line and they chose to keep it a class | |
* initialized by the framework that couldn't be accessed statically. Fast forward more than 10 | |
* years there are a lot of applications that made the application accessible even without using | |
* reflection and I strongly doubt this will ever become dangerous or problematic. | |
* | |
* Multi-process will still go through the same initialization process twice, and therefore each | |
* process will have its own Application object and a separate static accessor. | |
* | |
* It might still be a good idea to wrap platform interactions with interfaces so that they can be | |
* mocked out as needed. However forcing to pass the application context everywhere just because the | |
* Android team made a design mistake is kind of stupid in my opinion. Activity / Service / etc | |
* still needs to be passed around because those Context are NOT actually singletons. | |
* | |
* Look at any other platform: iOS, Web, ... no other platform hide access to the OS APIs behind | |
* an object initialized by the framework without giving static access to that object. | |
* | |
* You should still use dependency injection. However you shouldn't be forced to pass an object that | |
* you have no control over to your classes. For instance instead of directly grabbing a | |
* context.fileDir you could pass a File object to your class or hide that behind an interface. | |
* However at some point you'll need an implementation of it and that implementation shouldn't need | |
* to force every part of the DI hierarchy to pass down the Application object. That's my opinion. | |
* | |
* I still believe another huge mistake of the Android framework (which directly relates to this | |
* choice) is that it insist to create instances of every entry point class (Activity, Service, ...) | |
* instead of letting the developer create the objects and providing an API to obtain the context. | |
* This however has nothing to do with this class so I'll stop here :) | |
* | |
* On the implementation: there are two, undocumented, ways of grabbing the Application statically. | |
* They are both in the code, but only one is used, I chose the one that felt more right to me. | |
*/ | |
object StaticAndroidApplicationSingleton { | |
private var application: Application? = null | |
operator fun getValue(thisRef: Any?, property: KProperty<*>): Context { | |
return application | |
?: staticApplicationThroughReflection()?.also { application = it } | |
?: throw IllegalStateException("Application hasn't been initialized") | |
} | |
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Context) { | |
require(value is Application) { "Context must be an Application" } | |
application = value | |
} | |
private fun staticApplicationThroughReflection(): Application? { | |
return staticApplicationThroughAppGlobals() | |
} | |
private fun staticApplicationThroughAppGlobals(): Application? { | |
try { | |
val appGlobals = Class.forName("android.app.AppGlobals") | |
val getInitialApplication = appGlobals.getMethod("getInitialApplication") | |
return getInitialApplication.invoke(null) as Application | |
} catch (e: Exception) { | |
return null | |
} | |
} | |
private fun staticApplicationThroughActivityThread(): Application? { | |
try { | |
val activityThread = Class.forName("android.app.ActivityThread") | |
val currentActivityThread = activityThread.getMethod("currentActivityThread").invoke(null) | |
val application = activityThread.getMethod("getApplication").invoke(currentActivityThread) | |
return application as Application | |
} catch (e: Exception) { | |
return null | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment