【问题标题】:Updating sqlite database and using SimpleCursorAdapter causing UI to hang更新 sqlite 数据库并使用 SimpleCursorAdapter 导致 UI 挂起
【发布时间】:2014-02-10 14:54:26
【问题描述】:

编辑:tl;dr 如何使用内容解析器和 simpleCursorAdapter 插入 db 而不挂起 listfragment?

我有一个用于下载 JSON 数据的 AsyncTask。在该任务的 onPostExecute() 中,我调用 handleNewMessageReponse() 以使用结果更新我的 Sqlite 数据库。

handleNewMessageResponse()的代码

private void handleNewMessagesResponse() {

    getActivity().runOnUiThread(new Runnable() {
        @Override
        public void run() {

     //Step 1. Ensure we got something back.
        if (mMessagesResponse.getMessages().length > 0){

            //Step 2. Delete all Message DB rows.
            getActivity().getContentResolver().delete(EntegraContentProvider.CONTENT_URI_MESSAGES, null, null);

            //Step 3. Populate DB with new info.
           //Hangs UI here.
            for (int i = 0; i < mMessagesResponse.getMessages().length; i++) {
                Messages messages = mMessagesResponse.getMessages()[i];

                ContentValues values = new ContentValues();
                values.put("id", messages.getId());
                values.put("parent", messages.getParent());
                values.put("type", messages.getType());
                values.put("user", messages.getUser());
                values.put("subject", messages.getSubject());
                values.put("body", messages.getBody());
                values.put("datetime", messages.getDatetime());
                values.put("status", messages.getStatus());
                values.put("touched", messages.getTouched());

                //Perform the Insert.
                getActivity().getContentResolver().insert(
                        EntegraContentProvider.CONTENT_URI_MESSAGES, values);

                mWasDataLoaded = true;

            }

        }

            mDataListener.onDataFetchComplete();

        }

    });
}

这都包含在一个实现 LoaderManager.LoaderCallbacks 的 ListFragment 中

此片段的 onCreate 如下所示:

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

    String[] uiBindFrom = { "user", "subject" };
    int[] uiBindTo = { R.id.messageUser , R.id.messageTitle };

    getLoaderManager().initLoader(MESSAGES_LIST_LOADER, null, this);

    adapter = new SimpleCursorAdapter(
            getActivity(), R.layout.messages_list_item,
            null, uiBindFrom, uiBindTo,
            CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

    setListAdapter(adapter);

}

一切都很好,除了一个例外,下面的代码部分最多需要 10 秒才能执行并在 10 秒内挂起 UI/List。

此部分是 UI 挂起的时间:

