Uyarı: Android bağlam sınıflarını statik alanlara yerleştirmeyin; bu bir bellek sızıntısıdır (ve ayrıca Anında Çalıştırmayı da bozar)


84

Android Studio:

Android bağlam sınıflarını statik alanlara yerleştirmeyin; bu bir bellek sızıntısıdır (ve ayrıca Anında Çalıştırmayı da bozar)

O halde 2 soru:

# 1 startServiceBağlam için statik değişken olmadan statik bir yöntemden a'yı nasıl çağırırsınız ?
# 2 Statik bir yöntemden (aynı) localBroadcast'i nasıl gönderirsiniz?

Örnekler:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

veya

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

Bunu kullanmadan yapmanın doğru yolu ne olabilir mContext?

NOT: Sanırım asıl sorum, bağlamı çağıran yöntemin yaşadığı bir sınıfa nasıl geçireceğim olabilir.


Bağlamı yöntemde bir parametre olarak geçiremez misiniz?
Juan Cruz Soler

Bu rutini bağlamı olmayan yerlerde de çağırıyor olurdum.
John Smith

# 1 bunu parametre # 2 olarak aynı şekilde geçirin.
njzk2

Daha sonra bağlamı çağıran yönteme de iletmelisiniz. Onun Görünümler hepsi ile bir etkinlik sızıntı böylece sorun, statik alanlar çöp toplanan olmamasıdır
Juan Cruz Soler

1
@JohnSmith Başlatma etkinliğinden (yapıcı parametreleri veya yöntem parametreleri aracılığıyla) ihtiyacınız olan noktaya kadar bunu basamaklandırın.
AndroidMechanic - Viral Patel

Yanıtlar:


56

Yönteminize bir parametre olarak iletmeniz yeterlidir. ContextYalnızca bir .pst dosyasını başlatmak amacıyla statik bir örnek oluşturmanın bir anlamı yoktur Intent.

Yönteminiz şöyle görünmeli:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

Soruyla ilgili yorumlardan güncelleme: Başlatma etkinliğinden (yapıcı parametreleri veya yöntem parametreleri aracılığıyla) bağlamı ihtiyacınız olan noktaya kadar basamaklayın.


Yapıcı bir örnek verebilir misiniz?
John Smith

