Android android.database.StaleDataException

2019-08-06 09:23发布

I've updated my device to Android ICS (4.0.3) I have an activity which contains a list view filled in with data read from a database. The data are arranged in the listview using an extension of the ResourceCursorAdapter.

Once the list has been loaded and shown on the screen I've pressed the home screen in order to bring up the home screen. Then I've recovered my application from the recents (long pressing the home screen) and suddenly I got the following exception:

05-10 15:49:17.925: E/AndroidRuntime(10721): Caused by: android.database.StaleDataException: Attempted to access a cursor after it has been closed.
05-10 15:49:17.925: E/AndroidRuntime(10721):    at android.database.BulkCursorToCursorAdaptor.throwIfCursorIsClosed(BulkCursorToCursorAdaptor.java:75)
05-10 15:49:17.925: E/AndroidRuntime(10721):    at android.database.BulkCursorToCursorAdaptor.requery(BulkCursorToCursorAdaptor.java:144)
05-10 15:49:17.925: E/AndroidRuntime(10721):    at android.database.CursorWrapper.requery(CursorWrapper.java:186)
05-10 15:49:17.925: E/AndroidRuntime(10721):    at android.app.Activity.performRestart(Activity.java:4505)
05-10 15:49:17.925: E/AndroidRuntime(10721):    at android.app.Activity.performResume(Activity.java:4531)
05-10 15:49:17.925: E/AndroidRuntime(10721):    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2446)

I've read about the behavior of the OS in my case and the cursor seem to be invalidated. The problem is that despite I've registered a DataSetObserver the method onInvalidated is never called, and before the crash no Activity's method ( like onResume, onRestart ) is called as well. The code even didn't reach the bindView in the Adapter.

Could you please help me with that? I can provide more info and relevant code if you need.

Thanks in advance

Here the code, sorry if it is a mess but I'm just making it work before fine tuning:

