Android, ListView IllegalStateException:“ เนื้อหาของอะแดปเตอร์มีการเปลี่ยนแปลง แต่ ListView ไม่ได้รับการแจ้งเตือน”


189

สิ่งที่ฉันต้องการทำ : เรียกใช้เธรดพื้นหลังซึ่งคำนวณเนื้อหา ListView และอัปเดต ListView บางส่วนในขณะที่คำนวณผลลัพธ์

สิ่งที่ฉันรู้ว่าฉันต้องหลีกเลี่ยง : ฉันไม่สามารถยุ่งกับเนื้อหา ListAdapter จากเธรดพื้นหลังดังนั้นฉันจึงสืบทอด AsyncTask และเผยแพร่ผลลัพธ์ (เพิ่มรายการไปยังอะแดปเตอร์) จาก onProgressUpdate อะแดปเตอร์ของฉันใช้ ArrayList ของวัตถุผลลัพธ์การดำเนินการทั้งหมดในรายการอาร์เรย์เหล่านั้นจะถูกซิงโครไนซ์

งานวิจัยของคนอื่น ๆ : มีข้อมูลที่มีคุณค่ามากที่นี่ ฉันยังได้รับความเดือดร้อนจากการล่มเกือบทุกวันสำหรับกลุ่มผู้ใช้ประมาณ 500 คนและเมื่อฉันเพิ่มlist.setVisibility(GONE)/trackList.setVisibility(VISIBLE)block ใน onProgressUpdate การล่มก็ลดลงด้วยปัจจัย 10 แต่ไม่หายไป (แนะนำในคำตอบ )

สิ่งที่ฉันได้รับ : โปรดสังเกตว่ามันไม่ค่อยเกิดขึ้นจริง ๆ (สัปดาห์ละครั้งสำหรับผู้ใช้หนึ่งใน 3.5k) แต่ฉันต้องการกำจัดข้อผิดพลาดนี้อย่างสมบูรณ์ นี่คือ stacktrace บางส่วน:

`java.lang.IllegalStateException:` The content of the adapter has changed but ListView  did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class android.widget.ListView) with Adapter(class com.transportoid.Tracks.TrackListAdapter)]
at android.widget.ListView.layoutChildren(ListView.java:1432)
at android.widget.AbsListView.onTouchEvent(AbsListView.java:2062)
at android.widget.ListView.onTouchEvent(ListView.java:3234)
at android.view.View.dispatchTouchEvent(View.java:3709)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:852)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
[...]

ช่วยด้วย? ไม่ต้องการอีกต่อไปดูด้านล่าง

คำตอบสุดท้าย:ตามที่ปรากฏฉันได้เรียกnotifyDataSetChangedทุก ๆ 5 แทรกเพื่อหลีกเลี่ยงการกะพริบและการเปลี่ยนแปลงรายการอย่างฉับพลัน ไม่สามารถทำได้ด้วยวิธีนี้แจ้งให้อะแดปเตอร์เสมอเมื่อมีการเปลี่ยนแปลงรายการฐาน ข้อผิดพลาดนี้มันหายไปนานสำหรับฉันตอนนี้


3
คุณได้เรียก informDataSetChanged () หรือไม่
เดนิส Palnitsky

1
แน่นอนใน onProgressUpdate ไปมีลำดับ: list.setVisibility (หายไป) - addObjectToList / การดำเนินการทำข้อมูลให้ตรงกันในรายการ / - notifyDataSetChanged () - list.setVisibility (มองเห็น) (และการมองเห็นโดยไม่ต้องแก้ไขข้อยกเว้นเกิดขึ้นไกลบ่อย)
Tomash

1
คุณกำลังแก้ไข ArrayList พื้นฐานในเธรดอื่นหรือไม่? การเปลี่ยนแปลงทั้งหมดแม้แต่ ArrayList ที่อ้างอิงโดยอแด็ปเตอร์ก็ต้องเกิดขึ้นบนเธรด UI
Rich Schuler

2
@Qberticus - ตามที่ระบุไว้อย่างชัดเจนฉันไม่ได้แก้ไข ArrayList จากเธรดที่แตกต่างกัน แต่จากเมธอด onProgressUpdate ของ AcyncTask - ทำงานในเธรด GUI
tomash

1
@ Tomash ฉันได้รับข้อยกเว้นเดียวกันฉันใช้ pull เพื่อรีเฟรชและในโพสต์รันฉันได้เขียนadapter.notifyDataSetChanged (); lvAutherlist.completeRefreshing (); แต่บางครั้งก็มีข้อผิดพลาดนี้ว่าจะแก้ไขได้อย่างไร
Khan