sınıf adınız MyClassona bir yöntem gibi genel bir kurucu eklemek ise public MyClass(Context ctx) { // put this ctx somewhere to use later }(Bu sizin kurucunuzdur) Şimdi MyClassbu MyClass mc = new MyClass(ctx);
kurucuyu

Talep üzerine devretmenin o kadar basit olduğunu sanmıyorum. Eski bir bağlam veya buradaki gibi endişelenmemek gibi belirgin faydalar olsa da, statik olan. Eşzamansız olarak çağrılacak bir yanıt geri aramasında bağlama ihtiyacınız olduğunu varsayalım [tercihlere yazmak isteyebilirsiniz]. Yani bazen, bir üye alanına koymak zorunda kalıyorsunuz. Ve şimdi bunu nasıl statik yapmayacağınızı düşünmelisiniz. stackoverflow.com/a/40235834/2695276 çalışıyor gibi görünüyor.
Rajat Sharma

1
ApplicationContext'i statik alan olarak kullanmak uygun mudur? Etkinliklerin aksine, uygulama nesnesi yok edilmez, değil mi?
NeoWang

ama soru şu ki, başlangıçta neden bellek sızıntısı oluyor?
juztcode

51

Herhangi bir üye alanında saklamaya karar verirseniz, tekliğinize yöntemler / yapıcı aracılığıyla iletilen herhangi bir bağlamda context.getApplicationContext () ilettiğinizden veya getApplicationContext () 'i çağırdığınızdan emin olun.

aptal kanıtı örneği (birisi bir aktivitede geçse bile uygulama bağlamını alır ve bunu tekliyi somutlaştırmak için kullanır):

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

Aşağıdaki belgelere göre getApplicationContext (): "Geçerli sürecin tek, global Application nesnesinin bağlamını döndür ."

Bu, "getApplicationContext ()" tarafından döndürülen bağlamın tüm süreç boyunca yaşayacağı anlamına gelir ve bu nedenle, uygulamanızın çalışma süresi boyunca her zaman orada olacağından (ve herhangi bir nesneden daha uzun ömürlü olacağından, herhangi bir yerde ona statik bir referans depolayıp depolamamanızın bir önemi yoktur. / singletons tarafından somutlaştırılmış).

Bunu, büyük miktarda veri tutan görünümlerin / etkinliklerin içindeki bağlamla karşılaştırın, bir etkinlik tarafından tutulan bir bağlamı sızdırırsanız, sistem o kaynağı serbest bırakamayacağı açıktır ki bu da iyi değildir.

Bağlamına göre bir etkinliğe yapılan atıf, etkinliğin kendisiyle aynı yaşam döngüsünü yaşamalıdır, aksi takdirde bağlamı bir bellek sızıntısına neden olarak rehin tutacaktır (tüy bırakma uyarısının arkasındaki neden budur).

DÜZENLEME: Yukarıdaki belgelerden örnek alan adama, kodda az önce yazdıklarım hakkında bir yorum bölümü bile var:

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.

8
Yukarıdaki örneğe vuran adama vuran adama: Bu konunun amacı, Google'ın kendi önerilen singleton oluşturma modeliyle çelişen Lint uyarısıdır.
Raphael C

7
Okuyun: "Android bağlam sınıflarını statik alanlara yerleştirmeyin; bu bir bellek sızıntısıdır (ve ayrıca Anında Çalıştırmayı bozar)" Bağlam sınıflarının ne olduğunu biliyor musunuz? Etkinlik bunlardan biridir ve Activity'i sizin tanımladığınız gibi statik bir alan olarak saklamamalısınız (veya hafızayı sızdıracaktır). Bununla birlikte, her şeyi aştığı için Bağlamı (uygulama bağlamı olduğu sürece) statik bir alan olarak depolayabilirsiniz. (Ve bu nedenle uyarıyı dikkate almayın). Eminim bu basit gerçek üzerinde anlaşabiliriz, değil mi?
Marcus Gruneau

bir iOS veterineri olarak, Android'in ilk haftasında ... Bunun gibi açıklamalar, bu bağlamı anlamama yardımcı oluyor .. Yani, bu tüy bırakma uyarısı (ah, herhangi bir uyarıdan nasıl hoşlanmıyorum) ortalıkta dolanacak, ancak cevabınız gerçek sorunu çözecek .
eric

1
@Marcus eğer çocuk sınıfınız onu kimin hangi bağlamla örneklediğinin farkında değilse, onu statik bir üye olarak saklamak kötü bir uygulamadır. ayrıca, uygulama bağlamı, uygulamanızın Uygulama nesnesinin bir parçası olarak yaşar, uygulama nesnesi sonsuza kadar bellekte kalmaz, öldürülür. Popüler inanışın aksine, uygulama sıfırdan yeniden başlatılmayacaktır. Android, yeni bir Uygulama nesnesi oluşturacak ve uygulamanın ilk etapta asla öldürülmediği yanılsamasını vermek için kullanıcının daha önce bulunduğu etkinliği başlatacaktır.
Raphael C

@RaphaelC böyle bir dokümantasyonunuz var mı? Bu tamamen yanlış gibi görünüyor çünkü Android, her işlemin çalıştırılması için yalnızca bir Uygulama bağlamı sağlıyor.
HaydenKai

6

Bu sadece bir uyarı. Merak etmeyin. Bir uygulama bağlamı kullanmak istiyorsanız, onu projenizdeki tüm singleton sınıfını kaydetmek için kullanılan bir "singleton" sınıfına kaydedebilirsiniz.


2

Sizin durumunuzda, onu statik alan olarak kullanmak pek mantıklı gelmiyor, ancak her durumda kötü olduğunu düşünmüyorum. Şimdi ne yapıyorsanız, bağlamı olan ve daha sonra boş bırakan statik alana sahip olabilirsiniz. İçinde bağlamı olan ana model sınıfım için statik örnek oluşturuyorum, uygulama bağlamı etkinlik bağlamı değil ve ayrıca yok edildiğinde boşta bıraktığım Activity içeren sınıfın statik örnek alanına sahibim. Hafıza sızıntısı olduğunu görmüyorum. Yani zeki bir adam hatalı olduğumu düşünürse yorum yapmaktan çekinmeyin ...

Ayrıca Anında Çalıştırma burada iyi çalışıyor ...


Prensipte yanıldığınızı düşünmüyorum, ancak bahsettiğiniz Aktivitenin statik alanları kullanmadan önce herhangi bir zamanda yalnızca bir maksimum tek bir örneğe sahip olduğuna çok dikkat etmelisiniz. Uygulamanız farklı yerlerden başlatılabildiği için (bildirim, derin bağlantı, ...) birden fazla arka yığınla sonuçlanırsa, manifestte singleInstance gibi bir bayrak kullanmadığınız sürece işler ters gidecektir. Bu nedenle, Etkinlikler'deki statik alanlardan kaçınmak her zaman daha kolaydır.
BladeCoder

android: launchMode = "singleTask" yeterli olmalı, bu yüzden ona geçiyorum, singleTop kullandım ama yeterli olmadığını bilmiyordum çünkü ana faaliyetlerimin her zaman sadece tek örneklerini istiyorum, uygulamalarım böyle tasarlanıyor.
Renetik

2
"singleTask", görev başına yalnızca bir örneği garanti eder. Uygulamanızda derin bağlantı oluşturma veya bir bildirimden başlatma gibi birden çok giriş noktası varsa, birden çok görevle sonuçlanabilir.
BladeCoder

2

WeakReferenceBağlamı Singleton sınıflarında saklamak için kullanın ve uyarı kaybolur

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

Şimdi Bağlam'a şu şekilde erişebilirsiniz:

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }

