Lajang vs Konteks Aplikasi di Android?


362

Mengingat posting ini menyebutkan beberapa masalah dalam menggunakan lajang dan setelah melihat beberapa contoh aplikasi Android menggunakan pola lajang, saya bertanya-tanya apakah itu ide yang baik untuk menggunakan Singletons alih-alih satu contoh tunggal dibagi melalui keadaan aplikasi global (subclassing android.os.Application dan mendapatkannya melalui context.getApplication ()).

Apa kelebihan / kekurangan yang dimiliki kedua mekanisme?

Sejujurnya, saya mengharapkan jawaban yang sama dalam posting ini pola Singleton dengan aplikasi Web, Bukan ide yang bagus! tetapi diterapkan pada Android. Apakah saya benar? Apa yang berbeda di DalvikVM?

EDIT: Saya ingin memiliki pendapat tentang beberapa aspek yang terlibat:

  • Sinkronisasi
  • Dapat digunakan kembali
  • Pengujian

Jawaban:


295

Saya sangat tidak setuju dengan jawaban Dianne Hackborn. Kami sedikit demi sedikit menghapus semua lajang dari proyek kami demi benda-benda yang memiliki tugas ringan, yang dapat dengan mudah dibuat kembali ketika Anda benar-benar membutuhkannya.

Singleton adalah mimpi buruk untuk pengujian dan, jika malas diinisialisasi, akan memperkenalkan "keadaan ketidakpastian" dengan efek samping yang halus (yang mungkin tiba-tiba muncul ketika memindahkan panggilan getInstance()dari satu lingkup ke lingkup lain). Visibilitas telah disebutkan sebagai masalah lain, dan karena lajang menyiratkan akses "global" (= acak) ke status bersama, bug halus dapat muncul ketika tidak disinkronkan dengan benar dalam aplikasi bersamaan.

Saya menganggapnya sebagai anti-pola, itu adalah gaya berorientasi objek yang buruk yang pada dasarnya berarti mempertahankan negara global.

Untuk kembali ke pertanyaan Anda:

Meskipun konteks aplikasi dapat dianggap sebagai singleton sendiri, ia dikelola oleh kerangka kerja dan memiliki siklus hidup , ruang lingkup, dan jalur akses yang jelas. Karena itu saya percaya bahwa jika Anda perlu mengelola negara aplikasi-global, itu harus pergi ke sini, ke tempat lain. Untuk hal lain, pikirkan kembali apakah Anda benar - benar membutuhkan objek singleton, atau jika mungkin juga untuk menulis ulang kelas singleton Anda sebagai gantinya instantiate objek kecil, berumur pendek yang melakukan tugas di tangan.


131
Jika Anda merekomendasikan Aplikasi, Anda merekomendasikan penggunaan lajang. Jujur saja, tidak ada jalan lain. Aplikasi adalah singleton, dengan semantik crappier. Saya tidak akan masuk ke argumen agama tentang lajang sesuatu yang tidak boleh Anda gunakan. Saya lebih suka bersikap praktis - ada tempat-tempat di mana mereka adalah pilihan yang baik untuk mempertahankan kondisi per-proses dan dapat menyederhanakan hal-hal dengan melakukannya, dan Anda dapat menggunakannya dengan baik dalam situasi yang salah dan menembak diri sendiri di kaki.
hackbod

18
Benar, dan saya memang menyebutkan bahwa "konteks aplikasi dapat dianggap sebagai singleton sendiri". Perbedaannya adalah bahwa dengan instance aplikasi, menembak diri sendiri jauh lebih sulit, karena siklus hidupnya ditangani oleh kerangka kerja. Kerangka kerja DI seperti Guice, Hivemind, atau Spring juga menggunakan lajang, tetapi itu adalah detail implementasi yang seharusnya tidak dipedulikan pengembang. Saya pikir umumnya lebih aman untuk mengandalkan kerangka kerja semantik yang diimplementasikan dengan benar daripada kode Anda sendiri. Ya, saya akui saya lakukan! :-)
Matthias

93
Jujur itu tidak mencegah Anda menembak diri sendiri seperti halnya seorang singleton. Agak membingungkan, tetapi tidak ada siklus hidup Aplikasi. Itu dibuat ketika aplikasi Anda mulai (sebelum salah satu komponennya dipakai) dan onCreate () yang dipanggil pada saat itu, dan ... itu saja. Itu duduk di sana dan hidup selamanya, sampai prosesnya terbunuh. Persis seperti seorang singleton. :)
hackbod

