การเข้าถึง SharedPreferences ควรทำจาก UI Thread หรือไม่


113

ด้วยการเปิดตัว Gingerbread ที่ฉันได้รับการทดสอบด้วยบางส่วนของใหม่ API ซึ่งเป็นหนึ่งของพวกเขาถูกStrictMode

getSharedPreferences()ผมสังเกตเห็นว่าหนึ่งในคำเตือนสำหรับ

นี่คือคำเตือน:

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

และมีการกำหนดให้มีการgetSharedPreferences()โทรบนเธรด UI

ควรSharedPreferencesเข้าถึงและเปลี่ยนแปลงจากเธรด UI หรือไม่


ฉันได้ทำการกำหนดค่าตามความชอบในเธรด UI มาโดยตลอด แม้ว่าฉันเดาว่ามันสมเหตุสมผลเพราะเป็นการดำเนินการของ IO
Falmarri

คำตอบ:


184

ดีใจที่คุณเล่นด้วยแล้ว!

สิ่งที่ควรทราบ: (ในรูปแบบสัญลักษณ์แสดงหัวข้อย่อยที่ขี้เกียจ)

  • หากนี่เป็นปัญหาที่เลวร้ายที่สุดแอปของคุณอาจอยู่ในจุดที่ดี :) โดยทั่วไปแล้วการเขียนจะช้ากว่าการอ่านดังนั้นโปรดตรวจสอบให้แน่ใจว่าคุณใช้ SharedPreferenced $ Editor.apply () แทนการกระทำ () ใช้ () เป็น GB และ async ใหม่ (แต่ปลอดภัยเสมอโปรดระวังการเปลี่ยนวงจรชีวิต) คุณสามารถใช้การสะท้อนเพื่อเรียกใช้ตามเงื่อนไข () บน GB + และคอมมิต () บน Froyo หรือต่ำกว่า ฉันจะทำบล็อกโพสต์พร้อมโค้ดตัวอย่างวิธีการทำ

เกี่ยวกับการโหลดแม้ว่า ...

  • เมื่อโหลดแล้ว SharedPreferences คือ singletons และแคชทั้งกระบวนการ ดังนั้นคุณต้องการโหลดให้เร็วที่สุดเพื่อให้คุณมีไว้ในหน่วยความจำก่อนที่คุณจะต้องการ (สมมติว่ามีขนาดเล็กอย่างที่ควรจะเป็นถ้าคุณใช้ SharedPreferences ซึ่งเป็นไฟล์ XML แบบธรรมดา ... ) คุณไม่ต้องการที่จะผิดพลาดในอนาคตผู้ใช้บางคนคลิกปุ่ม

  • แต่เมื่อใดก็ตามที่คุณเรียก context.getSharedPreferences (... ) ไฟล์ XML สำรองจะถูกตรวจสอบว่ามีการเปลี่ยนแปลงหรือไม่ดังนั้นคุณจะต้องหลีกเลี่ยงสถิติเหล่านั้นในระหว่างเหตุการณ์ UI ต่อไป โดยปกติสถิติควรจะเร็ว (และมักจะถูกแคช) แต่ yaffs ไม่มีอะไรมากในการทำงานพร้อมกัน (และอุปกรณ์ Android จำนวนมากทำงานบน yaffs ... Droid, Nexus One ฯลฯ ) ดังนั้นหากคุณหลีกเลี่ยงดิสก์ คุณจะหลีกเลี่ยงการติดค้างหลังการทำงานของดิสก์อื่น ๆ ในเที่ยวบินหรือรอ

  • ดังนั้นคุณอาจต้องการโหลด SharedPreferences ระหว่าง onCreate () ของคุณและใช้อินสแตนซ์เดิมซ้ำเพื่อหลีกเลี่ยงค่าสถิติ

  • แต่ถ้าคุณไม่ต้องการค่ากำหนดของคุณในระหว่าง onCreate () เวลาในการโหลดนั้นจะทำให้การเริ่มต้นแอปของคุณหยุดชะงักโดยไม่จำเป็นดังนั้นโดยทั่วไปแล้วจะดีกว่าหากมีคลาสย่อย FutureTask <SharedPreferences> ที่เริ่มต้นเธรดใหม่ไปที่. set () ค่าของคลาสย่อย FutureTask จากนั้นเพียงค้นหาสมาชิกของ FutureTask <SharedPreferences> เมื่อใดก็ตามที่คุณต้องการและ. get () มัน ฉันวางแผนที่จะทำให้เบื้องหลังนี้เป็นอิสระใน Honeycomb อย่างโปร่งใส ฉันจะพยายามเผยแพร่โค้ดตัวอย่างซึ่งแสดงแนวทางปฏิบัติที่ดีที่สุดในด้านนี้

