Kotlin单例应用程序类

53

在 Android 上,我想将我的应用程序类制作成一个单例

做法如下:

object MyApplication: Application(){}

无法正常工作。运行时会抛出以下错误:

java.lang.IllegalAccessException: private com.... 不能从 android.app.Instrumentation 类访问。

也不可能这样做:

class MyApp: Application() {

    private val instance_: MyApp

    init{
        instance_ = this
    }

    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            Timber.plant(Timber.DebugTree());
        }
    }

    companion object{
        fun getInstance() = instance_
    }
}

如何在应用程序的任何地方获取我的应用程序类的实例?我想使用MyApp.instance()而不是(applicationContext as MyApp)

同时解释一下我想要这样做的原因:我的应用程序中有一些类,例如一个使用上下文初始化的SharedPreference单例。由于它是一个单例,它不能有参数。


请不要静态地访问上下文,这非常糟糕,以后可能会出现问题。相反,在Android中应该按需访问上下文。点此了解详情 - Alessio
4个回答

121

在Kotlin中,你可以像在Java中一样做同样的事情,即将Application实例放入静态字段中。 Kotlin没有静态字段,但是对象中的属性是静态可访问的。

class MyApp: Application() {

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

    companion object {
        lateinit var instance: MyApp
            private set
    }
}

您可以通过 MyApp.instance 访问该属性。


6
底部的 private set 有什么用? - Manohar
3
@Redman 因为我们希望只有MyApp可以设定实例值。 - letroll
3
“lateinit属性未初始化”错误。 - Shlomi Fresko
1
@KirillRakhman 系统将在应用程序启动时创建一个对象,另一个对象将由companion object创建。如果需要,可以像上面那样持有对另一个对象的引用,但是两个对象都必须在应用程序保持活动状态的同时存活。这个推断是否不正确? - Shaishav
1
@Shaishav 伴生对象的类型与外部类不同,您可以在字节码中查找。您将拥有一个MyApp类的实例和一个MyApp$Companion实例,它们都持有对MyApp实例的引用。在此处尝试:https://pl.kotl.in/Wm_sTSSQo - Kirill Rakhman
显示剩余8条评论

50
如果你想使用它来访问一些静态属性,你只需要使用你给这个类的名称即可。不用担心它不是一个实际的单例,你可以以同样的方式使用它。
示例:
class MyApp : Application() {

    companion object {
        const val CONSTANT = 12
        lateinit var typeface: Typeface
    }

    override fun onCreate() {
        super.onCreate()
        typeface = Typeface.createFromAsset(assets, "fonts/myFont.ttf")
    }

}

你可以在应用程序的任何地方使用MyApp.CONSTANTMyApp.typeface

-

如果你想要将其作为应用程序上下文使用,可以为Context创建扩展属性:

val Context.myApp: MyApp
        get() = applicationContext as MyApp

然后你可以使用myApp来获取应用程序上下文,无论您在哪里拥有上下文。


2
这看起来很不错@nitrico,但我无法让´val Context.myApp: MyApp get() = applicationContext as MyApp´这部分工作。你可以再详细解释一下吗?这行代码需要放在哪里并且如何在另一个类中访问它? - shredder
1
“在任何有上下文的地方获取应用程序上下文。” - 你能解释一下为什么如果我有一个上下文,我会需要/想要应用程序上下文吗?似乎如果我有任何类型的上下文,我可能更喜欢使用它。我想要应用程序上下文的用例是当我没有其他上下文时,例如访问资源字符串的实用类。我感到困惑。 - Mitch
在JAVA中不是更简单吗? - TheOnlyAnil
这样做不会创建一个新对象吗?现在虽然你只需要一个,但是会在内存中有两个Application对象。 - Shaishav
@Mitch“你可以解释一下为什么我需要/想要应用上下文,如果我有一个上下文的话?”不同之处在于,如果您使用Activity上下文创建对象并在该Activity不再需要时保留对其引用,那么可能会引起存储泄漏。如果您保留该对象(例如图像资源),则Activity永远不会被GC回收。应用程序在整个应用程序生命周期中存在,因此不会发生这种泄漏。应用程序上下文不应用于布局膨胀等任务。请参见https://medium.com/@banmarkovic/what-is-context-in-android-and-which-one-should-you-use-e1a8c6529652。 - Pavel Lahoda

19
class AppController : Application() {

    init {
        instance = this
    }

    companion object {
        private var instance: AppController? = null

        fun applicationContext() : AppController {
            return instance as AppController
        }
    }

    override fun onCreate() {
        super.onCreate()
        
    }
}

1
谢谢Raja Jawahar,这正是我要找的简明而最佳的解决方案。 - Fred Grott
我认为这是最好的解决方案。如果我们在onCreate()方法中放置instance = this,那么可能会出现一些问题(ContentProvider可能会在Application的onCreate()方法之前调用create)。 - Charon Chui
亲爱的 Raja:我有一个 AppController() 类的代码,其中包含一个字符串,我有一个活动和一个片段。在片段内,我正在调用来自 AppController 的字符串 AppController.mytext2,但我的应用程序崩溃并返回了 AppController$Companion.getMytext2(AppControler.kt:11)。出了什么问题? - C.F.G

3

您不能这样做,因为Android使用其无参数构造函数创建Application实例。

您想要解决的问题可以通过DI轻松解决。只需通过注入器创建实例,以便将Context作为依赖项注入到对象中。


是的,我一直知道那个解决方案,但希望还有其他方法。谢谢啊。 - johnny_crq
请问您能否解答我的疑惑?如果将上下文作为静态变量使用,那么如果依赖类持有其引用,哪个对象不会被垃圾回收:应用程序对象还是上下文变量?如果我们使用 DI,则上下文被传递给依赖类,现在如果它持有该引用,那么对象是否会被垃圾回收?这是两个相关的问题,希望我表述清楚了。谢谢! - ashwin mahajan
实际上,Application 就是 Context,在应用程序运行时它不会被垃圾回收。 - Michael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接