คำตอบ:


119

ฉันมีปัญหาเดียวกัน

ฉันเพิ่มรายการลงArrayListในเธรด UI นอกของฉัน

การแก้ไข: ฉันได้ทำทั้งสองอย่างแล้วadding the itemsและเรียกใช้notifyDataSetChanged()ในเธรด UI


4
ฉันทั้งสองเพิ่มรายการและเรียกว่า alertDataSetChanged () ในเธรด UI และฉันแก้ไขปัญหานี้
Mullins

23
แสดงความเห็นใจดีจริงๆ
dentex

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

2
สำหรับผู้อ่านในอนาคต - การคำนวณในเธรดพื้นหลังเป็นสิ่งที่ดี แต่คุณควรส่งผลลัพธ์ไปยังเธรด UI และเพิ่มรายการไปยังคอลเลกชันฐานอะแดปเตอร์และแจ้งให้อะแดปเตอร์ในบล็อกรหัสเดียวกัน
tomash

4
@Mullins คุณช่วยกรุณาแสดงตัวอย่างสำหรับวิธีการแก้ปัญหาของคุณได้หรือไม่ฉันติดอยู่ที่ปัญหาเดียวกัน
Jas

27

ฉันมีปัญหาเดียวกัน แต่ฉันแก้ไขโดยใช้วิธีการ

requestLayout();

จากชั้นเรียน ListView


35
เราควรเรียกวิธีนี้เมื่อใด
JehandadK

3
@DeBuGGeR หลังจากที่คุณเพิ่มรายการลงใน ListView หรือเปลี่ยนอะแดปเตอร์ของมัน
Ahmet Noyan Kızıltan

จะเกิดอะไรขึ้นถ้าฉันกำลังเพิ่มองค์ประกอบใน asynctask
ดร. aNdRO

2
เพียงบันทึก @ Dr.aNdRO คุณเก็บผลลัพธ์ของคุณใน AsyncTask doInBackground () และบันทึกผลลัพธ์เพื่ออัปเดตรายการใน AsyncTask.onPostExecute () ซึ่งจะทำงานในเธรด UI หากคุณต้องการอัปเดตตามไปใช้ AsyncTask.publishProgress () และ AsyncTask.onProgressUpdate () ซึ่งยังทำงานในเธรด UI
Nicole Borrelli

21

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

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

อย่างที่คุณเห็นในกรณีปกติ การอัพเดตอะแด็ปเตอร์ข้อมูลจากเธรดพื้นหลังและการเรียก informDataSetChanged ในเธรด UI ใช้งานได้

legalStateException นี้เกิดขึ้นเมื่อเธรด ui อัพเดตมุมมองและเธรดพื้นหลังอื่นจะเปลี่ยนข้อมูลอีกครั้ง ช่วงเวลานั้นทำให้เกิดปัญหานี้

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

นี่คือรหัสเฉพาะของฉันสำหรับผู้อื่นในการอ้างอิง

ตัวโหลดของฉันบนหน้าจอหลักโหลดรายชื่อติดต่อสมุดโทรศัพท์ลงในแหล่งข้อมูลของฉันในพื้นหลัง

    @Override
    public Void loadInBackground() {
        Log.v(TAG, "Init loadings contacts");
        synchronized (SingleTonProvider.getInstance()) {
            PhoneBookManager.preparePhoneBookContacts(getContext());
        }
    }

PhoneBookManager.getPhoneBookContacts นี้อ่านรายชื่อติดต่อจากสมุดโทรศัพท์และเติมลงในแฮชแมป รายการใดที่สามารถใช้งานได้โดยตรงเพื่อ List List

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

ตัวโหลดของฉันในกิจกรรมที่สองรอให้เธรดแรกเสร็จสมบูรณ์ จนกระทั่งมันแสดงแถบความคืบหน้า ตรวจสอบ loadInBackground ของ handler ทั้งสอง

จากนั้นจะสร้างอะแด็ปเตอร์และส่งไปยังกิจกรรมที่อยู่บน ui thread i ที่เรียก setAdapter

ที่แก้ไขปัญหาของฉัน

รหัสนี้เป็นตัวอย่างเท่านั้น คุณต้องเปลี่ยนมันเพื่อรวบรวมให้ดีสำหรับคุณ

@Override
public Loader<PhoneBookContactAdapter> onCreateLoader(int arg0, Bundle arg1) {
    return new PhoneBookContactLoader(this);
}