ตรวจสอบบล็อกนักพัฒนา Android สำหรับโพสต์ที่กำลังจะมีขึ้นในหัวข้อที่เกี่ยวข้องกับ StrictMode ในสัปดาห์หน้า


ว้าวไม่ได้คาดหวังว่าจะได้รับคำตอบที่ชัดเจนจากแหล่งที่มาโดยตรง! ขอบคุณมาก!
cottonBallPaws

9
เพื่อประโยชน์ของผู้อ่านใหม่ของโพสต์ที่ยอดเยี่ยมนี้ค้นหาด้านล่างเชื่อมโยงไปยังบล็อกโพสต์ดังกล่าวข้างต้นโดย @Brad ฟิทซ์: โพสต์บล็อกนักพัฒนาหุ่นยนต์ในโหมดที่เข้มงวดโดยแบรด นอกจากนี้โพสต์ยังมีลิงก์ไปยังโค้ดตัวอย่างสำหรับการใช้ Apply (ตั้งแต่ขนมปังขิงเป็นต้นไป) หรือคอมมิต (froyo) ตามเวอร์ชัน Android สำหรับการจัดเก็บ sharedpreferences: [conditionally use ใช้หรือกระทำ] ( code.google.com/p/zippy-android / source / Browse / trunk / samples / … )
tony m

4
สิ่งนี้ยังเกี่ยวข้องกับ ICS \ JB หรือไม่
ekatz

5

การเข้าถึงค่ากำหนดที่ใช้ร่วมกันอาจใช้เวลาพอสมควรเนื่องจากอ่านจากที่เก็บข้อมูลแฟลช คุณอ่านมากไหม? บางทีคุณอาจใช้รูปแบบอื่นจากนั้นเช่นฐานข้อมูล SQLite

แต่อย่าแก้ไขทุกสิ่งที่คุณพบโดยใช้ StrictMode หรืออ้างเอกสาร:

แต่อย่ารู้สึกว่าถูกบังคับให้แก้ไขทุกอย่างที่ StrictMode พบ โดยเฉพาะอย่างยิ่งการเข้าถึงดิสก์หลายกรณีมักจำเป็นในช่วงวงจรชีวิตของกิจกรรมปกติ ใช้ StrictMode เพื่อค้นหาสิ่งที่คุณทำโดยบังเอิญ แม้ว่าคำขอเครือข่ายบนเธรด UI มักจะเป็นปัญหาเสมอ


6
แต่ SQLite ไม่ใช่ไฟล์ที่ต้องอ่านจากที่เก็บข้อมูลแฟลช แต่เป็นไฟล์ที่ใหญ่กว่าและซับซ้อนกว่าเมื่อเทียบกับไฟล์ค่ากำหนด ฉันสมมติว่าสำหรับจำนวนข้อมูลที่เกี่ยวข้องกับการตั้งค่าไฟล์การตั้งค่าจะเร็วกว่าฐานข้อมูล SQLite
ทอม

ถูกต้อง. ดังที่แบรดได้กล่าวไปแล้วสิ่งนี้แทบจะไม่มีปัญหา - และเขายังกล่าวว่าเป็นความคิดที่ดีที่จะโหลด SharedPreferences หนึ่งครั้ง (อาจจะอยู่ในเธรดโดยใช้ FutureTask) และถือไว้เพื่อการเข้าถึงอินสแตนซ์เดียวที่เป็นไปได้
mreichelt

