Haruskah mengakses SharedPreferences dilakukan di luar UI Thread?

113

Dengan dirilisnya Gingerbread, saya telah bereksperimen dengan beberapa API baru, salah satunya adalah StrictMode .

Saya perhatikan bahwa salah satu peringatan adalah untuk getSharedPreferences().

Ini peringatannya:

StrictMode policy violation; ~duration=1949 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=23 violation=2

dan itu diberikan untuk getSharedPreferences()panggilan yang dilakukan di UI thread.

Haruskah SharedPreferencesakses dan perubahan benar-benar dibuat dari UI thread?

cottonBallPaws
sumber
Saya selalu melakukan operasi preferensi saya pada utas UI. Meskipun saya rasa itu masuk akal karena ini adalah operasi IO
Falmarri

Jawaban:

184

Saya senang Anda sudah memainkannya!

Beberapa hal yang perlu diperhatikan: (dalam bentuk peluru malas)

  • jika ini adalah masalah terburuk Anda, aplikasi Anda mungkin berada di posisi yang tepat. :) Penulisan biasanya lebih lambat daripada membaca, jadi pastikan Anda menggunakan SharedPreferenced $ Editor.apply () daripada commit (). apply () baru dalam GB dan asinkron (tetapi selalu aman, berhati-hatilah dengan transisi siklus proses). Anda dapat menggunakan refleksi untuk memanggil apply () secara bersyarat pada GB + dan commit () pada Froyo atau lebih rendah. Saya akan membuat posting blog dengan kode contoh bagaimana melakukan ini.

Mengenai pemuatan, meskipun ...

  • setelah dimuat, SharedPreferences adalah lajang dan seluruh proses yang di-cache. jadi Anda ingin memuatnya sedini mungkin agar Anda memilikinya di memori sebelum Anda membutuhkannya. (dengan asumsi itu kecil, sebagaimana mestinya jika Anda menggunakan SharedPreferences, file XML sederhana ...) Anda tidak ingin menyalahkannya di masa mendatang beberapa pengguna mengklik tombol.

  • tetapi setiap kali Anda memanggil context.getSharedPreferences (...), file XML pendukung adalah stat untuk melihat apakah itu berubah, jadi Anda tetap ingin menghindari statistik tersebut selama kejadian UI. Stat biasanya harus cepat (dan sering di-cache), tetapi yaffs tidak memiliki banyak konkurensi (dan banyak perangkat Android berjalan di yaffs ... Droid, Nexus One, dll.) Jadi jika Anda menghindari disk , Anda menghindari terjebak di belakang operasi disk lain dalam penerbangan atau tertunda.

  • jadi Anda mungkin ingin memuat SharedPreferences selama onCreate () dan menggunakan kembali instance yang sama, menghindari stat.

  • tetapi jika Anda tetap tidak memerlukan preferensi Anda selama onCreate (), waktu pemuatan itu menunda pengaktifan aplikasi Anda secara tidak perlu, jadi biasanya lebih baik memiliki sesuatu seperti subkelas <SharedPreferences> FutureTask yang memulai utas baru ke .set () nilai subkelas FutureTask. Kemudian cukup cari anggota FutureTask <SharedPreferences> Anda kapan pun Anda membutuhkannya dan .get (). Saya berencana untuk membuat ini gratis di belakang layar di Honeycomb, secara transparan. Saya akan mencoba merilis beberapa kode contoh yang menunjukkan praktik terbaik di bidang ini.

Periksa blog Pengembang Android untuk postingan mendatang tentang subjek terkait StrictMode dalam beberapa minggu mendatang.

Brad Fitzpatrick
sumber
Wow, tidak berharap mendapatkan jawaban yang begitu jelas langsung dari sumbernya! Terimakasih banyak!
cottonBallPaws
9
Untuk kepentingan pembaca baru dari posting yang luar biasa ini, temukan di bawah link ke posting blog yang disebutkan di atas oleh @Brad Fitzpatrick: posting blog pengembang android pada mode ketat oleh Brad . Postingan juga memiliki link ke kode contoh untuk menggunakan apply (dari gingerbread dan seterusnya) atau commit (froyo) berdasarkan versi android untuk menyimpan sharedpreferences: [gunakan bersyarat berlaku atau komit] ( code.google.com/p/zippy-android / source / browse / trunk / contoh /… )
tony m
4
Apakah ini masih relevan di ICS \ JB?
ekatz
5

