在后台加载数据,更新适配器?

Loading Data in Background, Updating Adapter?

我正在使用 ArrayAdapter 来显示项目列表,并且我有一个 AsyncTask 线程通过 HttpURLConnection 加载这些项目。我希望这个项目列表是用户在启动应用程序时看到的第一件事。我知道让应用程序在启动时等待此加载完成是不好的做法,这就是为什么我将加载设置为 AsyncTask。我将本地数据存储在同时显示的 SharedPreferences 中。

遗憾的是,我无法直接从 AsyncTask 中修改适配器。

java.lang.IllegalStateException: Make sure the content of your adapter is not modified from a background thread, but only from the UI thread.

我想我只需要一个信号来通知 UI 线程后台任务已完成;我可以修改我的代码以将数据加载到另一个不是 Adapter 的数据结构中,然后 UI 线程一旦知道任务完成就可以将它添加到 Adapter 中。我不知道如何在不等待 AsyncTask 完成的情况下执行此操作,这就像我在等待数据加载到主 UI 线程中一样。

我该怎么办?

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);
        ...
        LoadAnnouncementDataFromAPI announcementAPIQuery = new LoadAnnouncementDataFromAPI();
        announcementAPIQuery.execute();
        ...
    }

    ...

    private class LoadAnnouncementDataFromAPI extends AsyncTask<String, String, String> {

        private String announcementAPI = "http://10.0.2.2:3000/announcements";

        @Override
        protected String doInBackground(String... params) {
            try {
                URL url = new URL(announcementAPI);
                HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));

                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line).append("\n");
                }
                bufferedReader.close();
                return stringBuilder.toString();
            } catch (IOException e) {
                return "";
            }
        }

        @Override
        protected void onPostExecute(String queryResponse) {
            if (queryResponse != null && !queryResponse.equals("")) {
                try {
                    JSONArray apiResponse = new JSONArray(queryResponse);
                    for (int i = 0; i < apiResponse.length(); i++) {
                        JSONObject announcementJSON = apiResponse.getJSONObject(i);
                        if (!announcementJSON.getString("id").equals("")) {
                            mAnnouncementAdapter.items.add(new AnnouncementBlock(announcementJSON));
                        }
                    }
                    mAnnouncementAdapter.notifyDataSetChanged();
                } catch (JSONException e) {
                    return;
                }
            }
        }
    }
}

尝试创建一个函数 setListAdapter,您可以在其中创建适配器的新实例来传递新数据,如下所示:

public void setListAdapter (AnnouncementBlock listdata)   
    {
        If (listAdapter != null){
           listAdapter = null;
        }
     listAdapter = new MyListAdapter (listdata);
}

然后在你的 postExecute

中调用这个方法

您只需向您的 asynTask 添加一个侦听器即可做到这一点。当您收到回调时,请确保更新代码在 UI 线程上运行。

一些代码

1 - 回调接口

interface OnAddListener {
    void onAdd (AnnouncementBlock announcementBlock);
}

2 - 改变你的 class

class LoadAnnouncementDataFromAPI extends AsyncTask<String,String,String> {
    OnAddListener listener;

    public void setOnAddListener(OnAddListener listener) {
        this.listener = listener;
    }

    LoadAnnouncementDataFromAPI(OnAddListener listener) {
        this.listener = listener;
        // Or may be pass it to the constructor...
    }

    protected String doInBackground(String... params) {
        // Your logic
        // And then!
        if(listener!=null){
            listener.onAdd(new AnnouncementBlock(announcementJSON));
        }
        // ...
    }
}

3 - 您可以从 activity 获得回调。我倾向于喜欢 activity 直接实现接口的方法(可能是为了避免 final 关键字),但你也可以这样做。

// Notice that the activity is implementing the OnAddListener interface.
class YourActivity extends Activity implements OnAddListener {

    // ... Some logic life cycle callbacks

    // I suppose you're doing something similar
    private void startUpdating(){
        mLoadAnnouncementDataFromAPI.setListener(this); // set the listener.
    }

    // Your callback.
    @Override
    public void onAdd(final AnnouncementBlock announcementBlock) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Now you're sure it's running in the UI thread ;)
                mAnnouncementAdapter.items.onAdd(announcementBlock);
            }
        });
    }
}