如何在主线程中将 ListView、ArrayList、ArrayAdapter 和 notifyDataSetChanged 与可运行对象和消息处理程序一起使用

How to use ListView, ArrayList, ArrayAdapter and notifyDataSetChanged together with a runnable, and a message Handler in the main thread

我研究了其他 6 个 SO 问题线程以及一些具有相同问题的博客文章 错误信息,全部无效。

我正在将文件目录加载到 ArrayList,然后再加载到 ListView 使用 ArrayAdapter。代码实际工作 - 目录显示 并响应点击事件 - 但程序随后以此错误结束, 通常在我显示目录一次,几次或几次然后 运行 程序的另一部分将 loadUrl 加载到 WebView 中:

适配器的内容已经改变但是ListView没有收到 通知。确保你的适配器的内容没有被修改 后台线程,但仅来自 UI 线程。确保你的 适配器在其内容更改时调用 notifyDataSetChanged()。

None 我见过的简单示例使用 notifyDataSetChanged 或 单独的线程。我想知道我是否需要使用这些以及如何使用它们 正确。我对 ArrayAdapterArrayList 感到困惑,因为哪些只需要在主线程中修改。 ArrayList 中的数据仅在请求新目录列表时更改,然后清除并加载新数据。

我的问题是:在这种情况下我需要 notifyDataSetChanged 吗? [我很 当然我没有],我如何安排主要部分之间的各个部分 和 background/runnable 线程?

我通过交替 运行ing directoryList 和 显示 Ftp 帮助 [使用 WebView/loadUrl()] 部分 - 但我必须 运行 这对 多次到多次崩溃。

LogCat不包括程序最后执行的语句, 因此很难确定实际的触发因素;相反,它是这样的:

android.widget.ListView.layoutChildren(ListView.java:1555)

显示Ftp帮助部分 loadUrl() 到 WebView。我想知道这是怎么回事 最终触发错误。好像这个程序实际上不是 发生错误时对 ArrayList/ArrayAdapter 进行操作。这好像是 程序结构错误,而不是技术或逻辑错误。

我做的是,首先定义两个ListViews 和ArrayList。一 ListView 用于本地设备文件,另一个用于远程 Ftp 站点。

static ListView filesListLocal, filesListRemote;
static ArrayList<HashMap<String,String>> directoryEntries = new ArrayList<>();

我将展示 Ftp 目录的代码。本地目录的代码是 除了使用 file.getAbsolutePath() 而不是 ftp.list(),以及对 lookForFilesAndDirs() 的技术更改,即 本地目录处理的递归。

然后我使用 Runnable 将 Ftp 目录读入数组。我是 此处省略错误检查和其他细节;这是使用 Ftp4j 图书馆:

Runnable r = new Runnable() {
public void run() {
    Message msg = mHandler.obtainMessage();
    try {
        FTPFile[] fileAndDirs = ftp.list();
        dirName = ftp.currentDirectory();
    } catch (Various Exceptions e) {
        result = "The operation failed\n" + e.toString();
    }
    Bundle bundle = new Bundle();
    bundle.putString("mickKey", result);
    msg.setData(bundle);
    mHandler.sendMessage(msg);
}
};
 Thread t = new Thread(r);
 t.start();

在消息处理程序中,我将数据从数组复制到 ArrayList 并使用 Adapter 将其显示在 ListView 中。我原来有这个 Runnable 中的代码,然后将其移动到 main 中的消息处理程序 我第一次收到错误消息时的线程,但这没有帮助。 我猜测 notifyDataSetChanged 调用,但它也没有 差异:

dirName = ftp.currentDirectory();
sa.notifyDataSetChanged();
directoryEntries.clear();
lookForFilesAndDirs(fileAndDirs);    // load ArrayList
SimpleAdapter saFtp = new SimpleAdapter(myContext, directoryEntries,
    R.layout.my_two_lines, new String[] {"path", "filename"},
    new int[] {R.id.path, R.id.filename});
filesListRemote.setAdapter(saFtp);
sa.notifyDataSetChanged();

