Menggunakan konteks Aplikasi di mana saja?

476

Dalam aplikasi Android, apakah ada yang salah dengan pendekatan berikut:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

dan menyebarkannya di mana-mana (misalnya SQLiteOpenHelper) di mana konteks diperlukan (dan tentu saja tidak bocor)?

yanchenko
sumber
23
Hanya untuk menguraikan untuk orang lain menerapkan ini, maka Anda dapat memodifikasi <application>node file AndroidManifest.xml untuk menyertakan definisi atribut berikut: android:name="MyApp". MyApp harus di bawah paket yang sama dengan referensi manifes Anda.
Matt Huggins
6
Cara yang mengagumkan untuk mengatasi masalah memasok konteks ke SQLiteOpenHelper !! Saya telah mengimplementasikan singleton "SQLiteManager" dan terjebak pada "bagaimana F saya bisa mendapatkan konteks ke singleton?"
Seseorang di suatu tempat
8
Supaya Anda tahu Anda mengembalikan aplikasi Anda dengan salah satu antarmuka supernya, jadi jika Anda memberikan metode tambahan dalam MyApp Anda tidak akan dapat menggunakannya. GetContext () Anda seharusnya memiliki jenis pengembalian MyApp, dan dengan cara itu Anda dapat menggunakan metode yang ditambahkan nanti, serta semua metode di ContextWrapper dan Context.
5
Lihat juga goo.gl/uKcFn - ini adalah balasan lain yang terkait dengan pos serupa. Lebih baik mengatur variabel statis di onCreate dan bukan c'tor.
AlikElzin-kilaka
1
@ChuongPham Jika kerangka kerja telah membunuh aplikasi Anda, tidak akan ada yang mengakses konteks nol ...
Kevin Krumwiede

Jawaban:

413

Ada beberapa masalah potensial dengan pendekatan ini, meskipun dalam banyak keadaan (seperti contoh Anda) itu akan bekerja dengan baik.

Khususnya Anda harus berhati-hati ketika berurusan dengan apa pun yang berhubungan dengan GUIyang membutuhkan Context. Misalnya, jika Anda memasukkan Konteks aplikasi ke dalam LayoutInflaterAnda akan mendapatkan Pengecualian. Secara umum, pendekatan Anda sangat baik: ini adalah praktik yang baik untuk menggunakan di Activity's Contextdalam itu Activity, dan Application Contextketika melewati konteks di luar ruang lingkup Activityuntuk menghindari kebocoran memori .

Juga, sebagai alternatif untuk pola Anda Anda dapat menggunakan shortcut memanggil getApplicationContext()pada Contextobjek (seperti Activity) untuk mendapatkan Konteks Aplikasi.

Reto Meier
sumber
22
Terima kasih atas jawaban yang menginspirasi. Saya pikir saya akan menggunakan pendekatan ini semata-mata untuk lapisan kegigihan (karena saya tidak ingin menggunakan penyedia konten). Bertanya-tanya apa motivasi di balik mendesain SQLiteOpenHelper dengan cara yang mengharapkan suatu Konteks akan disediakan alih-alih memperolehnya dari Aplikasi itu sendiri. PS Dan bukumu hebat!
yanchenko
7
Menggunakan konteks aplikasi LayoutInflatorhanya dengan bekerja untuk saya. Pasti sudah berubah dalam tiga tahun terakhir.
Jacob Phillips
5
@JacobPhillips Menggunakan LayoutInflator tanpa konteks aktivitas akan kehilangan gaya Kegiatan itu. Jadi itu akan bekerja di satu sisi, tetapi tidak yang lain.
Markus
1
@MarkCarter Apakah maksud Anda menggunakan Konteks Aplikasi akan kehilangan gaya Kegiatan?
Jacob Phillips
1
@JacobPhillips ya, Konteks Aplikasi tidak dapat memiliki gaya karena setiap Kegiatan dapat ditata dengan cara yang berbeda.
Markus
28

Dalam pengalaman saya, pendekatan ini seharusnya tidak perlu. Jika Anda memerlukan konteks untuk apa pun, Anda biasanya dapat memperolehnya melalui panggilan ke View.getContext () dan menggunakan yang Contextdiperoleh di sana, Anda dapat menghubungi Context.getApplicationContext () untuk mendapatkan Applicationkonteks. Jika Anda mencoba untuk mendapatkan Applicationkonteks ini dari sebuah, ActivityAnda selalu dapat memanggil Activity.getApplication () yang seharusnya dapat diteruskan sesuai Contextkebutuhan untuk panggilan SQLiteOpenHelper().

Secara keseluruhan sepertinya tidak ada masalah dengan pendekatan Anda untuk situasi ini, tetapi ketika berhadapan dengan Contextpastikan Anda tidak membocorkan memori di mana pun seperti yang dijelaskan di blog resmi Google Android Developers .

snctln
sumber
13

Beberapa orang bertanya: bagaimana mungkin singleton mengembalikan pointer nol? Saya menjawab pertanyaan itu. (Saya tidak bisa menjawab dalam komentar karena saya perlu mengirim kode.)

Ini dapat mengembalikan nol di antara dua peristiwa: (1) kelas dimuat, dan (2) objek kelas ini dibuat. Ini sebuah contoh:

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);
    }
}

Mari kita jalankan kodenya:

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

