使用Collections.sort时android.os.AsyncTask中的java.lang.ArrayIndexOutOfBoundsException

时间:2014-12-22 16:28:41

标签: java android multithreading android-asynctask

我正在处理一个Android应用程序,它同时发生了许多异步操作。因此,我在应用程序中遇到了很多IndexOutOfBoundsExceptions和ConcurrentModificationExceptions。他们中的大多数是通过同步方法或确保UI操作在主UI线程上运行来解决的,但是这个让我感到难过。

在我的代码中,我使用AsyncTask从本地数据库中提取新对象,并刷新BaseAdapter中显示的对象列表。要显示的项目列表不断变化,我的刷新方法可以从多个线程调用。

有问题的代码粘贴在下面。 getChat()调用返回一个列表消息,这是可能不断变化的部分。为了避免任何复杂化,我在使用Lists.newLinkedList()排序/显示对象之前创建了列表的新副本。

        protected CurrentChat doInBackground(final Connection... params) {
             final Chat newChat = params[0].getChat(conversationID);
             final List<Message> newMessageList = Lists.newLinkedList(newChat.getMessages().values());
             Collections.sort(newMessageList, messageSorter);
             return new CurrentChat(newChat, newMessageList, newChat.getParticipants().size() > 2);
        }

作为参考,我使用的分拣机是:

private final Comparator<Message> messageSorter = new Comparator<Message>() {

    @Override
    public int compare(final Message lhs, final Message rhs) {
        if(lhs.getSentDateTime() != null && rhs.getSentDateTime() != null) {
            return lhs.getSentDateTime().compareTo(rhs.getSentDateTime());
        }
        else if(lhs.getSentDateTime() == null & rhs.getSentDateTime() == null) {
            return 0;
        }
        else if(lhs.getSentDateTime() == null) {
            return 1;
        }
        else {
            return -1;
        }
    }
};

我得到的异常是在Collections.sort()调用中发生的IndexOutOfBoundsException:

12-17 15:14:40.951    2270-2332/com.chat.android.communication E/AndroidRuntime? FATAL EXCEPTION: AsyncTask #1
    java.lang.RuntimeException: An error occured while executing doInBackground()
            at android.os.AsyncTask$3.done(AsyncTask.java:299)
            at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
            at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
            at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
            at java.util.concurrent.FutureTask.run(FutureTask.java:137)
            at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
            at java.lang.Thread.run(Thread.java:856)
     Caused by: java.lang.ArrayIndexOutOfBoundsException: length=11; index=11
            at java.util.LinkedList.toArray(LinkedList.java:958)
            at java.util.Collections.sort(Collections.java:1890)

我只看到这个错误发生过一次,因为错误只是从一个应用程序用户上传到我的服务器,我没有任何其他的东西可以解决。因此,我编写了一个单独的单元测试,它在列表中创建了大量的Integers,生成了几个线程,用于添加和删除列表中的项目并对其进行排序,希望至少创建相同的错误。我这样做是因为我怀疑在我的排序操作中重新使用比较器的相同实例是罪魁祸首。但是,这个单元测试没有问题。我遇到任何问题的唯一一次是我在排序之前删除复制列表。如果我这样做,我很容易遇到concurrentModificationExceptions和ArrayIndexOutOfBoundsExceptions。

我的单元测试我一直在试图重新创建相同的堆栈跟踪:

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("starting");

        Callback myCallback = new Callback();
        Thread[] threads = new Thread[1000];


        int exceptions = 0;

        final MessageComparator messageSorter = new MessageComparator();

        List<Integer> tempList = new LinkedList<Integer>();
        for(int i = 0; i < 1000; i++) {
            tempList.add(200-i);
        }

        for(int i = 0; i < 1000; i++) {
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);
            tempList.add(i);

            List<Integer> newList = new  ArrayList(tempList);
            Thread t = null;
            try {
            t = new Thread(new SorterThread(tempList, messageSorter));
            t.start();
            }
            catch(Exception e){
                e.printStackTrace();
                exceptions++;
            }
            threads[i] = t;

        }

        for(int i = 0; i < 1000; i++) {
            try {
                threads[i].join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();

            }

        }

        System.out.println("Finished");
        System.out.println(exceptions);
    }
}

我希望能够在我的单元测试中重新创建问题,以便我可以确保修复问题,但此时我不知道还有什么可能导致它。

非常感谢任何想法!

1 个答案:

答案 0 :(得分:0)

您的代码的基本问题如下:

final List<Message> newMessageList = Lists.newLinkedList(newChat.getMessages().values());
Collections.sort(newMessageList, messageSorter);

在Android中(特别是API级别23,请参阅this link),当您使用Collection<E>作为参数调用LinkedList的构造函数时,将运行以下代码:

      @Override
      public boolean addAll(Collection<? extends E> collection) {
         int adding = collection.size();
         if (adding == 0) {
            return false;
         }
         Collection<? extends E> elements = (collection == this) ?
                new ArrayList<E>(collection) : collection;

         Link<E> previous = voidLink.previous;
         for (E e : elements) {
            Link<E> newLink = new Link<E>(e, previous, null);
            previous.next = newLink;
            previous = newLink;
         }
        previous.next = voidLink;
        voidLink.previous = previous;
        size += adding;
        modCount++;
        return true;
   }

因此,如果collection的大小在int adding = collection.size()执行后立即发生变化,则elements数组的大小将无效,导致Collections.sort在{LinkedList.toArray()时抛出异常1}}被调用。但是,同步部分解决了问题,请考虑使用其他数据结构或副本。可能,以下代码可能有所帮助:

ArrayList<Message> newMessageList =
Arrays.asList(
    newChat.getMessages()
           .values()
           .toArray(new Message[0]));
Collections.sort(newMessageList, messageSorter);