5

ความละเอียดอ่อนอย่างหนึ่งเกี่ยวกับคำตอบของ Brad: แม้ว่าคุณจะโหลด SharedPreferences ใน onCreate () คุณอาจจะยังคงอ่านค่าบนเธรดพื้นหลังเนื่องจาก getString () เป็นต้นบล็อกจนกว่าจะอ่านค่ากำหนดไฟล์ที่แชร์เสร็จ (บนเธรดพื้นหลัง):

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

edit () ยังบล็อกในลักษณะเดียวกันแม้ว่า apply () จะปลอดภัยในเธรดเบื้องหน้า

(BTW ขอโทษที่เอามาลงที่นี่ฉันจะใส่ความคิดเห็นนี้ไว้ในคำตอบของ Brad แต่ฉันเพิ่งเข้าร่วมและไม่มีชื่อเสียงมากพอที่จะทำเช่นนั้น)


1

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันต้องการแบ่งปันแนวทางของฉัน ฉันอ่านหนังสือมานานและใช้การตั้งค่าร่วมกันและคลาสแอปพลิเคชันส่วนกลาง:

ใบสมัครคลาส:

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 (กิจกรรมที่เรียกก่อนในแอปพลิเคชันของคุณ):

LocalPreference.getLocalPreferences(this);

อธิบายขั้นตอน:

  1. กิจกรรมหลักเรียก getLocalPreferences (สิ่งนี้) -> สิ่งนี้จะอ่านค่ากำหนดของคุณตั้งค่าวัตถุตัวกรองในคลาสแอปพลิเคชันของคุณและส่งคืน
  2. เมื่อคุณเรียกใช้ฟังก์ชัน getLocalPreferences () อีกครั้งที่อื่นในแอปพลิเคชันขั้นแรกให้ตรวจสอบว่าไม่มีในคลาสแอปพลิเคชันซึ่งเร็วกว่ามาก

หมายเหตุ: ตรวจสอบเสมอว่าตัวแปรกว้างของแอปพลิเคชันแตกต่างจาก NULL หรือไม่เหตุผล -> http://www.developerphil.com/dont-store-data-in-the-application-object/

วัตถุแอปพลิเคชันจะไม่อยู่ในหน่วยความจำตลอดไปมันจะถูกฆ่า ตรงกันข้ามกับความเชื่อที่เป็นที่นิยมแอปจะไม่เริ่มต้นใหม่ตั้งแต่ต้น Android จะสร้างวัตถุแอปพลิเคชันใหม่และเริ่มกิจกรรมที่ผู้ใช้อยู่มาก่อนเพื่อให้ภาพลวงตาว่าแอปพลิเคชันไม่เคยถูกฆ่าตั้งแต่แรก

หากฉันไม่ได้ตรวจสอบค่าว่างฉันจะอนุญาตให้โยน nullpointer เมื่อเรียกเช่น getMaxDistance () บนวัตถุตัวกรอง (หากวัตถุแอปพลิเคชันถูกปัดจากหน่วยความจำโดย Android)


0

คลาส SharedPreferences จะอ่านและเขียนภายในไฟล์ XML บนดิสก์ดังนั้นเช่นเดียวกับการดำเนินการ IO อื่น ๆ ที่สามารถบล็อกได้ จำนวนข้อมูลที่จัดเก็บใน SharedPreferences ในปัจจุบันมีผลต่อเวลาและทรัพยากรที่ใช้โดยการเรียก API สำหรับข้อมูลจำนวนน้อยที่สุดการรับ / ใส่ข้อมูลจะใช้เวลาเพียงไม่กี่มิลลิวินาที (บางครั้งอาจน้อยกว่ามิลลิวินาที) แต่จากมุมมองของผู้เชี่ยวชาญอาจเป็นสิ่งสำคัญในการปรับปรุงประสิทธิภาพโดยการเรียก API ในพื้นหลัง สำหรับ SharedPreferences แบบอะซิงโครนัสฉันขอแนะนำให้ตรวจสอบไลบรารีDatum

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.