Uygulama bağlamını her yerde mi kullanıyorsunuz?


476

Bir Android uygulamasında, aşağıdaki yaklaşımla ilgili yanlış bir şey var mı:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

ve bağlamın gerekli olduğu her yerde (örneğin SQLiteOpenHelper) geçirin (ve elbette sızıntı yapmayın)?


23
Sadece bu uygulamaya diğerleri için ayrıntılı için, daha sonra değiştirebilir <application>şu özellik tanımını eklemek üzere AndroidManifest.xml dosyasının düğümünü: android:name="MyApp". Uygulamam, bildiriminizin başvurduğu paketin altında olmalıdır.
Matt Huggins

6
SQLiteOpenHelper bir bağlam sağlama sorunu aşmak için harika bir yol! Tek bir "SQLiteManager" uyguladım ve "nasıl F nasıl singleton için bir bağlam olsun?"
Birisi bir yerde

8
Sadece uygulamanızı süper arayüzlerinden biriyle iade ettiğinizi biliyorsunuz, bu yüzden Uygulamam içinde ek yöntemler sağladıysanız bunları kullanamazsınız. Bunun yerine getContext () yönteminizin bir dönüş türü MyApp olmalıdır ve bu şekilde daha sonra eklenen yöntemleri ve ContextWrapper ve Context içindeki tüm yöntemleri kullanabilirsiniz.

5
Ayrıca bkz. Goo.gl/uKcFn - benzer gönderiyle ilgili başka bir yanıt. Statik değişkeni onCreate'de değil c'tor'da ayarlayın.
AlikElzin-kilaka

1
@ChuongPham Çerçeve uygulamanızı öldürdüyse, boş içeriğe
erişecek bir

Yanıtlar:


413

Bu yaklaşımla ilgili birkaç potansiyel sorun vardır, ancak birçok durumda (örneğin örnek olarak) iyi çalışır.

İle fırsatlar Bunun konuyla uğraşırken Özellikle dikkatli olmalısın GUIki gerektirir Context. Örneğin, uygulama Bağlamı'na iletirseniz LayoutInflaterbir İstisna alırsınız. Genel olarak konuşursak, yaklaşımınız mükemmel: bir kullanmak iyi bir uygulamadır Activity's Contextbu dahilinde Activityve Application Contextbir kapsamı dışında bir bağlam geçerken Activityiçin bellek sızıntıları önlemek .

Ayrıca, deseninize alternatif olarak, Uygulama Bağlamını almak için getApplicationContext()bir Contextnesneyi (Etkinlik gibi) çağırmanın kısayolunu kullanabilirsiniz .


22
İlham verici bir cevap için teşekkürler. Bu yaklaşımı yalnızca kalıcılık katmanı için kullanacağımı düşünüyorum (içerik sağlayıcılarla gitmek istemediğim için). SQLiteOpenHelper'ı, bir Bağlamın Uygulama'nın kendisinden almak yerine sağlanmasını bekleyecek şekilde tasarlamanın arkasındaki motivasyonun ne olduğunu merak ediyorum. PS Ve kitabınız harika!
yanchenko

7
Uygulama bağlamını kullanarak LayoutInflatorsadece benim için çalıştı. Son üç yılda değiştirilmiş olmalı.
Jacob Phillips

5
@JacobPhillips LayoutInflator'ı bir etkinlik bağlamı olmadan kullanmak, o Faaliyetin stilini kaçırır. Yani bir anlamda işe yarar, ama başka bir işe yaramaz.
Mark

1
@MarkCarter Uygulama Bağlamını kullanmak Faaliyetin stilini kaçırır mı demek istediniz?
Jacob Phillips

1
@JacobPhillips evet, Uygulama İçeriği stile sahip olamaz çünkü her Etkinlik farklı bir tarzda şekillendirilebilir.
Mark

28

Deneyimlerime göre bu yaklaşım gerekli olmamalıdır. Herhangi bir şey için bağlama ihtiyacınız varsa, genellikle View.getContext () çağrısı ile alabilirsiniz ve Contextelde edilen kullanarak bağlamı almak için Context.getApplicationContext () ' yi arayabilirsiniz Application. Bu Applicationbağlamı almaya çalışıyorsanız Activity, her zaman bir çağrı için gereken şekilde geçirilebilmesi gereken Activity.getApplication () öğesiniContext çağırabilirsiniz SQLiteOpenHelper().