@Override
public void onLoadFinished(Loader<PhoneBookContactAdapter> arg0, PhoneBookContactAdapter arg1) {
    contactList.setAdapter(adapter = arg1);
}

/*
 * AsyncLoader to load phonebook and notify the list once done.
 */
private static class PhoneBookContactLoader extends AsyncTaskLoader<PhoneBookContactAdapter> {

    private PhoneBookContactAdapter adapter;

    public PhoneBookContactLoader(Context context) {
        super(context);
    }

    @Override
    public PhoneBookContactAdapter loadInBackground() {
        synchronized (SingleTonProvider.getInstance()) {
            return adapter = new PhoneBookContactAdapter(getContext());    
        }
    }

}

หวังว่านี่จะช่วยได้


ขออภัยคุณทำเช่นนั้นได้อย่างไร คุณซิงโครไนซ์โค้ดเธรดพื้นหลังและการเรียกการแจ้งเตือนการเปลี่ยนแปลงได้อย่างไร ฉันกำลังใช้ doInBackground เมื่อกรองข้อมูลใน AutocompleteTextView และฉันกำลังประสบปัญหาเดียวกัน ... คุณช่วยแนะนำบางอย่างให้ฉันแก้ไขได้ไหม?
tonix

@ user3019105 - การซิงโครไนซ์เธรดพื้นหลังสองรายการ สิ่งหนึ่งที่อัพเดตข้อมูลในเบื้องหลังและอื่น ๆ ซึ่งแจ้งให้เธรด ui เป็น setAdadpter หรือการแจ้งเตือนข้อมูลโดยใช้วิธีการ handler.post หรือเมธอด runOnUiThread ที่มีอยู่ สำหรับกรณีของฉันโดยเฉพาะฉันซิงโครไนซ์โฮเดอร์สองตัวกับ doInBackground ที่ซิงโครไนซ์กับวัตถุเดี่ยว หนึ่งเริ่มเตรียมข้อมูลบนหน้าจอเริ่มต้นเพื่อให้พร้อมใช้งานอย่างรวดเร็วสำหรับหน้าจอแดชบอร์ด นั่นคือความต้องการของฉัน
Javanator

คุณช่วยกรุณาโพสต์โค้ดของการใช้งานนี้ได้ไหม ในกรณีของฉันฉันได้แก้ไขการเรียก clear () บนอะแด็ปเตอร์แล้วสร้าง ArrayList <T> ของวัตถุที่ถูกกรองชั่วคราวจากนั้นทำการเรียกใช้ addAll (tmpArrayList) และสุดท้าย ฉันทำการเรียกทั้งหมดนี้ภายในวิธีการ publishResult () ของตัวกรองโดยรันบนเธรดหลัก คุณคิดว่านี่เป็นทางออกที่เชื่อถือได้หรือไม่?
tonix

@ user3019105 ตรวจสอบคำตอบที่แก้ไขแล้ว หวังว่ามันจะเป็นประโยชน์กับคุณตอนนี้
Javanator

คำตอบนี้ต้องอ่านจำนวนมากว่าทำไมทำงานอย่างไรtutorials.jenkov.com/java-concurrency/synchronized.htmlเป็นจุดเริ่มต้นที่ดี
abdu

15

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

List<> data = new ArrayList<>();
List<> adapterData = new ArrayList();

...
adapter = new Adapter(adapterData);
listView.setAdapter(adapter);

// Whenever data needs to be updated, it can be done in a separate thread
void updateDataAsync()
{
    new Thread(new Runnable()
    {
        @Override
        public void run()
        {
            // Make updates the "data" list.
            ...

            // Update your adapter.
            refreshList();
        }
    }).start();
}

void refreshList()
{
    runOnUiThread(new Runnable()
    {
        @Override
        public void run()
        {
            adapterData.clear();
            adapterData.addAll(data);
            adapter.notifyDataSetChanged();
            listView.invalidateViews();
        }
    });
}

7

ฉันเขียนรหัสนี้และให้มันทำงานในรูปจำลอง 2.1 เป็นเวลา ~ 12 ชั่วโมงและไม่ได้รับ IllegalStateException ฉันจะให้ประโยชน์กับกรอบการทำงานของ Android ในข้อสงสัยเกี่ยวกับสิ่งนี้และบอกว่ามันน่าจะเป็นข้อผิดพลาดในรหัสของคุณ ฉันหวังว่านี่จะช่วยได้. บางทีคุณสามารถปรับให้เข้ากับรายการและข้อมูลของคุณ

