【问题标题】:Singletons vs. Application Context in Android?Android中的单例与应用程序上下文?
【发布时间】:2010-09-30 00:35:37
【问题描述】:

回忆这个post enumerating several problems of using singletons 并且已经看到了几个使用单例模式的 Android 应用程序示例,我想知道使用单例而不是通过全局应用程序状态共享的单个实例是否是一个好主意(子类化 android.os.Application 并通过 context.getApplication() 获取它)。

这两种机制有什么优点/缺点?

老实说,我希望这篇文章Singleton pattern with Web application, Not a good idea! 得到相同的答案,但适用于 Android。我对么? DalvikVM 有什么不同?

编辑:我想就所涉及的几个方面发表意见:

  • 同步
  • 可重用性
  • 测试

【问题讨论】:

    标签: java android design-patterns singleton


    【解决方案1】:

    我非常不同意 Dianne Hackborn 的回应。我们一点一点地从我们的项目中删除所有单例,以支持轻量级、任务范围的对象,这些对象可以在您实际需要时轻松地重新创建。

    单例是测试的噩梦,如果延迟初始化,将引入带有微妙副作用的“状态不确定性”(当将调用 getInstance() 从一个范围转移到另一个范围时,可能会突然出现) .可见性是另一个问题,由于单例意味着“全局”(=随机)访问共享状态,因此在并发应用程序中未正确同步时可能会出现细微的错误。

    我认为它是一种反模式,它是一种糟糕的面向对象风格,本质上相当于维护全局状态。

    回到你的问题:

    虽然应用上下文本身可以被认为是一个单例,但它是由框架管理的,并且具有明确定义的生命周期、范围和访问路径。因此,我相信如果你确实需要管理应用程序全局状态,它应该去这里,别无他处。对于其他任何事情,请重新考虑您是否真的需要一个单例对象,或者是否也可以重写您的单例类来实例化执行手头任务的小型、短暂的对象。 p>

    【讨论】:

    • 如果你推荐应用程序,你推荐使用单例。老实说,没有办法解决它。应用程序单例,具有更糟糕的语义。我不会就你不应该使用的单身人士进行宗教争论。我更喜欢实用——在某些地方,它们是维护每个进程状态的好选择,并且可以通过这样做来简化事情,你也可以在错误的情况下使用它们,然后自取其辱。
    • 是的,我确实提到了“应用程序上下文本身可以被认为是一个单例”。不同之处在于,对于应用程序实例,要害死自己要困难得多,因为它的生命周期是由框架处理的。 Guice、Hivemind 或 Spring 等 DI 框架也使用单例,但这是开发人员不应该关心的实现细节。我认为依靠正确实现的框架语义而不是您自己的代码通常更安全。是的,我承认我有! :-)
    • 老实说,它不会像单身人士那样阻止你在脚上开枪。这有点令人困惑,但应用程序没有 no 生命周期。它是在您的应用程序启动时创建的(在它的任何组件被实例化之前)并且它的 onCreate() 在那个时候被调用,并且......就是这样。它坐在那里并永远存在,直到进程被杀死。就像一个单身人士。 :)
    • 哦,有件事可能会让人困惑——Android 的设计主要围绕在进程中运行应用程序以及管理这些进程的生命周期。因此,在 Android 上,单例是利用该进程管理的一种非常自然的方式——如果您想在进程中缓存某些内容,直到平台需要为其他内容回收进程的内存,则将该状态放入单例中即可。
    • 好吧,很公平。我只能说,自从我们摆脱了自我管理的单身人士之后,我就再也没有回头。我们现在选择了一种轻量级的 DI 风格的解决方案,我们确实保留了一个工厂单例 (RootFactory),而它又由应用程序实例管理(如果你愿意,它是一个委托)。这个单例管理所有应用程序组件所依赖的公共依赖项,但实例化在一个位置进行管理——应用程序类。虽然使用这种方法只剩下一个单例,但它仅限于 Application 类,因此没有其他代码模块知道该“细节”。
    【解决方案2】:

    我非常推荐单身人士。如果你有一个需要上下文的单例,有:

    MySingleton.getInstance(Context c) {
        //
        // ... needing to create ...
        sInstance = new MySingleton(c.getApplicationContext());
    }
    

    我更喜欢单例而不是应用程序,因为它有助于使应用程序更有条理和模块化——而不是在一个地方需要维护整个应用程序的所有全局状态,每个单独的部分都可以自行处理。此外,单例延迟初始化(根据请求)而不是引导您在 Application.onCreate() 中预先进行所有初始化这一事实也很好。

    使用单例没有本质上的错误。在有意义的时候正确使用它们。 Android 框架实际上有很多,用于维护加载资源的每个进程的缓存和其他类似的东西。

    对于简单的应用程序,多线程也不会成为单例的问题,因为根据设计,应用程序的所有标准回调都在进程的主线程上分派,因此除非您明确引入多线程,否则不会发生多线程通过线程或通过将内容提供者或服务 IBinder 发布到其他进程隐式地发布。

    请仔细考虑您正在做的事情。 :)

    【讨论】:

    • 如果一段时间后我想收听外部事件,或在 IBinder 上共享(我想这不是一个简单的应用程序)我将不得不添加双重锁定、同步、易失性、正确的?谢谢你的回答:)
    • 不适用于外部事件——BroadcastReceiver.onReceive() 也会在主线程上调用。
    • 好的。你能指出我可以看到主线程调度机制的一些阅读材料(我更喜欢代码)吗?我认为这将为我一次澄清几个概念。提前致谢。
    • 这是主要的应用端调度代码:android.git.kernel.org/?p=platform/frameworks/…
    • 使用单例没有本质上的错误。只要在有意义的时候正确使用它们。 .. 当然,准确地说,说得好。 Android 框架实际上有很多,用于维护加载资源的每个进程的缓存和其他类似的东西。 就像你说的那样。来自您在 iOS 世界中的朋友,iOS 中的“一切都是单例”.. 在物理设备上,没有什么比单例概念更自然的了:gps、时钟、陀螺仪等 - 从概念上讲,您还能如何设计这些作为单身人士?所以是的。
    【解决方案3】:

    发件人:Developer > reference - Application

    通常不需要子类化应用程序。在大多数情况下, 静态单例可以以更模块化的方式提供相同的功能 大大地。如果您的单例需要全局上下文(例如注册 广播接收器),检索它的函数可以给出 上下文在内部使用 Context.getApplicationContext() 时 首先构造单例。

    【讨论】:

    • 如果你为单例写一个接口,让getInstance保持非静态,你甚至可以让单例使用类的默认构造函数通过非默认构造函数注入生产单例,即也是您用于在其单元测试中创建使用单例的类的构造函数。
    【解决方案4】:

    应用和Singleton不一样。原因是:

    1. 在ui线程中调用应用程序的方法(如onCreate);
    2. 单例的方法可以在任意线程中调用;
    3. 在Application的onCreate方法中,可以实例化Handler;
    4. 如果单例在非ui线程中执行,你不能 实例化处理程序;
    5. 应用程序具有管理生命周期的能力 应用程序中的活动。它有方法 “registerActivityLifecycleCallbacks”。但是单身人士没有 能力。

    【讨论】:

    • 注意:你可以在任何线程上实例化 Handler。来自文档:“当你创建一个新的 Handler 时,它会绑定到创建它的线程的线程/消息队列”
    • @Christ 谢谢!刚刚学习了“Looper的机制”。如果在没有代码'Looper.prepare()'的none-ui线程上实例化handler,系统会报错“java.lang.RuntimeException:无法在未调用 Looper.prepare() 的线程内创建处理程序”。
    【解决方案5】:

    我也遇到了同样的问题:Singleton 还是创建一个子类 android.os.Application?

    首先我尝试使用 Singleton,但我的应用有时会调用浏览器

    Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));
    

    问题是,如果手机没有足够的内存,你的大部分类(甚至单例)都会被清理以获取一些内存,所以当从浏览器返回到我的应用程序时,它每次都会崩溃。

    解决方案:将所需的数据放在 Application 类的子类中。

    【讨论】:

    • 我经常看到人们说这可能会发生的帖子。因此,我只是简单地将对象附加到应用程序中,例如延迟加载的单例等,以确保生命周期已记录并已知。请确保不要将数百张图像保存到您的应用程序对象中,因为据我所知,如果您的应用程序在后台并且所有活动都被销毁以释放内存以供其他进程使用,它将不会从内存中清除。
    • 好吧,应用重启后的Singleton延迟加载并不是让对象被GC扫到的正确方式。弱引用是吧?
    • 真的吗? Dalvik 卸载类并丢失程序状态?你确定它不是在收集那些你不应该放在单例中的生命周期有限的 Activity 相关对象吗?你必须为这样一个非凡的主张举出清晰的例子!
    • 除非有我不知道的变化,Dalvik 确实卸载类。曾经。他们看到的行为是他们的进程在后台被杀死以为浏览器腾出空间。他们可能在他们的“主要”活动中初始化变量,当从浏览器返回时,它可能没有在新进程中创建。
    【解决方案6】:

    同时考虑两者:

    • 将单例对象作为类中的静态实例。
    • 拥有一个公共类 (Context),它返回应用程序中所有单例对象的单例实例,其优点是 Context 中的方法名称将是有意义的,例如:context.getLoggedinUser() 而不是 User.getInstance ()。

    此外,我建议您扩展 Context 以不仅包括对单例对象的访问,还包括一些需要全局访问的功能,例如:context.logOffUser()、context.readSavedData() 等。可能重命名Context to Facade 将是有意义的。

    【讨论】:

      【解决方案7】:

      它们实际上是一样的。 我可以看到一个区别。使用 Application 类,您可以在 Application.onCreate() 中初始化变量并在 Application.onTerminate() 中销毁它们。使用单例,您必须依赖 VM 初始化和销毁​​静态数据。

      【讨论】:

      【解决方案8】:

      出自马的口中……

      在开发您的应用时,您可能会发现有必要在您的应用中全球共享数据、上下文或服务。例如,如果您的应用具有会话数据,例如当前登录的用户,您可能希望公开此信息。在 Android 中,解决此问题的模式是让您的 android.app.Application 实例拥有所有全局数据,然后将您的 Application 实例视为具有各种数据和服务的静态访问器的单例。

      在编写 Android 应用程序时,保证您只有一个 android.app.Application 类的实例,因此将其视为单例是安全的(并且 Google Android 团队建议)。也就是说,您可以安全地将静态 getInstance() 方法添加到您的应用程序实现中。像这样:

      public class AndroidApplication extends Application {
      
          private static AndroidApplication sInstance;
      
          public static AndroidApplication getInstance(){
              return sInstance;
          }
      
          @Override
          public void onCreate() {
              super.onCreate();
              sInstance = this;
          }
      }
      

      【讨论】:

        【解决方案9】:

        我的 2 美分:

        我确实注意到,当我的活动被销毁时,一些单例/静态字段被重置。我在一些低端 2.3 设备上注意到了这一点。

        我的情况很简单:我只有一个私有文件“init_done”和一个从activity.onCreate() 调用的静态方法“init”。我注意到方法 init 在重新创建活动时重新执行自身。

        虽然我无法证明我的肯定,但这可能与单例/类首先创建/使用的时间有关。当活动被销毁/回收时,似乎只有这个活动引用的所有类也被回收了。

        我将我的单例实例移动到应用程序的子类中。我从应用程序实例访问它们。并且,从那以后,再也没有注意到这个问题。

        我希望这可以帮助某人。

        【讨论】:

          【解决方案10】:

          我的活动调用了 finish()(它不会立即完成,但最终会完成)并调用 Google Street Viewer。当我在 Eclipse 上调试它时,调用 Street Viewer 时我与应用程序的连接中断,我理解为(整个)应用程序被关闭,据说是为了释放内存(因为完成的单个活动不应该导致这种行为) .不过,我可以通过 onSaveInstanceState() 将状态保存在 Bundle 中,并在堆栈中下一个活动的 onCreate() 方法中恢复它。通过使用静态单例或子类化应用程序,我将面临应用程序关闭和丢失状态(除非我将其保存在 Bundle 中)。因此,根据我的经验,它们在国家保护方面是相同的。我注意到在 Android 4.1.2 和 4.2.2 中连接丢失,但在 4.0.7 或 3.2.4 中没有,据我了解,这表明内存恢复机制在某些时候发生了变化。

          【讨论】:

          • "我注意到在 Android 4.1.2 和 4.2.2 中连接丢失,但在 4.0.7 或 3.2.4 中没有,这在我的理解中表明内存恢复机制在某些方面发生了变化观点。” .....我认为您的设备没有相同数量的可用内存,也没有安装相同的应用程序。因此你的结论可能不正确
          • @Christ:是的,你一定是对的。如果内存恢复机制在版本之间发生变化,那就很奇怪了。可能是不同的内存使用导致了不同的行为。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-29
          • 1970-01-01
          • 2015-08-31
          • 1970-01-01
          • 1970-01-01
          • 2014-06-24
          相关资源
          最近更新 更多