1

Genel olarak, bağlam alanlarının statik olarak tanımlanmasından kaçının. Uyarının kendisi nedenini açıklıyor: Bu bir bellek sızıntısı. Anlık koşuyu kırmak , gezegendeki en büyük sorun olmayabilir .

Şimdi, bu uyarıyı alacağınız iki senaryo var. Bir örnek için (en bariz olanı):

public static Context ctx;

Ve sonra bağlamın bir sınıfa sarıldığı biraz daha zor olanı var:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

Ve bu sınıf bir yerde statik olarak tanımlanır:

public static Example example;

Ve uyarıyı alacaksınız.

Çözümün kendisi oldukça basittir: Bağlam alanlarını , ister bir sarmalama sınıfı olsun, ister onu doğrudan statik olarak bildiren statik örneklere yerleştirmeyin .

Ve uyarının çözümü basit: alanı statik olarak yerleştirmeyin. Sizin durumunuzda, bağlamı yönteme örnek olarak aktarın. Birden çok Bağlam çağrısının yapıldığı sınıflar için bağlamı (veya bu konuyla ilgili bir Aktivite) sınıfa geçirmek için bir yapıcı kullanın.

Bunun bir uyarı olduğunu, bir hata olmadığını unutmayın. Herhangi bir nedenle statik bir bağlama ihtiyacınız olursa , bunu yapabilirsiniz. Bunu yaptığınızda bir bellek sızıntısı yaratmanıza rağmen.


bellek sızıntısı oluşturmadan bunu nasıl yapabiliriz?
isJulian00

1
Yapamazsın. Çevrenizdeki bağlamları kesinlikle geçmeniz gerekiyorsa, bir etkinlik otobüsüne bakabilirsiniz
Zoe

ok bu benim yaşadığım problemdi, lütfen bir göz atabilirseniz, belki de bunu yapmanın başka bir yolu olabilir, btw yöntem statik olmalıdır çünkü onu c ++ kod stackoverflow.com/questions/54683863/… '
isJulian00

0

Bunun bir Uygulama Bağlamı olduğundan emin olursanız. Bu çok önemli. Bunu ekle

@SuppressLint("StaticFieldLeak")

1
Yine de bunu yapmanızı tavsiye etmem. Bağlama ihtiyacınız varsa, AndroidX kitaplıkları kullanıyorsanız, requireContext () yöntemini kullanabilirsiniz. Veya Bağlamı, ona ihtiyaç duyan yönteme doğrudan aktarabilirsiniz. Veya uygulamanın sınıf referansını bile alabilirsiniz, ancak böyle bir SuppressLint önerisini kullanmamayı tercih ederim.
Oleksandr Nos
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.