public class ListViewStressTest extends ListActivity {
    ArrayAdapter<String> adapter;
    ListView list;
    AsyncTask<Void, String, Void> task;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        this.list = this.getListView();

        this.list.setAdapter(this.adapter);

        this.task = new AsyncTask<Void, String, Void>() {
            Random r = new Random();
            int[] delete;
            volatile boolean scroll = false;

            @Override
            protected void onProgressUpdate(String... values) {
                if(scroll) {
                    scroll = false;
                    doScroll();
                    return;
                }

                if(values == null) {
                    doDelete();
                    return;
                }

                doUpdate(values);

                if(ListViewStressTest.this.adapter.getCount() > 5000) {
                    ListViewStressTest.this.adapter.clear();
                }
            }

            private void doScroll() {
                if(ListViewStressTest.this.adapter.getCount() == 0) {
                    return;
                }

                int n = r.nextInt(ListViewStressTest.this.adapter.getCount());
                ListViewStressTest.this.list.setSelection(n);
            }

            private void doDelete() {
                int[] d;
                synchronized(this) {
                    d = this.delete;
                }
                if(d == null) {
                    return;
                }
                for(int i = 0 ; i < d.length ; i++) {
                    int index = d[i];
                    if(index >= 0 && index < ListViewStressTest.this.adapter.getCount()) {
                        ListViewStressTest.this.adapter.remove(ListViewStressTest.this.adapter.getItem(index));
                    }
                }
            }

            private void doUpdate(String... values) {
                for(int i = 0 ; i < values.length ; i++) {
                    ListViewStressTest.this.adapter.add(values[i]);
                }
            }

            private void updateList() {
                int number = r.nextInt(30) + 1;
                String[] strings = new String[number];

                for(int i = 0 ; i < number ; i++) {
                    strings[i] = Long.toString(r.nextLong());
                }

                this.publishProgress(strings);
            }

            private void deleteFromList() {
                int number = r.nextInt(20) + 1;
                int[] toDelete = new int[number];

                for(int i = 0 ; i < number ; i++) {
                    int num = ListViewStressTest.this.adapter.getCount();
                    if(num < 2) {
                        break;
                    }
                    toDelete[i] = r.nextInt(num);
                }

                synchronized(this) {
                    this.delete = toDelete;
                }

                this.publishProgress(null);
            }

            private void scrollSomewhere() {
                this.scroll = true;
                this.publishProgress(null);
            }

            @Override
            protected Void doInBackground(Void... params) {
                while(true) {
                    int what = r.nextInt(3);

                    switch(what) {
                        case 0:
                            updateList();
                            break;
                        case 1:
                            deleteFromList();
                            break;
                        case 2:
                            scrollSomewhere();
                            break;
                    }

                    try {
                        Thread.sleep(0);
                    } catch(InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

        };

        this.task.execute(null);
    }
}

ขอบคุณสำหรับการทำงานของคุณ! ฉันสังเกตเห็นว่าสำหรับวิธี ArrayAdapter hasStableIds () = false; การใช้งานของฉันกลับจริงซึ่งไม่ถูกต้องเนื่องจากแถวถูกเรียงก่อนหน้า ฉันจะลองดู - ถ้าความขัดข้องมีชีวิตฉันจะทำการตรวจสอบต่อไป ขอแสดงความนับถืออย่างสูง!
tomash

4

หลายวันที่ผ่านมาฉันพบปัญหาเดียวกันและทำให้เกิดความผิดพลาดหลายพันครั้งต่อวันประมาณ 0.1% ของผู้ใช้ที่พบสถานการณ์นี้ ฉันพยายามsetVisibility(GONE/VISIBLE)และrequestLayout()แต่นับความผิดพลาดเพียงลดลงเล็กน้อย

และในที่สุดฉันก็แก้มันได้ setVisibility(GONE/VISIBLE)ไม่มีอะไรกับ requestLayout()ไม่มีอะไรกับ

ในที่สุดฉันก็พบว่าเหตุผลที่ฉันใช้ a Handlerto call notifyDataSetChanged()หลังจากอัพเดตข้อมูลซึ่งอาจนำไปสู่การเรียงลำดับของ

  1. อัปเดตข้อมูลเป็นวัตถุจำลอง (ฉันเรียกว่าเป็นแหล่งข้อมูล)
  2. ผู้ใช้สัมผัส listview (ซึ่งอาจโทรcheckForTap()/ onTouchEvent()และโทรสุดท้ายlayoutChildren())
  3. อะแด็ปเตอร์รับข้อมูลจากโมเดลวัตถุและเรียกใช้notifyDataSetChanged()และอัพเดตมุมมอง

และฉันทำผิดพลาดว่าในอีกgetCount(), getItem()และgetView()ผมโดยตรงใช้เขตข้อมูลในแหล่งข้อมูลมากกว่าคัดลอกไปยังอะแดปเตอร์ ดังนั้นในที่สุดมันก็ล้มเหลวเมื่อ:

  1. อะแดปเตอร์อัปเดตข้อมูลที่ตอบสนองล่าสุดให้
  2. เมื่อตอบกลับครั้งต่อไป DataSource จะอัปเดตข้อมูลซึ่งทำให้การนับรายการเปลี่ยนแปลง
  3. ผู้ใช้แตะ listview ซึ่งอาจเป็นการแตะหรือเลื่อนหรือพลิก
  4. getCount()และgetView()มีการเรียกและข้อมูล ListView java.lang.IllegalStateException: The content of the adapter has changed but...พบไม่สอดคล้องและพ่นข้อยกเว้นเช่น อีกข้อยกเว้นทั่วไปเป็นIndexOutOfBoundExceptionถ้าคุณใช้ส่วนหัว / ListViewท้ายกระดาษใน

ดังนั้นวิธีการแก้ปัญหาเป็นเรื่องง่ายที่ฉันเพียงแค่คัดลอกข้อมูลไปอะแดปเตอร์จากแหล่งข้อมูลของฉันเมื่อทริกเกอร์ Handler notifyDataSetChanged()ของฉันอะแดปเตอร์เพื่อให้ได้ข้อมูลและบริการโทร ความผิดพลาดในขณะนี้ไม่เคยเกิดขึ้นอีกครั้ง


1
นี่คือปัญหาของฉัน ในกรณีของฉัน getCount () ส่งคืนจำนวนใหม่ที่พร้อมใช้งานก่อนที่จะเรียก informDataSetChanged เนื่องจากรายการของฉันคือ 'เสมือน' ตอนนี้ฉันจับการนับที่อัปเดตในวิธี informDataSetChanged ของฉันแล้วส่งคืนจำนวนที่แคชนี้เมื่อถูกขอให้นับผ่าน getCount ()
เกล็น

3

หากสิ่งนี้เกิดขึ้นเป็นระยะ ๆ ปรากฎว่าฉันมีปัญหานี้เฉพาะเมื่อรายการถูกเลื่อนหลังจากรายการสุดท้าย 'โหลดเพิ่มเติม' ถูกคลิก หากรายการไม่เลื่อนทุกอย่างทำงานได้ดี

หลังจากการดีบั๊กมากมันเป็นข้อผิดพลาดในส่วนของฉัน แต่ความไม่สอดคล้องในรหัส Android ก็เช่นกัน

เมื่อการตรวจสอบเกิดขึ้นรหัสนี้จะถูกดำเนินการใน ListView

        } else if (mItemCount != mAdapter.getCount()) {
            throw new IllegalStateException("The content of the adapter has changed but "
                    + "ListView did not receive a notification. Make sure the content of "

แต่เมื่อ onChange เกิดขึ้นมันจะยิงรหัสนี้ใน AdapterView (parent ของ ListView)

    @Override
    public void onChanged() {
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = getAdapter().getCount();

ขอให้สังเกตว่าอะแดปเตอร์ไม่รับประกันว่าจะเหมือนกัน!

ในกรณีของฉันเนื่องจากมันเป็น 'LoadMoreAdapter' ฉันส่งคืน WrappedAdapter ในการเรียก getAdapter (สำหรับการเข้าถึงวัตถุพื้นฐาน) สิ่งนี้ส่งผลให้การนับแตกต่างกันเนื่องจากรายการ 'โหลดเพิ่มเติม' และการยกเว้นถูกโยนทิ้ง

ฉันทำสิ่งนี้เพียงเพราะเอกสารทำให้ดูเหมือนว่าจะทำ

ListView.getAdapter javadoc

ส่งคืนอแด็ปเตอร์ที่ใช้งานอยู่ใน ListView นี้ อะแด็ปเตอร์ที่ส่งคืนอาจไม่ใช่อะแด็ปเตอร์เดียวกันที่ส่งไปยัง setAdapter (ListAdapter) แต่อาจเป็น WrapperListAdapter


ฉันมีปัญหาที่คล้ายกัน คุณช่วยแนะนำวิธีแก้ไขได้ไหม
May13ank

หากคุณดูตัวอย่างโค้ด 2 ตัวอย่างด้านบนคุณจะเห็น mAdapter ใน ListView และ getAdapter () ในพาเรนต์ของ ListView (AdapterView) หากคุณแทนที่ getAdapter () ผลลัพธ์ของ getAdapter () อาจไม่ใช่ mAdapter หากจำนวนของอะแดปเตอร์ 2 ตัวแตกต่างกันคุณจะได้รับข้อผิดพลาด
aaronvargas

3

ปัญหาของฉันเกี่ยวข้องกับการใช้ตัวกรองร่วมกับ ListView

เมื่อตั้งค่าหรืออัปเดตโมเดลข้อมูลพื้นฐานของ ListView ฉันทำสิ่งนี้:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    getFilter().filter(filter);
}

การโทรfilter()ในบรรทัดสุดท้ายจะทำให้ (และต้อง) ทำให้notifyDataSetChanged()ถูกเรียกในpublishResults()วิธีการของตัวกรอง สิ่งนี้อาจใช้ได้ในบางครั้งโดยเฉพาะใน Nexus 5 ที่รวดเร็วของฉัน แต่ในความเป็นจริงมันซ่อนข้อบกพร่องที่คุณจะสังเกตเห็นด้วยอุปกรณ์ที่ช้าลงหรือในสภาวะที่ใช้ทรัพยากรมาก

ปัญหาคือการกรองเสร็จแบบอะซิงโครนัสดังนั้นระหว่างการสิ้นสุดfilter()คำสั่งและการเรียกpublishResults()ทั้งในเธรด UI รหัสเธรด UI อื่น ๆ บางอันอาจเรียกใช้และเปลี่ยนเนื้อหาของอะแดปเตอร์

การแก้ไขจริงนั้นง่ายเพียงแค่โทรหาnotifyDataSetChanged()ก่อนขอให้ทำการกรอง:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}

3

ฉันมีรายการถ้าฟีดวัตถุ มันผนวกและตัดทอนจากด้ายไม่มี UI ทำงานได้ดีกับอะแดปเตอร์ด้านล่าง ฉันโทรหาFeedAdapter.notifyDataSetChangedเธรด UI อยู่แล้ว แต่จะใหม่กว่า ฉันทำสิ่งนี้เพราะวัตถุฟีดของฉันยังคงอยู่ในหน่วยความจำใน Local Service แม้ว่า UI จะตาย

public class FeedAdapter extends BaseAdapter {
    private int size = 0;
    private final List<Feed> objects;

    public FeedAdapter(Activity context, List<Feed> objects) {
        this.context = context;
        this.objects = objects;
        size = objects.size();
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ...
    }

    @Override
    public void notifyDataSetChanged() {
        size = objects.size();

        super.notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return size;
    }

    @Override
    public Object getItem(int position) {
        try {
            return objects.get(position);
        } catch (Error e) {
            return Feed.emptyFeed;
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
}

2

ฉันกำลังประสบปัญหาเดียวกันกับบันทึกข้อผิดพลาดเดียวกันทั้งหมด ในกรณีของฉันonProgress()ของ AsyncTask mAdapter.add(newEntry)เพิ่มค่าอะแดปเตอร์ใช้ เพื่อหลีกเลี่ยงการ UI ตอบสนองน้อยฉันตั้งmAdapter.setNotifyOnChange(false)และโทรmAdapter.notifyDataSetChanged()4 ครั้งที่สอง หนึ่งครั้งต่อวินาทีอาร์เรย์จะถูกจัดเรียง

วิธีนี้ใช้งานได้ดีและดูเป็นเรื่องที่น่าติดตาม แต่น่าเสียดายที่มันเป็นไปได้ที่จะเกิดความผิดพลาดโดยการสัมผัสรายการที่แสดงบ่อยครั้งพอ

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



2

แม้ว่าฉันจะประสบปัญหาเดียวกันในแอปพลิเคชันการแจ้งเตือน XMPP ของฉันข้อความผู้รับต้องถูกเพิ่มกลับไปยังมุมมองรายการ (นำไปใช้กับArrayList) เมื่อฉันพยายามเพิ่มเนื้อหาของผู้รับผ่านMessageListener(เธรดแยกต่างหาก) แอปพลิเคชันหยุดทำงานโดยมีข้อผิดพลาดด้านบน ฉันแก้ไขสิ่งนี้โดยการเพิ่มเนื้อหาลงในวิธีการarraylist& setListviewadapaterผ่านrunOnUiThreadซึ่งเป็นส่วนหนึ่งของคลาสกิจกรรม นี่เป็นการแก้ไขปัญหาของฉัน


1

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

public class MyActivity... {
    private MyTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // your code
       task = new MyTask();
       setList();
    }

    private void setList() {
    if (task != null)
        if (task.getStatus().equals(AsyncTask.Status.RUNNING)){
            task.cancel(true);
            task = new MyTask();
            task.execute();         
        } else if (task.getStatus().equals(AsyncTask.Status.FINISHED)) {
            task = new MyTask();
            task.execute();
        } else 
            task.execute();
    }

    class MyTask extends AsyncTask<Void, Item, Void>{
       List<Item> Itens;

       @Override
       protected void onPreExecute() {

        //your code

        list.setVisibility(View.GONE);
        adapterItem= new MyListAdapter(MyActivity.this, R.layout.item, new ArrayList<Item>());
        list.setAdapter(adapterItem);

        adapterItem.notifyDataSetChanged();
    }

    @Override
    protected Void doInBackground(Void... params) {

        Itens = getItens();
        for (Item item : Itens) {
            publishProgress(item );
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Item ... item ) {           
        adapterItem.add(item[0]);
    }

    @Override
    protected void onPostExecute(Void result) {
        //your code
        adapterItem.notifyDataSetChanged();     
        list.setVisibility(View.VISIBLE);
    }

}

}

1

ฉันมีปัญหาเดียวกันและฉันแก้ไขมัน ปัญหาของฉันคือว่าฉันกำลังใช้ a listviewกับอะแดปเตอร์อาร์เรย์และตัวกรอง ในวิธีการที่performFilteringฉันยุ่งกับอาร์เรย์ที่มีข้อมูลและเป็นปัญหาเนื่องจากวิธีนี้ไม่ได้ทำงานบนเธรด UI และจริงๆแล้วมันทำให้เกิดปัญหาบางอย่าง


1

สาเหตุหนึ่งสำหรับความผิดพลาดนี้คือArrayListวัตถุนั้นไม่สามารถเปลี่ยนแปลงได้อย่างสมบูรณ์ ดังนั้นเมื่อฉันลบรายการฉันต้องทำสิ่งนี้:

mList.clear();
mList.addAll(newDataList);

นี่เป็นการแก้ไขข้อผิดพลาดสำหรับฉัน


1

ในกรณีของฉันฉันเรียกว่าวิธีการที่GetFilter()อะแดปเตอร์จากที่วิธีการในกิจกรรมหลักและฉันได้เพิ่มข้อมูลที่มีสำหรับห่วงTextWatcher() GetFilter()วิธีแก้ไขคือเปลี่ยนวิธีวนรอบเป็นAfterTextChanged()ย่อยในกิจกรรมหลักและลบการโทรออกGetFilter()


1
โปรดอธิบายวิธีการแก้ปัญหาของคุณ ฉันยังอยู่ในสถานการณ์ดูเหมือนว่า u /
nAkhmedov

1
adapter.notifyDataSetChanged()

2
เป็นวิธีปฏิบัติที่ดีสำหรับ Stack Overflow เพื่อเพิ่มคำอธิบายว่าทำไมโซลูชันของคุณควรทำงานหรือดีกว่าโซลูชันที่มีอยู่ สำหรับข้อมูลเพิ่มเติมอ่านวิธีการตอบ
Samuel Liew

0

ฉันยังได้รับข้อผิดพลาดเดียวกันแน่นอนและใช้ AsyncTask:

`java.lang.IllegalStateException:` The content of the adapter has changed but ListView  did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class android.widget.ListView) with Adapter... etc

ฉันแก้ไขได้โดยใส่adapter.notifyDataSetChanged();ที่ด้านล่างของเธรด UI นั่นคือ AsyncTask onPostExecute ของฉัน แบบนี้ :

 protected void onPostExecute(Void aVoid) {

 all my other stuff etc...
    all my other stuff etc...

           adapter.notifyDataSetChanged();

                }

            });
        }