Mengakses preferensi bersama dapat memakan waktu cukup lama karena preferensi tersebut dibaca dari penyimpanan flash. Apakah kamu banyak membaca? Mungkin Anda bisa menggunakan format yang berbeda, misalnya database SQLite.

Tapi jangan perbaiki semua yang Anda temukan menggunakan StrictMode. Atau mengutip dokumentasi:

Tetapi jangan merasa harus memperbaiki semua yang ditemukan StrictMode. Secara khusus, banyak kasus akses disk sering kali diperlukan selama siklus hidup aktivitas normal. Gunakan StrictMode untuk menemukan hal-hal yang Anda lakukan secara tidak sengaja. Permintaan jaringan di thread UI hampir selalu menjadi masalah.

mreichelt.dll
sumber
6
Tetapi bukankah SQLite juga merupakan file yang harus dibaca dari penyimpanan flash - tetapi yang lebih besar dan lebih rumit dibandingkan dengan file preferensi. Saya telah mengasumsikan bahwa, untuk jumlah data yang terkait dengan preferensi, file preferensi akan jauh lebih cepat daripada database SQLite.
Tom
Itu benar. Seperti yang sudah disebutkan oleh Brad, ini hampir selalu tidak menjadi masalah - dan dia juga menyebutkan bahwa sebaiknya memuat SharedPreferences sekali (bahkan mungkin di Thread menggunakan FutureTask) dan menyimpannya untuk kemungkinan akses ke satu instance.
mreichelt
5

Satu kehalusan tentang jawaban Brad: bahkan jika Anda memuat SharedPreferences di onCreate (), Anda mungkin masih harus membaca nilai di thread latar belakang karena blok getString () dll. Hingga membaca preferensi file bersama di selesai (di thread latar belakang):