Genel olarak, bu durum için yaklaşımınızla ilgili bir sorun yok gibi görünüyor, ancak uğraşırken Context, resmi Google Android Geliştiricileri blogunda açıklandığı gibi herhangi bir yere bellek sızdırmadığınızdan emin olun .


13

Bazı insanlar sordu: singleton nasıl bir boş gösterici döndürür? Bu soruyu cevaplıyorum. (Bir yorumda cevap veremiyorum çünkü kod göndermem gerekiyor.)

İki olay arasında null değeri döndürebilir: (1) sınıf yüklenir ve (2) bu sınıfın nesnesi oluşturulur. İşte bir örnek:

class X {
    static X xinstance;
    static Y yinstance = Y.yinstance;
    X() {xinstance=this;}
}
class Y {
    static X xinstance = X.xinstance;
    static Y yinstance;
    Y() {yinstance=this;}
}

public class A {
    public static void main(String[] p) {
    X x = new X();
    Y y = new Y();
    System.out.println("x:"+X.xinstance+" y:"+Y.yinstance);
    System.out.println("x:"+Y.xinstance+" y:"+X.yinstance);
    }
}

Kodu çalıştıralım:

$ javac A.java 
$ java A
x:X@a63599 y:Y@9036e
x:null y:null

İkinci hat gösterir Y.xinstance ve X.yinstance olan boş ; bunlar boştur, çünkü X.xinstance ve Y.yinstance değişkenleri null olduğunda okunur.

Bu düzeltilebilir mi? Evet,

class X {
    static Y y = Y.getInstance();
    static X theinstance;
    static X getInstance() {if(theinstance==null) {theinstance = new X();} return theinstance;}
}
class Y {
    static X x = X.getInstance();
    static Y theinstance;
    static Y getInstance() {if(theinstance==null) {theinstance = new Y();} return theinstance;}
}

public class A {
    public static void main(String[] p) {
    System.out.println("x:"+X.getInstance()+" y:"+Y.getInstance());
    System.out.println("x:"+Y.x+" y:"+X.y);
    }
}

ve bu kod anomali göstermez:

$ javac A.java 
$ java A
x:X@1c059f6 y:Y@152506e
x:X@1c059f6 y:Y@152506e

ANCAK bu Android Applicationnesnesi için bir seçenek değildir : programcı oluşturulduğu zamanı kontrol etmez.

Bir kez daha: ilk örnek ile ikincisi arasındaki fark, ikinci örneğin statik işaretçi boşsa bir örnek oluşturmasıdır. Ama bir programcı oluşturamıyor sistem bunu yapmaya karar önce Android uygulama nesnesi.

GÜNCELLEME

Başlatılan statik alanların olduğu bir şaşırtıcı örnek daha null.

Main.java :

enum MyEnum {
    FIRST,SECOND;
    private static String prefix="<", suffix=">";
    String myName;
    MyEnum() {
        myName = makeMyName();
    }
    String makeMyName() {
        return prefix + name() + suffix;
    }
    String getMyName() {
        return myName;
    }
}
public class Main {
    public static void main(String args[]) {
        System.out.println("first: "+MyEnum.FIRST+" second: "+MyEnum.SECOND);
        System.out.println("first: "+MyEnum.FIRST.makeMyName()+" second: "+MyEnum.SECOND.makeMyName());
        System.out.println("first: "+MyEnum.FIRST.getMyName()+" second: "+MyEnum.SECOND.getMyName());
    }
}

Ve şunu elde edersiniz:

$ javac Main.java
$ java Main
first: FIRST second: SECOND
first: <FIRST> second: <SECOND>
first: nullFIRSTnull second: nullSECONDnull

Statik değişken bildirimini bir satır üste taşıyamayacağınızı, kodun derlenmeyeceğini unutmayın.


3
Yararlı örnek; Böyle bir delik olduğunu bilmek güzel. Bundan ne alıyorum biri herhangi bir sınıf statik başlatma sırasında böyle bir statik değişken başvurmaktan kaçınmalıdır olmasıdır.
ToolmakerSteve

10

Uygulama Sınıfı:

import android.app.Application;
import android.content.Context;

public class MyApplication extends Application {

    private static Context mContext;

    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
    }

    public static Context getAppContext() {
        return mContext;
    }

}

AndroidManifest'te Uygulamayı Bildirin:

<application android:name=".MyApplication"
    ...
/>

Kullanımı:

MyApplication.getAppContext()

1
Bellek sızıntılarına eğilimli. Bunu asla yapmamalısın.
Dragas