30
Oh satu hal yang mungkin membingungkan ini - Android sangat dirancang untuk menjalankan aplikasi dalam proses, dan mengelola siklus hidup dari proses tersebut. Jadi pada Android lajang adalah cara yang sangat alami untuk mengambil keuntungan dari manajemen proses - jika Anda ingin menembolokkan sesuatu dalam proses Anda sampai platform perlu merebut kembali memori proses untuk sesuatu yang lain, menempatkan keadaan itu dalam singleton akan melakukan itu.
hackbod

7
Oke cukup adil. Saya hanya bisa mengatakan bahwa saya belum melihat ke belakang sejak kami membuat langkah menjauh dari lajang yang mengatur sendiri. Kami sekarang memilih solusi gaya DI yang ringan, di mana kami menyimpan satu singleton pabrik (RootFactory), yang pada gilirannya dikelola oleh instance aplikasi (itu adalah delegasi jika Anda mau). Singleton ini mengelola dependensi umum yang diandalkan semua komponen aplikasi, tetapi instantiasi dikelola di satu lokasi tunggal - kelas aplikasi. Sementara dengan pendekatan itu satu singleton tetap, itu terbatas pada kelas Aplikasi, jadi tidak ada modul kode lain yang tahu tentang "detail" itu.
Matthias

231

Saya sangat merekomendasikan lajang. Jika Anda memiliki seorang lajang yang membutuhkan konteks, miliki:

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

Saya lebih suka lajang daripada Aplikasi karena itu membantu menjaga aplikasi lebih terorganisir dan modular - daripada memiliki satu tempat di mana semua keadaan global Anda di seluruh aplikasi perlu dipertahankan, setiap bagian yang terpisah dapat mengurus dirinya sendiri. Juga fakta bahwa lajang malas menginisialisasi (atas permintaan) alih-alih mengarahkan Anda ke jalan melakukan semua inisialisasi di muka di Application.onCreate () baik.

Secara intrinsik tidak ada yang salah dengan menggunakan lajang. Cukup gunakan dengan benar, saat itu masuk akal. Kerangka kerja Android sebenarnya memiliki banyak dari mereka, untuk itu mempertahankan cache per-proses dari sumber daya yang dimuat dan hal-hal lain semacam itu.

Juga untuk aplikasi sederhana multithreading tidak menjadi masalah dengan lajang, karena dengan merancang semua panggilan balik standar ke aplikasi dikirim pada utas utama dari proses sehingga Anda tidak akan memiliki multi-threading terjadi kecuali Anda memperkenalkannya secara eksplisit melalui utas atau secara implisit dengan menerbitkan penyedia konten atau layanan IBinder untuk proses lain.

Hanya berpikir tentang apa yang Anda lakukan. :)


1
Jika beberapa waktu kemudian saya ingin mendengarkan acara eksternal, atau berbagi di IBinder (saya kira itu bukan aplikasi sederhana) Saya harus menambahkan penguncian ganda, sinkronisasi, mudah berubah, kan? Terima kasih atas jawaban Anda :)
mschonaker

2
Bukan untuk acara eksternal - BroadcastReceiver.onReceive () juga dipanggil di utas utama.
hackbod

2
Baik. Apakah Anda mengarahkan saya ke beberapa bahan bacaan (saya lebih suka kode) di mana saya dapat melihat mekanisme pengiriman utas utama? Saya pikir itu akan menjelaskan beberapa konsep sekaligus untuk saya. Terima kasih sebelumnya.
mschonaker

2
Ini adalah kode pengiriman sisi-aplikasi utama: android.git.kernel.org/?p=platform/frameworks/…
hackbod

8
Secara intrinsik tidak ada yang salah dengan menggunakan lajang. Cukup gunakan dengan benar, saat itu masuk akal. .. tentu saja, kata baik. Kerangka kerja Android sebenarnya memiliki banyak dari mereka, untuk itu mempertahankan cache per-proses dari sumber daya yang dimuat dan hal-hal lain semacam itu. Persis seperti yang Anda katakan. Dari teman Anda di dunia iOS, "semuanya adalah singleton" di iOS .. tidak ada yang lebih alami pada perangkat fisik selain konsep singleton: gps, jam, gyro, dll. - secara konseptual bagaimana Anda akan merekayasa selain sebagai lajang? Jadi ya.
Fattie

22

Dari: Pengembang> referensi - Aplikasi

Biasanya tidak perlu untuk subkelas Aplikasi. Dalam sebagian besar situasi, lajang statis dapat menyediakan fungsionalitas yang sama dengan cara yang lebih modular. Jika singleton Anda memerlukan konteks global (misalnya untuk mendaftarkan penerima siaran), fungsi untuk mengambilnya dapat diberikan Konteks yang secara internal menggunakan Context.getApplicationContext () ketika pertama kali membangun singleton.


