Android'deki Singletons ve Uygulama Bağlamı mı?


362

Singleton kullanmanın çeşitli problemlerini numaralandıran ve singleton desenini kullanan Android uygulamalarının birkaç örneğini gördükten sonra bu yazıyı hatırlatarak , küresel uygulama durumu (android.os alt sınıflaması) aracılığıyla paylaşılan tek örnekler yerine Singletons kullanmanın iyi bir fikir olup olmadığını merak ediyorum. context.getApplication ()) aracılığıyla.

Her iki mekanizmanın da ne gibi avantajları / dezavantajları vardır?

Dürüst olmak gerekirse, ben bu cevap Web uygulaması ile Singleton desen bekliyoruz, İyi bir fikir değil! ancak Android'e uygulandı. Doğrumuyum? DalvikVM'de farklı olan nedir?

DÜZENLEME: İlgili çeşitli yönler hakkında görüş almak istiyorum:

  • Senkronizasyon
  • Tekrar Kullanılabilirlik
  • Test yapmak

Yanıtlar:


295

Dianne Hackborn'un cevabına çok katılmıyorum. Gereksinim duyduğunuzda kolayca yeniden oluşturulabilen hafif, görev kapsamındaki nesneler lehine tüm tektonları projemizden biraz çıkararak bitiriyoruz.

Singletons, test için bir kabustur ve tembel bir şekilde başlatılırsa, ince yan etkileri olan (çağrıları bir kapsamdan diğerine taşırken aniden ortaya çıkabilen) "devlet belirsizliği" tanıtacaktır getInstance(). Görünürlük başka bir sorun olarak belirtilmiştir ve tekil noktalar paylaşılan duruma "global" (= rastgele) erişim ima ettiğinden, eşzamanlı uygulamalarda düzgün şekilde senkronize edilmediğinde küçük hatalar ortaya çıkabilir.

Bunu bir anti-desen olarak görüyorum, esasen küresel devleti korumak anlamına gelen kötü bir nesne yönelimli stil.

Sorunuza geri dönmek için:

Uygulama bağlamı tek birtonun kendisi olarak kabul edilebilir, ancak çerçeve tarafından yönetilir ve iyi tanımlanmış bir yaşam döngüsü , kapsam ve erişim yoluna sahiptir. Bu nedenle, eğer uygulama küresel durumunu yönetmeniz gerekiyorsa, buraya başka bir yere gitmemesi gerektiğine inanıyorum. Başka bir şey için, gerçekten tek bir nesneye ihtiyacınız varsa veya elinizdeki görevi yerine getiren küçük, kısa ömürlü nesneleri başlatmak için singleton sınıfınızı yeniden yazmanın mümkün olup olmadığını yeniden düşünün .


131
Uygulama tavsiye ediyorsanız, tekliton kullanmanızı öneririz. Dürüst olmak gerekirse, bunun hiçbir yolu yoktur. Uygulama , crappier semantiği ile bir singleton. Hiç kullanmamanız gereken bir şey hakkında singletonlar hakkında dini tartışmalara girmeyeceğim. Pratik olmayı tercih ediyorum - süreç başına durumu korumak için iyi bir seçim oldukları ve bunu yaparak işleri basitleştirebilecekleri yerler var ve bunları yanlış durumda da kullanabilir ve kendinizi ayağınızdan çekebilirsiniz.
hackbod

18
Doğru, ve ben "app bağlamı bir singleton kendisi olarak kabul edilebilir" bahsetti. Fark, uygulama örneğinde, yaşam döngüsünün çerçeve tarafından ele alındığından, kendinizi ayağa vurmanın çok daha zor olmasıdır. Guice, Hivemind veya Spring gibi DI çerçeveleri de tek tektonları kullanır, ancak bu geliştiricinin umursamaması gereken bir uygulama detayıdır. Bence çerçeve semantiğinin kendi kodunuzdan ziyade doğru bir şekilde uygulanmasına güvenmek daha güvenlidir. Evet, itiraf ediyorum! :-)
Matthias

93
Dürüst olmak gerekirse, kendinizi bir singletondan daha fazla ayağa vurmanızı engellemez. Biraz kafa karıştırıcı, ancak Uygulama yaşam döngüsü yok . Uygulamanız başladığında (bileşenlerinden herhangi biri somutlaştırılmadan önce) ve o noktada onCreate () çağrıldığında oluşturulur ve hepsi bu. Süreç ölene kadar orada oturuyor ve sonsuza dek yaşıyor. Tıpkı bir singleton gibi. :)
hackbod