...此处省略更多细节

public static void lookForFilesAndDirs(FTPFile[] fileAndDirs) {
for (FTPFile fileOrDir : fileAndDirs) {
    String fileOrDirName = fileOrDir.getName();
    int entryType = fileOrDir.getType();
    if (entryType == 1) {               // entry is a directory
        HashMap<String,String> listEntry = new HashMap<>();
        listEntry.put("path", fileOrDirName);
        listEntry.put("filename", null);
        directoryEntries.add(listEntry);
    } else {                             // entry is a file
        HashMap<String,String> listEntry = new HashMap<>();
        listEntry.put("path", dirName);
        listEntry.put("filename", fileOrDirName);
        directoryEntries.add(listEntry);
    }
}

LogCat:

10-14 19:47:29.403 3006-3006/com.webs.mdawdy.htmlspyii I/Mick: onMenuItemClick
10-14 19:47:29.403 3006-3006/com.webs.mdawdy.htmlspyii I/Mick: ftpPrintFilesList
10-14 19:47:30.235 3006-3006/com.webs.mdawdy.htmlspyii I/Mick: handleMessage
10-14 19:47:32.339 3006-3006/com.webs.mdawdy.htmlspyii W/EGL_genymotion: eglSurfaceAttrib not implemented
10-14 19:47:34.627 3006-3006/com.webs.mdawdy.htmlspyii I/Mick: onMenuItemClick
10-14 19:47:34.627 3006-3006/com.webs.mdawdy.htmlspyii I/Mick: displayFtpHelp Begin
10-14 19:47:34.627 3006-3006/com.webs.mdawdy.htmlspyii I/Mick: displayFtpHelp end
10-14 19:47:34.819 3006-3006/com.webs.mdawdy.htmlspyii D/AndroidRuntime: Shutting down VM
10-14 19:47:34.819 3006-3006/com.webs.mdawdy.htmlspyii W/dalvikvm: threadid=1:
    thread exiting with uncaught exception (group=0xa4d2db20)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime: FATAL EXCEPTION: main
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime: Process: com.webs.mdawdy.htmlspyii, PID: 3006
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:
    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.
    Make sure your adapter calls notifyDataSetChanged() when its content changes.
    [in ListView(2131427419, class android.widget.ListView) with
    Adapter(class android.widget.SimpleAdapter)]
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.widget.ListView.layoutChildren(ListView.java:1555)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.widget.ListView.setSelectionInt(ListView.java:1980)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.widget.AbsListView.resurrectSelection(AbsListView.java:5376)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.widget.AbsListView.onWindowFocusChanged(AbsListView.java:2822)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.view.View.dispatchWindowFocusChanged(View.java:7900)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:968)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:972)
[6 more lines identical to the one above]
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:3133)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:102)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:136)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5001)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at java.lang.reflect.Method.invokeNative(Native Method)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:515)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
10-14 19:47:34.823 3006-3006/com.webs.mdawdy.htmlspyii E/AndroidRuntime:     at dalvik.system.NativeStart.main(Native Method)

一些建议:

  1. 使用单独的目录条目列表来存储支持两个 ListView 的数据。

    • List<HashMap<String,String>> directoryEntriesLocal = new ArrayList<>();
    • List<HashMap<String,String>> directoryEntriesRemote = new ArrayList<>();
  2. 在 activity 的 onCreate() 方法中将适配器 sasaFtp 设置为各自的列表视图。在这个阶段directoryEntriesLocaldirectoryEntriesRemote都可以为空。

  3. 在主线程Handler中,修改需要更新的目录条目列表的数据,然后调用 notifyDataSetChanged() 对应的适配器。如果声明两个处理程序会更好,一个用于更新远程列表,另一个用于本地列表。或者,您可以在一个 Handler 中完成这两项操作。这个选择留给你。无需在此处实例化和分配新的适配器。
  4. 请不要直接使用HandlerThread类。请改用 AsyncTask。它将使您作为 Android 程序员的生活变得更加轻松。