1
Dan jika Anda menulis antarmuka untuk singleton, membiarkan getInstance non-statis, Anda bahkan dapat membuat konstruktor default dari kelas yang menggunakan singleton menyuntikkan singleton produksi melalui konstruktor non-standar, yang juga merupakan konstruktor yang Anda gunakan untuk menyusun kelas yang menggunakan singleton dalam tes unitnya.
android.weasel

11

Aplikasi tidak sama dengan Singleton. Alasannya adalah:

  1. Metode aplikasi (seperti onCreate) disebut di utas ui;
  2. metode singleton dapat dipanggil di utas apa pun;
  3. Dalam metode "onCreate" Aplikasi, Anda dapat membuat Instanate Handler;
  4. Jika singleton dieksekusi di utas bukan-ui, Anda tidak dapat membuat Instanate;
  5. Aplikasi memiliki kemampuan untuk mengelola siklus hidup dari aktivitas dalam aplikasi. Aplikasi ini memiliki metode "registerActivityLifecycleCallbacks". Tetapi para lajang tidak memiliki kemampuan.

1
Catatan: Anda dapat instantiate Handler pada utas apa pun. from doc: "Ketika Anda membuat Handler baru, itu terikat ke antrian utas / pesan dari utas yang membuatnya"
Christ

1
@ Chris Terima kasih! Baru saja saya belajar "mekanisme Looper". Jika pengendali instantiate pada utas none-ui tanpa kode 'Looper.prepare ()', sistem akan melaporkan kesalahan "java.lang.RuntimeException: Dapatkah dapat membuat handler di dalam utas yang belum disebut Looper.prepare () ".
sunhang

11

Saya memiliki masalah yang sama: Singleton atau membuat aplikasi subclass android.os.Application?

Pertama saya mencoba dengan Singleton tetapi aplikasi saya pada beberapa titik melakukan panggilan ke browser

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

dan masalahnya adalah, jika handset tidak memiliki cukup memori, sebagian besar kelas Anda (bahkan Singleton) dibersihkan untuk mendapatkan memori sehingga, ketika kembali dari browser ke aplikasi saya, itu macet setiap kali.

Solusi: letakkan data yang diperlukan di dalam subkelas kelas Aplikasi.


1
Saya sering menemukan posting di mana orang menyatakan bahwa ini mungkin terjadi. Karena itu saya cukup melampirkan objek ke aplikasi seperti lajang dengan pemuatan malas dll. Hanya untuk memastikan bahwa siklus hidup didokumentasikan dan diketahui. Pastikan untuk tidak menyimpan ratusan gambar ke objek aplikasi Anda karena saya mengerti itu tidak akan dihapus dari memori jika aplikasi Anda ada di latar belakang dan semua aktivitas dihancurkan untuk membebaskan memori untuk proses lain.
Janusz

Nah, Singleton malas memuat setelah aplikasi restart bukan cara yang tepat untuk membiarkan benda-benda tersapu oleh GC Referensi Lemah, kan?
mschonaker

15
Betulkah? Dalvik menurunkan kelas dan kehilangan status program? Apakah Anda yakin itu bukan sampah yang mengumpulkan semacam siklus terbatas objek yang berhubungan dengan Aktivitas yang Anda seharusnya tidak menempatkan ke lajang di tempat pertama? Anda harus memberikan contoh clrar untuk klaim luar biasa seperti itu!
android.weasel

1
Kecuali ada perubahan yang saya tidak sadari, Dalvik tidak membongkar kelas. Pernah. Perilaku yang mereka lihat adalah proses mereka dibunuh di latar belakang untuk memberi ruang bagi browser. Mereka mungkin menginisialisasi variabel dalam aktivitas "utama" mereka, yang mungkin tidak dibuat dalam proses baru ketika kembali dari browser.
Groxx

5

Pertimbangkan keduanya sekaligus:

  • memiliki objek tunggal sebagai contoh statis di dalam kelas.
  • memiliki kelas umum (Konteks) yang mengembalikan instance singleton untuk semua objek singelton dalam aplikasi Anda, yang memiliki keuntungan bahwa nama metode dalam Konteks akan bermakna misalnya: context.getLoggedinUser () alih-alih User.getInstance ().