30
Oh, bunu kafa karıştırıcı olabilecek bir şey var - Android, süreçlerde uygulamaları çalıştırmak ve bu süreçlerin yaşam döngüsünü yönetmek için çok tasarlanmış. Android tektonları, bu süreç yönetiminden yararlanmanın çok doğal bir yoludur - platformun işlemin belleğini başka bir şey için geri alması gerekene kadar işleminizde bir şey önbelleğe almak istiyorsanız, bu durumu tek birtona koymak bunu yapar.
hackbod

7
Tamam, yeterince adil. Sadece kendi kendini yöneten singletonlardan uzaklaştığımızdan beri geriye bakmadığımı söyleyebilirim. Şimdi hafif bir DI tarzı çözüm seçiyoruz, burada bir fabrika single'ı (RootFactory) tutuyoruz, bu da uygulama örneği tarafından yönetiliyor (eğer yapacaksanız bir delege). Bu singleton, tüm uygulama bileşenlerinin güvendiği ortak bağımlılıkları yönetir, ancak örnekleme tek bir konumda yönetilir - uygulama sınıfı. Bu yaklaşımda bir singleton kalırken, Application sınıfıyla sınırlıdır, bu nedenle başka hiçbir kod modülü bu "detay" hakkında bilgi sahibi değildir.
Matthias

231

Singletons'ı çok tavsiye ederim. Bir bağlama ihtiyaç duyan bir singletonunuz varsa:

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

Uygulamaya göre singletonları tercih ederim çünkü bir uygulamayı çok daha düzenli ve modüler tutmaya yardımcı olur - uygulamadaki tüm küresel durumunuzun korunması gereken tek bir yere sahip olmak yerine, her bir ayrı parça kendi kendine bakabilir. Ayrıca, tek tek uygulamaların, Application.onCreate () öğesinde tüm başlatma işlemlerini başaşağı yapmak yerine tembel olarak başlatması (istek üzerine) olması iyidir.

Singleton kullanmanın özünde yanlış bir şey yoktur. Sadece mantıklı olduğunda onları doğru kullanın. Android çerçevesi, yüklü kaynakların ve diğer benzeri şeylerin işlem başına önbelleklerini sürdürmesi için aslında birçoğuna sahiptir.

Ayrıca, basit uygulamalar için çoklu iş parçacığı tek başına bir sorun haline gelmez, çünkü tasarım gereği uygulamaya tüm standart geri çağrılar sürecin ana iş parçacığına gönderilir, böylece açık bir şekilde iş parçacıkları veya bir içerik sağlayıcısını veya hizmet IBinder'ini diğer süreçlere yayınlayarak dolaylı olarak

Sadece ne yaptığınıza dikkat edin. :)


1
Bir süre sonra harici bir olay dinlemek veya bir IBinder (sanırım bu basit bir uygulama olmaz) paylaşmak istiyorum, ben çift kilitleme, senkronizasyon, uçucu, eklemek gerekir? Cevabınız için teşekkürler :)
mschonaker

2
Harici bir olay için değil - ana iş parçacığında BroadcastReceiver.onReceive () de çağrılır.
hackbod

2
Tamam. Beni ana iş parçacığı sevk mekanizması görebileceğiniz bazı okuma materyali (kod tercih ederdim) işaret eder misiniz? Bunun benim için birden fazla kavramı netleştireceğini düşünüyorum. Şimdiden teşekkürler.
mschonaker

2
Bu, ana uygulama tarafı gönderme kodudur: android.git.kernel.org/?p=platform/frameworks/…
hackbod

8
Singleton kullanmanın özünde yanlış bir şey yoktur. Sadece mantıklı olduğunda onları doğru kullanın. .. kesinlikle, kesin, iyi dedi. Android çerçevesi, yüklü kaynakların ve diğer benzeri şeylerin işlem başına önbelleklerini sürdürmesi için aslında birçoğuna sahiptir. Aynen dediğin gibi. İOS dünyasındaki arkadaşlarınızdan, iOS'ta "her şey bir singleton" dur. Fiziksel cihazlarda hiçbir şey tek bir konseptten daha doğal olamaz: gps, saat, jiroskoplar, vb. - kavramsal olarak bunları nasıl tasarlarsınız? singleton olarak? Yani evet.
Fattie

22

Gönderen: Geliştirici> referans - Uygulama

Normalde Uygulamayı alt sınıfa ayırmaya gerek yoktur. Çoğu durumda, statik tek tonlar aynı işlevselliği daha modüler bir şekilde sağlayabilir. Singletonunuzun genel bir bağlama ihtiyacı varsa (örneğin yayın alıcılarını kaydetmek için), onu alma işlevine, singleton'u ilk oluştururken dahili olarak Context.getApplicationContext () kullanan bir Bağlam verilebilir.


