回顾这篇文章列举了使用单例的几个问题在看到了几个使用单例模式的Android应用程序的例子之后,我想知道使用单例模式而不是通过全局应用程序状态(对android.os.application进行子类化,并通过context.getapplication()获取)共享的单个实例是否是个好主意。
两种机制都有哪些优点/缺点?
老实说,我希望在这个后单例模式中,Web应用程序也能得到同样的答案,这不是一个好主意!但适用于Android。我说的对吗?Dalvikvm有什么不同?
编辑:我想就涉及的几个方面发表意见:
我非常不同意黛安娜·哈克伯恩的回答。我们一点一点地从我们的项目中删除所有的单例,以支持轻量级的、任务范围的对象,这些对象可以在您实际需要时轻松地重新创建。
单例测试对于测试来说是一场噩梦,如果懒惰地初始化,将引入带有细微副作用的"状态不确定性"(当将调用从一个作用域移动到另一个作用域时,可能会突然出现这种情况)。可视性是另一个问题,由于单例意味着对共享状态的"全局"(=random)访问,因此在并发应用程序中不正确同步时可能会出现细微的错误。
我认为它是一种反模式,它是一种糟糕的面向对象风格,本质上相当于维护全局状态。
回到你的问题:
尽管应用程序上下文本身可以被视为单例,但它是框架管理的,并且具有定义良好的生命周期、范围和访问路径。因此,我相信,如果你确实需要管理应用程序的全局状态,它应该放在这里,而不是其他地方。对于其他任何事情,如果您真的需要一个单例对象,或者它也可以重写您的单例类,而不是实例化执行手头任务的小的、短暂的对象。
- 我试图用一个应用程序类代替我的单例程序,但我遇到了一些困难。例如,服务处理程序无法访问应用程序。
- 您总是可以通过上下文(Context.getApplication[Context]来访问应用程序实例,这样您就可以在Handler中的某个地方保留一个引用。小心不要泄露那个参考。
- 你能在答案中加些代码吗?区分好与坏,让你的观点更有力。
- 如果你是推荐申请,你是推荐使用单件。老实说,这是不可能的。应用程序是单例的,具有蹩脚的语义。我不会卷入关于单身的宗教争论,你永远不应该使用这些东西。我更喜欢实际操作——在某些地方,它们是维护每个流程状态的好选择,并且可以通过这样做简化事情,而且您也可以在错误的情况下使用它们,并让自己陷入困境。
- 是的,我确实提到了"应用程序上下文本身可以被视为单例"。不同之处在于,对于应用程序实例,用脚射自己要困难得多,因为它的生命周期是由框架来处理的。像guice、hivemind或spring这样的DI框架也使用单例,但这是开发人员不应该关心的实现细节。我认为依赖正确实现的框架语义而不是您自己的代码通常更安全。是的,我承认我知道!-)
- 老实说,这并不能阻止你像单身汉那样用脚射自己。这有点令人困惑,但应用程序没有生命周期。它是在应用程序启动时创建的(在它的任何组件被实例化之前),并且在此时调用它的onCreate(),并且…仅此而已。它坐在那里,永远活下去,直到这个过程结束。就像单身汉一样。:)
- 哦,有一件事可能会让人困惑——Android非常注重在流程中运行应用程序,以及管理这些流程的生命周期。因此,在Android上,单例是利用流程管理的一种非常自然的方法——如果您希望在平台需要回收流程内存以获取其他内容之前在流程中缓存一些内容,那么将该状态置于单例中就可以做到这一点。
- 好的,很公平。我只能说,自从我们摆脱了自我管理的单身生活后,我再也没有回头过。我们现在选择的是一个轻量级的DI风格的解决方案,在这里我们保留一个工厂单例(rootfactory),它反过来由应用程序实例管理(如果愿意,它是一个委托)。这个singleton管理所有应用程序组件所依赖的公共依赖项,但实例化是在一个位置(即应用程序类)管理的。虽然这种方法只剩下一个单例,但它仅限于应用程序类,因此没有其他代码模块知道这个"细节"。
- 这些都是我原则上赞同的关于单身汉的经典批评。在实践中,有时它们是完全有意义的,只要你把危险牢记在心——事实上,这些关注对于所有对象都是正确的:减少可变状态,封装对你必须保持的状态的访问,等等。我想知道你指的是什么不确定性,马提亚斯。您使用的是线程安全的单例模式还是经典的"if(instance==null)"等模式?不要质疑你的练习--我想知道我是否错过了什么。:)
- 顺便问一下,如果我同意黛安娜和马蒂亚斯的观点,可以吗?:)
- 嘿,戴夫,我所指的是惰性初始化(它通常与单例一起使用,尽管当然不是必需的)。当然,延迟初始化没有什么问题,但是与单例结合起来,它意味着"延迟全局状态",它会在第一次访问单例时突然出现。这可能会导致细微的错误,重新排序语句会导致不同的执行行为。我个人发现,如果一个问题的解决方案需要我处理这么多微妙的问题,那可能不是一个好的解决方案。
- 好的,底线是:从应用程序的角度来看,任何你要创建的单例,应用程序对象在任何时候都会被清除。正确的模式是让每个活动等检查singleton是否在其onCreate()中丢失。这还意味着每个对象必须在onSaveInstanceState()中保存足够的信息,以便在需要时重新创建singleton。都对吗?
- 只要应用程序进程是活动的,应用程序对象就永远不会被清除,并且任何通过强引用可访问的单例对象(或任何与此相关的对象)也不会被清除。这里提到的优点和缺点主要是w.r.t.到访问路径(它对测试性等有很强的影响)和生命周期,而不是生命周期。所以,不,单例不能"丢失",应用程序对象也不能。
- 是的,但是这个过程可以在任何时候被终止(只要它的任何活动实际上都不是活动的),对吗?
- 这是正确的,但是在这种情况下,如果用户切换回应用程序,android只需重新启动它(即调用Application.onCreate),所有内容都将重新初始化。
- 单件被单件使用是危险的。如果管理不当,很容易导致内存泄漏
- 我认为重要的是要记住爱德华福尔克斯以上的观点,即这些独生子在任何时候都会随着这个过程而被杀死。但是,我不同意OnSavedinstanceState应回购数据。根据应用程序的不同,没有noHistory标志的每个活动都需要检查singleton数据是否完整(简单的标志解决方案),如果不完整,则在数据被保存到任何位置时重新弹出singleton,或者返回堆栈中表示会话开始的activity,因此可以允许用户重建状态
- 来自iOS世界的一个词……iOS世界中的等效"应用程序"确实是…正是一个单身汉。嘿!它的呈现方式与任何其他单例完全相同。如果我理解这里hackbod的评论,在Android中也是如此……除了应用程序有"蹩脚的语义"——但它仍然是一个单例。(我愚蠢的苹果设备上该死的拼写检查程序把它改成了"黑客"btw,对不起!呵呵)
- @马蒂亚斯在进程被终止时并不像"Android会简单地重启它,这样所有的东西都会重新初始化"那样简单。有一些问题,如菲利普·布雷特在这里指出的(goo.gl/oibhbx)
- @马蒂亚斯,一个小的Github项目怎么做?更简单的理解是:—)
- 我从来没有欣赏过这样的评论:"这是一种糟糕的面向对象的风格,经常被来自C等程序语言的人所接受。"JVM是用什么语言实现的?
- @Sriram the idioms of the application platform are completely ortigonal to the idioms used in the itself platform.跟随你的论据逻辑,我们都应该在C Instead of HTML中写入Web Frontends,因为浏览器在C.
- @Matthias Well,I agree with you,but I wasn't the one who started generalizing stuff(in your case about C programmers who transitioned to Java),which is honestly speaking a little sad and doesn't carry any solid proof.
- @Sriram Granted,that how was inappropriate.道歉,如果我伤害了人。我站在我的立场上,认为从一种语言到另一种语言的实践和愚蠢是绝对常见的,而诚恳地说,我并不排除我自己在这里(我的第一次到Scala的forays were quite Java-Esque…),with singletons specifically programmient programmient in procedural languages find a familiar way of encodingHat essentially amounts to Global State.我不在乎,不在乎,但我确实注意到了。
- @Sriram我已经编辑了我的答案,我希望没有潜在的侮辱。谢谢你的指点!
- 当外交官不在安德罗德工作时,我停止了使用单词,他们解决的问题多于他们创造的问题,事实上,我从来没有遭受过来自单词的任何负面影响。
我非常推荐单件的。如果您有一个需要上下文的singleton,请具有:
1 2 3 4 5
| MySingleton. getInstance(Context c ) {
//
// ... needing to create ...
sInstance = new MySingleton (c. getApplicationContext());
} |
与应用程序相比,我更喜欢单例应用程序,因为它有助于使应用程序更具组织性和模块化——而不是让应用程序中的所有全局状态都需要维护在一个地方,每个单独的部分都可以自行处理。另外,单例初始化(根据请求)而不是在application.oncreate()中提前执行所有初始化的过程中,这一点也很好。
使用单例并没有本质上的错误。只要在有意义的时候正确使用它们。Android框架实际上有很多这样的东西,它可以维护加载资源和其他类似东西的每个进程缓存。
同样,对于简单的应用程序,多线程处理不会成为单线程的问题,因为根据设计,对应用程序的所有标准回调都在进程的主线程上调度,所以除非通过线程或通过将内容提供程序或服务IBinder隐式地发布到其他过程。
只是要考虑一下你在做什么。:)
- 如果一段时间以后我想听一个外部事件,或者在ibinder上共享(我想这不是一个简单的应用程序),我就必须添加双重锁定、同步、易失性,对吗?谢谢你的回答:)
- 不用于外部事件--在主线程上也调用BroadcastReceiver.onReceive()。
- 可以。你能给我指一些阅读材料吗(我更喜欢代码),在那里我可以看到主线程调度机制?我认为这将同时为我澄清几个概念。事先谢谢。
- 这是主要的应用程序端调度代码:android.git.kernel.org/?P=平台/框架/…
- 我承认,当你为单身汉定义正确的责任时,没有什么错。即使没有真正的实时对象实例在最终代码中对其进行响应,在设计中绘制单例也应该感到舒适。"对于我来说,编程语言中的"类型"通常是单例的好例子。
- 同意。。。我还建议重写getSystemService(),并在那里返回OnDemand实例。这在创建myPersistAntobj.From(上下文上下文)方法以及相应的getSystemService重写时需要一些开销,但您可以从两个方面获得最好的结果。您可以将代码分成一个单独的utils类型类,并保持应用程序类的整洁。缺点是每个访问至少需要一个字符串比较。这也是Android框架为布局充气机等工作的方式。
- 使用单例并没有本质上的错误。只要在有意义的时候正确使用它们。…当然,没错,说得好。Android框架实际上有很多这样的东西,它可以维护加载资源和其他类似东西的每个进程缓存。正如你所说。从你在iOS世界的朋友那里,iOS中的"一切都是一个独身者"。在物理设备上,没有什么比单体概念更自然的了:GPS、时钟、陀螺仪等等——从概念上讲,除了单体概念之外,你还会如何设计这些概念呢?所以是的。
- 应该在上下文操作中使用弱引用
- @hackbod链接断开。
- @乔布劳,你能给我指一下AOSP中的一行代码吗?
- 有点晚了,但这里有一个链接指向从AOSP source repo克隆的activitythread.java源代码。更具体地说,这里是指向实际执行整个应用程序回调的方法的链接;这包括活动、片段和服务的所有生命周期事件。
- 最终,我们将从单例转向虚拟化。它为您提供了一个具有理想口味的单例副本,与实际单例分离,并提供了更明智地共享资源的能力。
- @hackbod lint说:"不要将android上下文类放在静态字段中;这是内存泄漏(同时也会中断即时运行)"。
- 我不知道是否有人注意到这一点,但是如果每次调用getInstance()时都创建一个新实例,那么它实际上不是一个单例实例,只是一个静态的util方法。尽管如此,我的理解还是有缺陷的。请纠正我。
来自:开发者>参考-应用程序
There is normally no need to subclass Application. In most situation,
static singletons can provide the same functionality in a more modular
way. If your singleton needs a global context (for example to register
broadcast receivers), the function to retrieve it can be given a
Context which internally uses Context.getApplicationContext() when
first constructing the singleton.
- 如果你写了一个接口,留下一个非静态的接口,你甚至可以使单元格的缺陷结构——使用类别注入生产单元格,通过一个非缺陷结构,也就是说,在单元格的测试中,制造商使用单元格来创建单元格。
我也有同样的问题:单件还是制作一个子类android.os.application?
首先,我尝试了单件,但我的应用程序在某个时候调用了浏览器。
1
| Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com")); |
问题是,如果手机没有足够的内存,你的大部分课程(甚至是单件课程)都会被清理以获得一些内存,所以当从浏览器返回到我的应用程序时,每次都会崩溃。
解决方案:将所需数据放入应用程序类的子类中。
- 我经常遇到人们说这可能发生的职位。因此,我只需将对象通过延迟加载等方式附加到应用程序,以确保生命周期是文档化的和已知的。请确保不要将数百个图像保存到您的应用程序对象中,正如我所理解的那样,如果您的应用程序在后台,并且所有活动都被销毁,以释放内存以供其他进程使用,则不会从内存中清除图像。
- 嗯,应用程序重启后的单例延迟加载不是让GC扫描对象的正确方法。我们的关系是,对吗?
- 真正地?Dalvik卸载类并丢失程序状态?你确定这不是垃圾收集那种有限的生命周期活动相关的对象,你一开始不应该把它们放在单例中吗?你必须给克莱拉举个例子来说明这一非凡的主张!
- 除非我不知道有什么变化,否则Dalvik不会卸载类。曾经。他们看到的行为是他们的进程在后台被杀死,为浏览器腾出空间。他们可能正在初始化"主"活动中的变量,这些变量可能在从浏览器返回时在新进程中没有创建。
应用程序与singleton不同。原因如下:
在UI线程中调用应用程序的方法(如onCreate);
可以在任何线程中调用singleton方法;
在应用程序的"onCreate"方法中,可以实例化处理程序;
如果在none-ui线程中执行singleton,则无法实例化处理程序;
应用程序能够管理应用程序中的活动。它具有"RegisterActivityLifecycleCallbacks"。但单例没有能力。
- 注:您可以在任何线上安装手柄。从文档中可以看出:"当你创建一个新的手柄时,它会接近创建该手柄的螺纹/消息尾巴"
- 谢天谢地!just now I learned"mechanism of the looper."if instantiate handler on the none-ui thread without the code looper.prepare(),the system will report the error"Java.lang.runtimeexception:can't create handler inside thread that has not called looper.prepare().
同时考虑两者:
- 将单例对象作为类内的静态实例。
- 有一个公共类(context),该类为应用程序中的所有singleton对象返回singleton实例,其优点是context中的方法名将有意义,例如:context.getLoggedinuser()而不是user.getInstance()。
此外,我建议您扩展您的上下文,不仅包括对singleton对象的访问,还包括一些需要全局访问的功能,例如:context.logoffuser()、context.readsaveddata(),等等。那么将上下文重命名为facade可能是有意义的。
它们实际上是一样的。我能看到一个区别。通过应用程序类,您可以在application.oncreate()中初始化变量,并在application.onterminate()中销毁它们。对于singleton,您必须依赖VM初始化和销毁静态。
- OnTerminate的文档说它只被模拟器调用过。在设备上,该方法可能不会被调用。developer.android.com/reference/android/app/…
从传说中的马嘴里…
在开发应用程序时,您可能会发现有必要在整个应用程序中共享数据、上下文或服务。例如,如果您的应用程序具有会话数据,例如当前登录的用户,则您可能希望公开此信息。在android中,解决这个问题的模式是让android.app.application实例拥有所有的全局数据,然后将您的应用程序实例视为具有对各种数据和服务的静态访问器的单一实例。
在编写Android应用程序时,保证只有一个android.app.application类的实例,因此将其视为单例是安全的(并由Google Android团队推荐)。也就是说,可以将静态getInstance()方法安全地添加到应用程序实现中。像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class AndroidApplication extends Application {
private static AndroidApplication sInstance;
public static AndroidApplication getInstance(){
return sInstance;
}
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
}
} |
我的2分钱:
我确实注意到,当我的活动被破坏时,一些单例/静态字段被重置了。我在一些低端2.3设备上注意到了这一点。
我的案例非常简单:我只有一个私有的文件"initu done"和一个静态方法"init",它是我从activity.oncreate()调用的。我注意到init方法在重新创建活动时正在重新执行自己。
虽然我不能证明我的肯定,但它可能与创建/使用单例/类的时间有关。当活动被销毁/回收时,似乎只有此活动引用的所有类也都被回收。
我将singleton实例移动到应用程序的子类中。我从应用程序实例中访问它们。从那时起,就再也没有注意到这个问题。
我希望这能帮助别人。
我的活动调用finish()(这不会使它立即完成,但最终会完成)并调用Google Street Viewer。当我在Eclipse上调试它时,当调用Street Viewer时,我与应用程序的连接就会断开,我理解这是因为(整个)应用程序正在关闭,应该是为了释放内存(因为正在完成的单个活动不应该导致这种行为)。不过,我可以通过onsaveInstanceState()将状态保存到包中,并将其恢复到堆栈中下一个活动的onCreate()方法中。通过使用静态单例或子类化应用程序,我将面对应用程序关闭和丢失的状态(除非我将其保存在一个包中)。根据我的经验,在国家保护方面,它们是一样的。我注意到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中,我的理解表明,记忆恢复机制在某些方面发生了变化。"我想你的器械不同于可用的记忆然后你的结论可能是不正确的
- 耶稣:是的,你必须是对的。如果记忆恢复机制在不同版本之间发生变化,那会很奇怪。大概不同的记忆使用导致了不同的行为。