Selain itu, saya sarankan Anda memperluas Konteks Anda untuk mencakup tidak hanya akses ke objek tunggal tetapi beberapa fungsi yang perlu diakses secara global, seperti misalnya: context.logOffUser (), context.readSavedData (), dll. Mungkin mengubah nama Konteks untuk Fasad akan masuk akal kalau begitu.


4

Mereka sebenarnya sama. Ada satu perbedaan yang bisa saya lihat. Dengan kelas Aplikasi Anda dapat menginisialisasi variabel Anda di Application.onCreate () dan menghancurkannya di Application.onTerminate (). Dengan singleton Anda harus mengandalkan VM menginisialisasi dan menghancurkan statika.


16
dokumen untuk onTerminate mengatakan bahwa itu hanya dipanggil oleh emulator. Pada perangkat metode itu mungkin tidak akan dipanggil. developer.android.com/reference/android/app/…
danb

3

2 sen saya:

Saya memperhatikan bahwa beberapa bidang tunggal / statis diatur ulang ketika aktivitas saya dihancurkan. Saya perhatikan ini pada beberapa perangkat low-end 2.3.

Kasus saya sangat sederhana: Saya hanya meminta private mengajukan "init_done" dan metode statis "init" yang saya panggil dari activity.onCreate (). Saya perhatikan bahwa metode init mengeksekusi kembali dirinya sendiri pada beberapa penciptaan kembali aktivitas.

Meskipun saya tidak dapat membuktikan afirmasi saya, ini mungkin terkait dengan KAPAN singleton / class diciptakan / digunakan terlebih dahulu. Ketika aktivitas hancur / didaur ulang, tampaknya semua kelas yang hanya dirujuk aktivitas ini juga didaur ulang.

Saya memindahkan instance singleton saya ke subkelas Aplikasi. Saya mengaksesnya dari instance aplikasi. dan, sejak itu, tidak melihat masalah lagi.

Saya harap ini bisa membantu seseorang.


3

Dari mulut pepatah kuda ...

Saat mengembangkan aplikasi Anda, Anda mungkin perlu berbagi data, konteks, atau layanan secara global di seluruh aplikasi Anda. Misalnya, jika aplikasi Anda memiliki data sesi, seperti pengguna yang saat ini masuk, Anda mungkin ingin mengekspos informasi ini. Di Android, pola untuk menyelesaikan masalah ini adalah membuat instance android.app.Application Anda memiliki semua data global, dan kemudian memperlakukan instance Aplikasi Anda sebagai singleton dengan akses statis ke berbagai data dan layanan.

Saat menulis aplikasi Android, Anda dijamin hanya memiliki satu instance kelas android.app.Application, dan karenanya aman (dan direkomendasikan oleh tim Android Google) untuk memperlakukannya sebagai singleton. Artinya, Anda dapat dengan aman menambahkan metode getInstance () statis ke implementasi Aplikasi Anda. Seperti itu:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

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

2

Panggilan aktivitas saya selesai () (yang tidak menyelesaikannya dengan segera, tetapi pada akhirnya akan dilakukan) dan memanggil Google Street Viewer. Ketika saya men-debug-nya di Eclipse, koneksi saya ke aplikasi terputus ketika Street Viewer dipanggil, yang saya pahami ketika aplikasi (keseluruhan) ditutup, yang seharusnya membebaskan memori (karena satu aktivitas yang sedang diselesaikan tidak seharusnya menyebabkan perilaku ini) . Namun demikian, saya dapat menyimpan status dalam sebuah Bundel melalui onSaveInstanceState () dan mengembalikannya dalam metode onCreate () dari aktivitas selanjutnya di stack. Baik dengan menggunakan Aplikasi statis singleton atau subclassing saya menghadapi aplikasi menutup dan kehilangan negara (kecuali saya menyimpannya dalam Bundel). Jadi dari pengalaman saya mereka sama dengan pelestarian negara. Saya perhatikan bahwa koneksi terputus di Android 4.1.2 dan 4.2.2 tetapi tidak pada 4.0.7 atau 3.2.4,


"Saya perhatikan bahwa koneksi terputus di Android 4.1.2 dan 4.2.2 tetapi tidak pada 4.0.7 atau 3.2.4, yang menurut saya menunjukkan bahwa mekanisme pemulihan memori telah berubah di beberapa titik." ..... Saya rasa perangkat Anda tidak memiliki jumlah memori yang sama, atau aplikasi yang sama terpasang. dan karena itu kesimpulan Anda mungkin salah
Christ

@ Chris: Ya, Anda pasti benar. Akan aneh jika mekanisme pemulihan memori berubah antar versi. Mungkin penggunaan memori yang berbeda menyebabkan perilaku yang berbeda.
Piovezan
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.