1
Ve singleton için bir arabirim yazarsanız, getInstance'ı statik olmayan bir şekilde bırakırsanız, singleton kullanan sınıfın varsayılan kurucusunu, üretim singletonunu varsayılan olmayan bir kurucu aracılığıyla enjekte edebilirsiniz, bu da birim testlerinde singleton sınıfı.
android.weasel

11

Başvuru Singleton ile aynı değildir.

  1. Uygulamanın yöntemi (onCreate gibi) ui iş parçacığında çağrılır;
  2. singleton yöntemi herhangi bir iş parçacığında çağrılabilir;
  3. Uygulama "onCreate" yönteminde, Handler'ı başlatabilirsiniz;
  4. Singleton none-ui iş parçacığında yürütülürse, Handler'ı başlatamazsınız;
  5. Uygulama app.It yöntemi "registerActivityLifecycleCallbacks" yöntemini yönetme yeteneğine sahiptir. Ama singleton yeteneği yoktur.

1
Not: İşleyiciyi herhangi bir iş parçacığında başlatabilirsiniz. from doc: "Yeni bir işleyici oluşturduğunuzda, onu oluşturan iş parçacığının iş parçacığı / ileti kuyruğuna bağlı"
Mesih

1
@Christ Teşekkür ederim! Şimdi "Lüper mekanizmasını" öğrendim. 'Looper.prepare ()' kodu olmadan none-ui iş parçacığında işleyici başlatılırsa, sistem "java.lang.RuntimeException hatasını bildirir: 'Looper.prepare ()' i çağırmamış iş parçacığının içinde bir işleyici yaratmayın.
sunhang

11

Ben de aynı sorunu vardı: Singleton ya da bir alt sınıf android.os.Application yapmak?

Öncelikle Singleton ile denedim ama bir noktada uygulamam tarayıcıyı aradı

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

ve sorun şu ki, eğer yakışıklı yeterli belleğe sahip değilse, sınıflarınızın çoğu (Singletons bile) biraz bellek almak için temizlenir, bu yüzden tarayıcıdan uygulamama döndüğümde, her seferinde çöktü.

Çözüm: Gerekli verileri Uygulama sınıfının bir alt sınıfına yerleştirin.


1
Sık sık insanların bunun olabileceğini belirten gönderilerle karşılaştım. Bu nedenle, yaşam döngüsünün belgelendiğinden ve bilindiğinden emin olmak için uygulamaya tembel yükleme vb. Gibi tektonlar gibi nesneler ekliyorum. Uygulamanız arka planda ise ve tüm işlemler diğer işlemler için belleği boşaltmak için yok edildiğinde anladığım için yüzlerce görüntüyü uygulama nesnenize kaydetmediğinizden emin olun.
Janusz

Uygulama yeniden başlatıldıktan sonra Singleton tembel yükleme, nesnelerin GC tarafından süpürülmesine izin vermenin doğru yolu değildir. Zayıf Referanslar, değil mi?
mschonaker

15
Gerçekten mi? Dalvik dersleri kaldırır ve program durumunu kaybeder mi? Öncelikle singletonlara koymamanız gereken sınırlı yaşam döngüsü Aktivite ile ilgili nesneleri çöp topladığından emin değil misiniz? Böyle olağanüstü bir iddia için clrar örnekleri vermelisiniz!
android.weasel

1
Değişiklikler olmuştur sürece ben, Dalvik gelmez habersiz olduğum değil boşaltmak sınıflar. Hiç. Gördükleri davranış, tarayıcıya yer açmak için süreçlerinin arka planda öldürülmesidir. Değişkeni, tarayıcıdan geri dönerken yeni süreçte yaratılmamış olabilecek "ana" etkinliklerinde başlatırlardı.
Groxx

5

Her ikisini de aynı anda düşünün:

  • sınıflar içinde tek tek nesnelerin statik örnekler olarak bulunması.
  • uygulamanızdaki tüm singelton nesneleri için tekil örnekleri döndüren ortak bir sınıfa (Context) sahip olması, bu bağlamda Context'teki yöntem adlarının anlamlı olması avantajı vardır: User.getInstance () yerine context.getLoggedinUser ().

Ayrıca, Bağlamınızı yalnızca tekil nesnelere erişimi değil, aynı zamanda küresel olarak erişilmesi gereken bazı işlevleri de içerecek şekilde genişletmenizi öneririm, örneğin: context.logOffUser (), context.readSavedData (), vb. Cephe o zaman mantıklı olurdu.