ตอนนี้แอพของฉันใช้งานได้

แก้ไข: ที่จริงแล้วแอพของฉันยังคงชนกันทุก ๆ 1 ใน 10 ครั้งทำให้เกิดข้อผิดพลาดเดียวกัน

ในที่สุดฉันก็เจอrunOnUiThreadโพสต์ก่อนหน้าซึ่งฉันคิดว่าอาจมีประโยชน์ ดังนั้นฉันวางไว้ในวิธี doInBackground ของฉันเช่นนี้

@Override
protected Void doInBackground(Void... voids) {

    runOnUiThread(new Runnable() {
                      public void run() { etc... etc...

และฉันลบ adapter.notifyDataSetChanged();วิธีการ ตอนนี้แอพของฉันไม่มีปัญหาเลย


0

โปรดลองหนึ่งในวิธีแก้ปัญหาต่อไปนี้:

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

  2. ตรวจสอบให้แน่ใจว่ามีการเรียกใช้การอัปเดต UI ทั้งหมดในเธรด UI


0

ฉันมีปัญหาเดียวกันเมื่อเพิ่มข้อมูลใหม่ในตัวโหลดรูปภาพที่ขี้เกียจฉันเพิ่งใส่

         adapter.notifyDataSetChanged();

ใน

       protected void onPostExecute(Void args) {
        adapter.notifyDataSetChanged();
        // Close the progressdialog
        mProgressDialog.dismiss();
         }

หวังว่ามันจะช่วยคุณ


0

Like @Mullins พูดว่า "
ฉันทั้งสองเพิ่มรายการและเรียกnotifyDataSetChanged()ในเธรด UI และฉันแก้ไขสิ่งนี้ - Mullins"

ในกรณีของฉันฉันมีasynctaskและฉันเรียกว่าnotifyDataSetChanged()ในdoInBackground()วิธีการและปัญหาได้รับการแก้ไขเมื่อฉันโทรจากonPostExecute()ฉันได้รับการยกเว้น


0

ฉันมีประเพณีListAdapterและโทรมาsuper.notifyDataSetChanged()ที่จุดเริ่มต้นไม่ใช่จุดสิ้นสุดของวิธี

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}

0

ฉันมี sittuation เดียวกันฉันมีหลายกลุ่มชน insite รายการของฉันใน listview และฉันก็เปลี่ยนค่าบูลีนบางอย่างในรายการของฉันเช่น holder.rbVar.setOnclik ...

ปัญหาของฉันเกิดขึ้นเพราะฉันกำลังเรียกวิธีการภายใน getView (); และกำลังบันทึกวัตถุในการแปลร่วมกันดังนั้นฉันจึงมีข้อผิดพลาดด้านบน

ฉันจะแก้ไขมันอย่างไร ฉันลบวิธีการของฉันภายใน getView () เพื่อแจ้งข้อมูล DataSetInvalidated () และปัญหาหายไป

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }

0

ผมมีปัญหาเหมือนกัน. ในที่สุดฉันก็ได้ทางออก

ก่อนอัปเดตมุมมองรายการหากปุ่มกดซอฟท์คีย์ปิดอยู่ก่อน หลังจากที่ตั้งค่าแหล่งข้อมูลและเรียกการแจ้งเตือนเปลี่ยนแปลง ()

ในขณะที่การปิดปุ่มกด listview ภายในจะปรับปรุง UI ของมัน มันยังคงโทรต่อไปจนกว่าจะปิดปุ่มกด เวลานั้นถ้าแหล่งข้อมูลเปลี่ยนแปลงจะไม่ส่งข้อยกเว้นนี้ ถ้าข้อมูลกำลังอัพเดตใน onActivityResult อาจมีโอกาสเกิดข้อผิดพลาดเดียวกันได้

 InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);

        view.postDelayed(new Runnable() {
            @Override
            public void run() {
                refreshList();
            }
        },100L);

0

ทางออกของฉัน:

1) temp ArrayListสร้าง

2) ทำงานหนักของคุณ (ดึงแถว sqlite, ... ) ในdoInBackgroundวิธีการและเพิ่มรายการลงในรายการอาร์เรย์ชั่วคราว

3) เพิ่มรายการทั้งหมดจาก araylist temp ไปยังรายการ arraylist ของคุณในonPostExecuteวิธี

note:คุณอาจต้องการที่จะลบบางรายการจาก ListView และลบออกจากฐานข้อมูล SQLite และอาจจะลบไฟล์บางอย่างที่เกี่ยวข้องกับรายการจาก sdcard background threadเพียงลบรายการออกจากฐานข้อมูลและลบไฟล์ที่เกี่ยวข้องของพวกเขาและเพิ่มให้อุณหภูมิใน จากนั้นในUI threadลบรายการที่มีอยู่ใน temp arraylist จากรายการของ arrayview

หวังว่านี่จะช่วยได้

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