public class CallLogsList extends Activity implements Runnable,
        OnItemClickListener {

    // ... various declaration here

    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case CALLLOGS_LOAD_DONE:
                loadCurrCallLogsList(true);
                break;
        case SHOW_ALL_LOG:
            case SHOW_MISSED_LOG:
            case SHOW_OUTGOING_LOG:
            case SHOW_INCOMING_LOG:
                // - set the adapter
                if (null == mCad) {
                    // the first time the adapter need to be called
                    mCad = new CallLogsCursorAdapter(mContext, mDataCursor);
                    mCallLogsListView.setAdapter(mCad);
                } else {
                    mCad.changeCursor(mDataCursor);
                    mCad.notifyDataSetChanged();
                }
                break;
            } // end of switch ctrl structure
            return;
        } // end of method handleMessage

    }; // end of Handler object


    /**
     * The following inner class implements the custom adapter to contain the
     * call log entries as read from the database
 * 
     */
    class CallLogsCursorAdapter extends ResourceCursorAdapter {
        Cursor mCallLogCursor = null;
        CallLogDataSetObserver mLogDataObserver = null;

        /**
         * Class constructor
         * 
         * @param context
         * @param c
         */
        public CallLogsCursorAdapter(Context context, Cursor c) {
            super(context, R.layout.recent_calls_list_item, c);
            mLogDataObserver = new CallLogDataSetObserver();
            mCallLogCursor = c;
            mCallLogCursor.registerDataSetObserver(mLogDataObserver);
            return;
        } // end of class constructor

        /**
         * This method binds an existing view to the data pointed to by the
         * cursor
         */
        @Override
        public void bindView(View view, Context context, Cursor cursor) {
                  // ... bind data to the view here
        } // end of method bindView

        /**
         * This method inflates the new view from the specified resource Such
         * resource has been passed to the super class in the call at the parent
         * class constructor we did in this derived class
         */
        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            View view = super.newView(context, cursor, parent);
                    // ... create the view
            return view;
        } // end of method newView

        /**

        /**
         * The data set observer for the data used by this adapter
         */
        private class CallLogDataSetObserver extends DataSetObserver {

            @Override
            public void onChanged() {
                return;
            } // end of method onChanged

            @Override
            public void onInvalidated() {
                if( null != mCallLogCursor ) {
                                // TODO: Remove this call coz the UI can get stuck
                                // if the call log is too long. Just ask for a new         
                                // cursor asynchronously
                                mCallLogCursor.requery();
                             }
                return;
            } // end of method onInvalidated

        } // end of class inner class CallLogDataSetObserver

    } // end of class CallLogsCursorAdapter



    /**
     * This method is called the first time the activity is created
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(THIS_FILE, "Enter onCreate");
        super.onCreate(savedInstanceState);
             // ... initialization code here
        loadCurrCallLogsList(false);
        return;
    } // end of method onCreate




    /**
     * This method loads the current communication list
     */
    private synchronized void loadCurrCallLogsList(final boolean fromPullDown) {
        if (false == fromPullDown) {
            showLoadingView(true);
        }
        // start the loader thread
        Thread loader = new Thread(this);
        loader.start();
        return;
    } // end of method loadCurrCommunicationsList

    /**
     * This method is called when the activity is going to be destroyed
     */
    @Override
    protected void onDestroy() {
        if (null != database) database.close();
        database = null;
        if (mDataCursor != null)  mDataCursor.close();
        mDataCursor = null;
        // call the super class onDestroy method
        super.onDestroy();
    }


    /**
     * This method create the menu for the activity
     * 
     * @param menu
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.in_call_log_list_menu, menu);
        return super.onCreateOptionsMenu(menu);
    } // end of method onCreateOptionsMenu

    /**
     * This method is called when the menu is going to be shown
     */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if (false == CordlessApplication.getInstance().canUseJungoApi()) {
            menu.findItem(R.id.menu_item_edit).setEnabled(false);
        } else {
            // enable the edit item in the menu if the list is not empty
            menu.findItem(R.id.menu_item_edit).setEnabled(
                    null != mCad && !mCad.isEmpty());
        }
        return super.onPrepareOptionsMenu(menu);
    } // end of method onPrepareOptionsMenu

    /**
     * This method is called when the user click on an item in the menu
     * 
     * @param item
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        item.setChecked(true);
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.sub_menu_item_all:
            mCurrentLogView = CURR_LIST_ALL_LOG;
            mListHeader.setText(R.string.all_calls_log_header_txt);
            loadCurrCallLogsList(false);
            break;
        case R.id.sub_menu_item_in_only:
            mCurrentLogView = CURR_LIST_INCOMING_LOG;
            mListHeader.setText(R.string.received_calls_log_header_txt);
            loadCurrCallLogsList(false);
            break;
        case R.id.sub_menu_item_out_only:
            mCurrentLogView = CURR_LIST_OUTGOING_LOG;
            mListHeader.setText(R.string.dialled_calls_log_header_txt);
            loadCurrCallLogsList(false);
            break;
        case R.id.sub_menu_item_miss_only:
            mCurrentLogView = CURR_LIST_MISSED_LOG;
            mListHeader.setText(R.string.missed_calls_log_header_txt);
            loadCurrCallLogsList(false);
            break;
        case R.id.menu_item_edit:
            startModifyActivity();
            break;
           default:
            return super.onOptionsItemSelected(item);
        }

        return (true);
    } // end of method onOptionsItemSelected




    /**
     * This method is called when the user comes back to this activity from a
     * sub activity
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (Activity.RESULT_OK != resultCode)
            return;
        switch (requestCode) {
        case DialingUtils.REQ_CODE_ADD_NEW_CONTACT: // pass through
        case DialingUtils.REQ_CODE_UPDATE_EXISTING_CONTACT:
            // refresh the call log list
            mCad.getCursor().requery();
            mCad.notifyDataSetChanged();
            break;
        case DialingUtils.REQ_CODE_PICK_CONTACT:
            DialingUtils.updateExistingContact(this, data.getData(),
                    mCallerInfo.mPhoneNumber, true);
            break;
        }
    } // end of method onActivityResult

    /**

    /**
     * This method load a filter version of the call logs
     * 
     * @param filter
     */
    private void loadFilteredData(final int filter) {
        if( null != mDataCursor ) mDataCursor.close();
        mDataCursor = null;
        // see whether it is needed to recover the database
        if (null == database) {
            database = new DBAdapter(mContext);
            database.open();
        }
        // read all the call logs from the database
        mDataCursor = database.getFilteredCallLogs(filter);
        return;
    } // end of method loadFilterData

    /**
     * This method is called when the user press a key on the device We use this
     * method to handle the press on the back key
     */
    @Override

    /**
     * This method is called in order to load the data from the 
     * local database in a separated thread
     */
    @Override
    public synchronized void run() {
        Looper.prepare();
        synchronized (MyConstants.mCallLogsMutex) {
            switch (mCurrentLogView) {
            case CURR_LIST_ALL_LOG:
                loadFilteredData(0);
                mHandler.sendEmptyMessage(SHOW_ALL_LOG);
                break;
            case CURR_LIST_MISSED_LOG:
                loadFilteredData(CallLog.Calls.MISSED_TYPE);
                mHandler.sendEmptyMessage(SHOW_MISSED_LOG);
                break;
            case CURR_LIST_OUTGOING_LOG:
                loadFilteredData(CallLog.Calls.OUTGOING_TYPE);
                mHandler.sendEmptyMessage(SHOW_OUTGOING_LOG);
                break;
            case CURR_LIST_INCOMING_LOG:
                loadFilteredData(CallLog.Calls.INCOMING_TYPE);
                mHandler.sendEmptyMessage(SHOW_INCOMING_LOG);
                break;
            }
        } // end of synch block
    } // end of method run


} // end of class CallLogsList

3条回答
【Aperson】
2楼-- · 2019-08-06 10:00

I've found the cause and the solution is following: Just avoid Activity.managedQuery(...) and Activity.startManagingCursor(...) when you have to deal with Cursor or Database. The documentation says they are deprecated indeed.

查看更多
孤傲高冷的网名
3楼-- · 2019-08-06 10:16

Instead of using Activity.ManagedQuery() use the Context.Query()

string[] projection = new string[] { CalendarContract.Calendars.InterfaceConsts.Id };
        string selection = CalendarContract.Calendars.InterfaceConsts.Id + "=?";
        string[] selectionArgs =new string[]{Constants.CALENDAR_ID.ToString()};
        var cursor = m_ContentResolver.Query(calendarUri, projection, selection, selectionArgs, null);

Example from Xamarin App, Calendar manipulation

查看更多
Rolldiameter
4楼-- · 2019-08-06 10:17

Use

Activity.getContentResolver().query() 

instead of

Activity.managedQuery()
查看更多
登录 后发表回答