4

Aslında aynı. Görebildiğim bir fark var. Application sınıfıyla değişkenlerinizi Application.onCreate () 'de başlatabilir ve Application.onTerminate ()' de yok edebilirsiniz. Singleton ile, statik başlatma ve yok etme VM'sine güvenmelisiniz.


16
onTerminate için dokümanlar bunun yalnızca emülatör tarafından çağrıldığını söylüyor. Cihazlarda bu yöntem muhtemelen çağrılmayacaktır. developer.android.com/reference/android/app/…
danb

3

2 sentim:

Etkinliğim yok edildiğinde bazı tekil / statik alanların sıfırlandığını fark ettim. Bunu bazı düşük uçlu 2.3 cihazlarda fark ettim.

Benim durumum çok basitti: Ben sadece özel bir dosyalanmış "init_done" ve static.onCreate () denilen statik bir yöntem "init" var. Yöntem init'in aktivitenin bazı yeniden oluşturulmasında kendini yeniden yürüttüğünü fark ettim.

Onayımı kanıtlayamasam da, singleton / sınıfın ilk yaratıldığı / kullanıldığı zamanla ilgili olabilir. Etkinlik yok edildiğinde / geri dönüştürüldüğünde, sadece bu etkinliğin bahsettiği tüm sınıflar da geri dönüştürülmüş gibi görünmektedir.

Singleton örneğimi bir alt Uygulama sınıfına taşıdım. Onları uygulama örneğinden erişiyorum. ve o zamandan beri sorunu tekrar fark etmediler.

Umarım bu birine yardımcı olabilir.


3

Meşhur atın ağzından ...

Uygulamanızı geliştirirken, uygulamanız genelinde küresel olarak veri, içerik veya hizmet paylaşmanız gerekebilir. Örneğin, uygulamanızda şu anda oturum açmış olan kullanıcı gibi oturum verileri varsa, büyük olasılıkla bu bilgileri göstermek istersiniz. Android'de, bu sorunu çözmenin yolu android.app.Application örneğinizin tüm global verilere sahip olmasını ve ardından Uygulama örneğinizi çeşitli veri ve hizmetlere statik erişimli bir singleton olarak ele almaktır.

Bir Android uygulaması yazarken, android.app.Application sınıfının yalnızca bir örneğine sahip olmanız garanti edilir ve bu nedenle onu tek birton gibi ele almak güvenlidir (ve Google Android ekibi tarafından önerilir). Yani, Uygulama uygulamanıza güvenli bir şekilde statik getInstance () yöntemi ekleyebilirsiniz. Şöyle ki:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}

2

Etkinlik çağrılarım bitirir () (hemen bitirmez, ancak sonunda biter) ve Google Street Viewer'ı arar. Eclipse üzerinde hata ayıkladığımda, Street Viewer çağrıldığında, (bütün) uygulamanın kapatıldığını, sözde belleği boşaltmayı düşündüğüm uygulama ile olan bağlantım kesiliyor (biten tek bir etkinlik bu davranışa neden olmamalıdır) . Yine de, onSaveInstanceState () aracılığıyla bir Paket halinde durumu kaydedebilir ve yığında bir sonraki etkinliğin onCreate () yönteminde geri yükleyebilirim. Statik bir singleton veya alt sınıf uygulaması kullanarak, uygulamanın kapanma ve kaybetme durumu ile karşılaşıyorum (bir Pakete kaydetmediğim sürece). Benim tecrübelerime göre, devletin korunması konusunda aynı şeyler. Android 4.1.2 ve 4.2.2'de bağlantının kesildiğini, ancak 4.0.7 veya 3.2.4'te kaybolduğunu fark ettim,


"Bağlantının Android 4.1.2 ve 4.2.2'de kaybolduğunu fark ettim, ancak 4.0.7 veya 3.2.4'te değil, ki bu benim anlayışımla bellek kurtarma mekanizmasının bir noktada değiştiğini gösteriyor." ..... Cihazlarınızın aynı miktarda kullanılabilir belleğe veya yüklü bir uygulamaya sahip olmadığını düşünürdüm. ve dolayısıyla sonucunuz yanlış olabilir
Mesih

@Christ: Evet haklı olmalısın. Bellek kurtarma mekanizmasının sürümler arasında değişmesi garip olurdu. Muhtemelen farklı bellek kullanımı farklı davranışlara neden olmuştur.
Piovezan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.