//Step 3. Populate DB with new info.
            for (int i = 0; i < mMessagesResponse.getMessages().length; i++) {
                Messages messages = mMessagesResponse.getMessages()[i];

                ContentValues values = new ContentValues();
                values.put("id", messages.getId());
                values.put("parent", messages.getParent());
                values.put("type", messages.getType());
                values.put("user", messages.getUser());
                values.put("subject", messages.getSubject());
                values.put("body", messages.getBody());
                values.put("datetime", messages.getDatetime());
                values.put("status", messages.getStatus());
                values.put("touched", messages.getTouched());

                //Perform the Insert.
                getActivity().getContentResolver().insert(
                        EntegraContentProvider.CONTENT_URI_MESSAGES, values);

有人能指出我正确的方向并告诉我这个看似常见的任务的标准方法是什么吗?这应该很简单,但很难找到适合我用例的示例。

谢谢。

这是我的最终代码。更加流畅:

private class EventsFetcher extends AsyncTask<String, Void, EventsResponse> {
    private static final String TAG = "EventsFetcher";
    @Override
    protected EventsResponse doInBackground(String... params) {
        EventsResponse returnResponse = null;
        try {
            //Create an HTTP client
            HttpClient client = new DefaultHttpClient();
            HttpPost post = new HttpPost(mEventsBaseUrl);

            //Perform the request and check the status code
            HttpResponse response = client.execute(post);
            StatusLine statusLine = response.getStatusLine();
            if(statusLine.getStatusCode() == 200) {
                HttpEntity entity = response.getEntity();
                InputStream content = entity.getContent();

                try {
                    //Read the server response and attempt to parse it as JSON
                    Reader reader = new InputStreamReader(content);

                    Gson gson = new Gson();

                    returnResponse=  gson.fromJson(reader, EventsResponse.class);

                    content.close();

                } catch (Exception ex) {
                    Log.e(TAG, "Failed to parse JSON due to: " + ex);
                    failedGettingEvents();
                }
            } else {
                Log.e(TAG, "Server responded with status code: " + statusLine.getStatusCode());
                failedGettingEvents();
            }
        } catch(Exception ex) {
            Log.e(TAG, "Failed to send HTTP POST request due to: " + ex);
            failedGettingEvents();
        }
        return returnResponse;
    }

    @Override
    protected void onPostExecute(EventsResponse resultResponse) {
        EventsResultsHandler resultsHandler = new EventsResultsHandler();
        resultsHandler.execute(resultResponse);
    }
}

private class EventsResultsHandler extends AsyncTask<EventsResponse, Void, String> {
    private static final String TAG = "EventsResultsHandler";

    @Override
    protected String doInBackground(EventsResponse... params) {

        try {

            EventsResponse evResponse = params[0];

            //Step 1. Ensure we got something back.
            if (evResponse.getEvents().length > 0){

                //Step 2. Populate Array with new info.
                List<ContentValues> jsonResults = new ArrayList<ContentValues>();
                for (int i = 0; i < evResponse.getEvents().length; i++) {

                    Events events = evResponse.getEvents()[i];
                    ContentValues values = new ContentValues();

                    values.put("start_date", events.getStart_date());
                    values.put("end_date", events.getEnd_date());
                    values.put("name", events.getName().replace("'","''"));
                    values.put("location", events.getLocation().replace("'","''"));
                    values.put("summary", events.getSummary().replace("'","''"));
                    values.put("lat", events.getLat());
                    values.put("lng", events.getLng());
                    values.put("is_active", events.getIs_active());
                    values.put("hide_calendar_button", events.getHide_calendar_button());
                    values.put("hide_map", events.getHide_map());

                    jsonResults.add(values);

                }

                //Step 3. Delete all data in table.
                getActivity().getContentResolver().delete(EntegraContentProvider.CONTENT_URI_EVENTS, null, null);

                //Step 4. Insert new data via via Bulk insert.
                getActivity().getContentResolver().bulkInsert(EntegraContentProvider.CONTENT_URI_EVENTS, jsonResults.toArray(new ContentValues[jsonResults.size()]));

                mWasDataLoaded = true;

            }

        } catch(Exception ex) {
            Log.e(TAG, "Failed to update results due to: " + ex);
            failedGettingEvents();
        }
        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        mDataListener.onDataFetchComplete();
    }
}

【问题讨论】:

  • 如果 ui 挂起,你应该在后台线程而不是 ui one 中更新你的数据库
  • pskink,这不是我在上面的代码中所做的吗? getActivity().runOnUiThread(new Runnable() { @Override public void run() {
  • 对 runOnUiThread 的调用会安排一个 runnable 在 UI 线程上执行(该方法确实如其名称所暗示的那样......)。您需要在后台线程或另一个 AsyncTask 上启动 handleNewMessagesResponse 代码。
  • NigelK,对不起,我忘了说我试过了。当我按照您的建议进行操作时,列表不再完全挂起,但滚动时非常生涩。然后它消失,然后在最后闪回。

标签: android sqlite user-interface bulkinsert android-contentresolver


【解决方案1】:

您不想在 UI 线程上运行 DB 更新,因为这会如您所见那样挂起 UI。

但是您的更新方法可能会导致您所描述的问题,因为您使用的是游标适配器。

您正在有效地清除数据库并重新填充。如果光标适配器在您重写所有行之前运行,您将在上面的第二条评论中看到您正在谈论的结果。

你有两个选择,第一个是加载到内存中,然后通过交换适配器中指向列表数据的指针来快速交换列表适配器内部列表,然后告诉列表适配器事情发生了变化。您必须在 UI 线程上进行此交换,但由于它只是更新指向数据的指针,因此速度很快并保持 UI 响应。缺点是您必须在内存中有两个数据副本才能进行交换。

第二个可能更难的是更改与服务器同步的方式,这样您就不必清除所有内容并重新加载所有内容,而是删除、添加或修改单个记录。我推荐这条路线,但它可能更适合你。

【讨论】:

  • 我将编辑我的问题并展示我最终做了什么。它更好但并不完美。使用这种新的更新方式,列表和 UI 保持流畅,但您可以在数据刷新期间看到列表有时会闪烁,然后您可以看到一些记录出现。不完美但可用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-01
  • 1970-01-01
  • 2013-09-13
  • 2020-05-29
  • 2015-11-19
相关资源
最近更新 更多