9

Application Context'i almak için bir sarıcı oluşturmaya çalışıyorsunuz ve " null" işaretçisini döndürme olasılığı var .

Anladığım kadarıyla, 2 Context.getApplicationContext() veya herhangi birini çağırmak için daha iyi bir yaklaşım sanırım Activity.getApplication().


13
ne zaman boş dönmeli?
Takılma

25
Fark ettiğim statik Context.getApplicationContext () yöntemi yok. Bir şey mi kaçırıyorum?
dalcantara

Ayrıca benim uygulamada aynı yaklaşımı uygulamak, ancak SQLiteOpenHelper çağrıldığında, boş işaretçi döndürür. Bu tür bir durum için herhangi bir cevap.
ashutosh

2
Uygulamadan önce yüklenen bir içerik sağlayıcıda SQLiteOpenHelper'ı çağırırsanız bu durum söz konusu olabilir.
Gunnar Bernstein

5

İyi bir yaklaşım. Ben de kullanıyorum. Yalnızca onCreatebir yapıcı kullanmak yerine singleton ayarlamak için geçersiz kılmayı öneririm .

Ve bahsettiğinizden beri SQLiteOpenHelper: onCreate ()Veritabanını da açabilirsiniz.

Şahsen belgelerin yanlış söylediklerini düşünüyorum normalde Uygulama alt sınıfına gerek olmadığını . Ben bunun tam tersi olduğunu düşünüyorum: Her zaman Uygulama alt sınıf gerekir.


3

Yapıcıda bir Sistem Hizmeti almak için Uygulama Bağlamını kullanırdım. Bu, testi kolaylaştırır ve kompozisyondan yararlanır

public class MyActivity extends Activity {

    private final NotificationManager notificationManager;

    public MyActivity() {
       this(MyApp.getContext().getSystemService(NOTIFICATION_SERVICE));
    }

    public MyActivity(NotificationManager notificationManager) {
       this.notificationManager = notificationManager;
    }

    // onCreate etc

}

Test sınıfı daha sonra aşırı yüklenmiş yapıcıyı kullanır.

Android varsayılan yapıcıyı kullanır.


1

Beğendim, ancak bunun yerine bir singleton öneririm:

package com.mobidrone;

import android.app.Application;
import android.content.Context;

public class ApplicationContext extends Application
{
    private static ApplicationContext instance = null;

    private ApplicationContext()
    {
        instance = this;
    }

    public static Context getInstance()
    {
        if (null == instance)
        {
            instance = new ApplicationContext();
        }

        return instance;
    }
}

31
Android.app.application genişletme zaten singleton garanti bu yüzden bu gereksiz
Vincent

8
Etkinlik dışı sınıflardan erişim istiyorsanız ne olur?
Maxrunner

9
newUygulamayı asla kendiniz yapmamalısınız (olası birim testi hariç). İşletim sistemi bunu yapacaktır. Bir kurucunuz da olmamalı. Bunun için budur onCreate.
Martin

@Vincent: Bu konuda bir bağlantı gönderebilir misin? tercihen kod - burada soruyorum: stackoverflow.com/questions/19365797/…
Mr_and_Mrs_D

@radzio neden yapıcıda yapmamalıyız?
Miha_x64

1

Aynı yaklaşımı kullanıyorum, singleton'u biraz daha iyi yazmanızı öneririm:

public static MyApp getInstance() {

    if (instance == null) {
        synchronized (MyApp.class) {
            if (instance == null) {
                instance = new MyApp ();
            }
        }
    }

    return instance;
}

ama her yerde kullanmıyorum, kullanıyorum getContext()ve getApplicationContext()nerede yapabileceğim!


Bu yüzden, lütfen cevabı neden düşürdüğünüzü açıklamak için bir yorum yazın, böylece anlayayım. Tekton yaklaşımı, faaliyetler veya görüş organı dışında geçerli bir bağlam elde etmek için yaygın olarak kullanılmaktadır ...
Seraphim'in

1
İşletim sistemi, Uygulamanın tam olarak bir kez başlatılmasını sağladığı için gerek yoktur. Varsa Singelton'u onCreate () olarak ayarlamanızı öneririm.
Martin

1
Tembel bir ipucu için güvenli bir yol, bir singleton başlatmak, ancak burada gerekli değildir.
naXa

2
Vay canına, insanların sonunda çift kontrollü kilitleme kullanmayı bıraktıklarını düşündüğümde ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen
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.