public String getString(String key, String defValue) {
    synchronized (this) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

edit () juga memblokir dengan cara yang sama, meskipun apply () tampaknya aman di thread latar depan.

(BTW maaf untuk meletakkan ini di sini. Saya akan meletakkan ini sebagai komentar untuk jawaban Brad, tapi saya baru saja bergabung dan tidak memiliki reputasi yang cukup untuk melakukannya.)

Tom O'Neill
sumber
1

Saya tahu ini pertanyaan lama tetapi saya ingin berbagi pendekatan saya. Saya memiliki waktu membaca yang lama dan menggunakan kombinasi preferensi bersama dan kelas aplikasi global:

ApplicationClass:

public class ApplicationClass extends Application {

    private LocalPreference.Filter filter;

    public LocalPreference.Filter getFilter() {
       return filter;
    }

    public void setFilter(LocalPreference.Filter filter) {
       this.filter = filter;
    }
}

LocalPreference:

public class LocalPreference {

    public static void saveLocalPreferences(Activity activity, int maxDistance, int minAge,
                                            int maxAge, boolean showMale, boolean showFemale) {

        Filter filter = new Filter();
        filter.setMaxDistance(maxDistance);
        filter.setMinAge(minAge);
        filter.setMaxAge(maxAge);
        filter.setShowMale(showMale);
        filter.setShowFemale(showFemale);

        BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
        babysitApplication.setFilter(filter);

        SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
        securePreferences.edit().putInt(Preference.FILER_MAX_DISTANCE.toString(), maxDistance).apply();
        securePreferences.edit().putInt(Preference.FILER_MIN_AGE.toString(), minAge).apply();
        securePreferences.edit().putInt(Preference.FILER_MAX_AGE.toString(), maxAge).apply();
        securePreferences.edit().putBoolean(Preference.FILER_SHOW_MALE.toString(), showMale).apply();
        securePreferences.edit().putBoolean(Preference.FILER_SHOW_FEMALE.toString(), showFemale).apply();
    }

    public static Filter getLocalPreferences(Activity activity) {

        BabysitApplication babysitApplication = (BabysitApplication) activity.getApplication();
        Filter applicationFilter = babysitApplication.getFilter();

        if (applicationFilter != null) {
            return applicationFilter;
        } else {
            Filter filter = new Filter();
            SecurePreferences securePreferences = new SecurePreferences(activity.getApplicationContext());
            filter.setMaxDistance(securePreferences.getInt(Preference.FILER_MAX_DISTANCE.toString(), 20));
            filter.setMinAge(securePreferences.getInt(Preference.FILER_MIN_AGE.toString(), 15));
            filter.setMaxAge(securePreferences.getInt(Preference.FILER_MAX_AGE.toString(), 50));
            filter.setShowMale(securePreferences.getBoolean(Preference.FILER_SHOW_MALE.toString(), true));
            filter.setShowFemale(securePreferences.getBoolean(Preference.FILER_SHOW_FEMALE.toString(), true));
            babysitApplication.setFilter(filter);
            return filter;
        }
    }

    public static class Filter {
        private int maxDistance;
        private int minAge;
        private int maxAge;
        private boolean showMale;
        private boolean showFemale;

        public int getMaxDistance() {
            return maxDistance;
        }

        public void setMaxDistance(int maxDistance) {
            this.maxDistance = maxDistance;
        }

        public int getMinAge() {
            return minAge;
        }

        public void setMinAge(int minAge) {
            this.minAge = minAge;
        }

        public int getMaxAge() {
            return maxAge;
        }

        public void setMaxAge(int maxAge) {
            this.maxAge = maxAge;
        }

        public boolean isShowMale() {
            return showMale;
        }

        public void setShowMale(boolean showMale) {
            this.showMale = showMale;
        }

        public boolean isShowFemale() {
            return showFemale;
        }

        public void setShowFemale(boolean showFemale) {
            this.showFemale = showFemale;
        }
    }

}

MainActivity (aktivitas yang dipanggil pertama kali dalam aplikasi Anda):

LocalPreference.getLocalPreferences(this);

Langkah-langkah menjelaskan:

  1. Aktivitas utama memanggil getLocalPreferences (ini) -> ini akan membaca preferensi Anda, menyetel objek filter di kelas aplikasi Anda dan mengembalikannya.
  2. Saat Anda memanggil fungsi getLocalPreferences () lagi di tempat lain dalam aplikasi, fungsi tersebut akan memeriksa terlebih dahulu apakah fungsi itu tidak tersedia di kelas aplikasi yang jauh lebih cepat.

CATATAN: SELALU periksa apakah variabel lebar aplikasi berbeda dari NULL, alasan -> http://www.developerphil.com/dont-store-data-in-the-application-object/

Objek aplikasi tidak akan tinggal di memori selamanya, itu akan dimatikan. Berlawanan dengan kepercayaan populer, aplikasi tidak akan dimulai ulang dari awal. Android akan membuat objek Aplikasi baru dan memulai aktivitas di mana pengguna berada sebelumnya untuk memberikan ilusi bahwa aplikasi tidak pernah mati sejak awal.

Jika saya tidak memeriksa null, saya akan mengizinkan nullpointer dilemparkan saat memanggil misalnya getMaxDistance () pada objek filter (jika objek aplikasi digesek dari memori oleh Android)

Jdruwe
sumber
0

Kelas SharedPreferences melakukan beberapa pembacaan & penulisan dalam file XML pada disk, jadi seperti operasi IO lainnya, hal ini dapat memblokir. Jumlah data yang saat ini disimpan di SharedPreferences memengaruhi waktu dan sumber daya yang digunakan oleh panggilan API. Untuk jumlah data minimal, perlu beberapa milidetik (terkadang bahkan kurang dari satu milidetik) untuk mendapatkan / memasukkan data. Tapi dari sudut pandang seorang ahli, mungkin penting untuk meningkatkan kinerja dengan melakukan panggilan API di latar belakang. Untuk SharedPreferences asinkron, saya sarankan untuk memeriksa perpustakaan Datum .

navid
sumber