Baris kedua menunjukkan bahwa Y.xinstance dan X.yinstance adalah null ; mereka nol karena variabel X.xinstance ans Y.yinstance dibaca ketika mereka nol.

Bisakah ini diperbaiki? Iya,

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);
    }
}

dan kode ini tidak menunjukkan anomali:

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

TAPI ini bukan opsi untuk Applicationobjek Android : programmer tidak mengontrol waktu ketika itu dibuat.

Sekali lagi: perbedaan antara contoh pertama dan yang kedua adalah bahwa contoh kedua menciptakan sebuah instance jika pointer statis adalah nol. Tapi programmer tidak dapat membuat satu objek aplikasi Android sebelum sistem memutuskan untuk melakukannya.

MEMPERBARUI

Satu lagi contoh membingungkan di mana bidang statis yang diinisialisasi terjadi 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());
    }
}

Dan Anda mendapatkan:

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

Perhatikan bahwa Anda tidak dapat memindahkan deklarasi variabel statis satu baris ke atas, kode tidak akan dikompilasi.

18446744073709551615
sumber
3
Contoh yang berguna; itu baik untuk mengetahui bahwa ada lubang seperti itu. Apa yang saya ambil dari ini adalah bahwa seseorang harus menghindari merujuk ke variabel statis seperti itu selama inisialisasi statis kelas apa pun.
ToolmakerSteve
10

Kelas Aplikasi:

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;
    }

}

Deklarasikan Aplikasi di AndroidManifest:

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

Pemakaian:

MyApplication.getAppContext()
untuk Ha
sumber
1
Rawan kebocoran memori. Anda seharusnya tidak pernah melakukan ini.
Dragas
9

Anda mencoba membuat pembungkus untuk mendapatkan Konteks Aplikasi dan ada kemungkinan " null" itu mengembalikan pointer.

Sesuai pemahaman saya, saya kira pendekatan yang lebih baik untuk memanggil-salah satu dari 2 Context.getApplicationContext() atau Activity.getApplication().

Prasanta
sumber
13
Kapan seharusnya mengembalikan nol?
Terjebak
25
Tidak ada metode Context.getApplicationContext () statis yang saya sadari. Apakah saya melewatkan sesuatu?
dalcantara
Saya juga menerapkan pendekatan yang sama dalam aplikasi saya, tetapi ketika memanggil SQLiteOpenHelper, ia mengembalikan null pointer. Jawaban apa pun untuk situasi seperti ini.
ashutosh
2
Ini mungkin terjadi jika Anda memanggil SQLiteOpenHelper dalam penyedia konten yang dimuat sebelum aplikasi.
Gunnar Bernstein
5

Itu pendekatan yang bagus. Saya menggunakannya sendiri juga. Saya hanya akan menyarankan untuk mengganti onCreatemengatur singleton daripada menggunakan konstruktor.

Dan karena Anda menyebutkan SQLiteOpenHelper: onCreate ()Anda dapat membuka database juga.

Secara pribadi saya pikir dokumentasi salah dengan mengatakan bahwa Biasanya tidak perlu untuk subkelas Aplikasi . Saya pikir kebalikannya benar: Anda harus selalu subkelas Aplikasi.

Martin
sumber
3

Saya akan menggunakan Konteks Aplikasi untuk mendapatkan Layanan Sistem di konstruktor. Ini memudahkan pengujian & manfaat dari komposisi

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

}

Kelas uji kemudian akan menggunakan konstruktor yang kelebihan beban.

Android akan menggunakan konstruktor default.

Blundell
sumber
1

Saya suka, tapi saya akan menyarankan singleton sebagai gantinya:

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;
    }
}
Franklin Peña
sumber
31
Memperluas aplikasi android.app. sudah menjamin singleton jadi ini tidak perlu
Vincent
8
Bagaimana jika Anda ingin akses dari kelas non-aktivitas?
Maxrunner
9
Anda tidak boleh newmenggunakan Aplikasi sendiri (dengan kemungkinan pengujian unit). Sistem operasi akan melakukan itu. Anda seharusnya juga tidak memiliki konstruktor. Itu untuk apa onCreate.
Martin
@Incent: dapatkah Anda memposting beberapa tautan tentang ini? lebih disukai kode - Saya bertanya di sini: stackoverflow.com/questions/19365797/…
Mr_and_Mrs_D
@radzio mengapa kita tidak melakukannya di konstruktor?
Miha_x64
1

Saya menggunakan pendekatan yang sama, saya sarankan untuk menulis singleton sedikit lebih baik:

public static MyApp getInstance() {

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

    return instance;
}

tapi saya tidak menggunakan di mana-mana, saya menggunakan getContext()dan di getApplicationContext()mana saya bisa melakukannya!

Seraphim
sumber
Jadi, tolong tulis komentar untuk menjelaskan mengapa Anda menurunkan jawaban sehingga saya bisa mengerti. Pendekatan tunggal digunakan secara luas untuk mendapatkan konteks yang valid di luar kegiatan atau pandangan tubuh ...
Seraphim
1
Tidak perlu karena sistem operasi memastikan bahwa Aplikasi dipakai tepat sekali. Jika ada saya sarankan untuk mengatur Singelton di onCreate ().
Martin
1
Cara aman yang baik untuk malas menginisialisasi singleton, tetapi tidak perlu di sini.
naXa
2
Wow, tepat ketika saya berpikir orang akhirnya berhenti menggunakan penguncian dua kali ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen