开发者

changeCursor causes CalledFromWrongThreadException exception... but only once

开发者 https://www.devze.com 2023-03-12 20:37 出处:网络
I\'m working with a listview and a context menu. When the user long presses an item in the context menu, a little mark appears next to the item in the list, to let the user know the item has been mark

I'm working with a listview and a context menu. When the user long presses an item in the context menu, a little mark appears next to the item in the list, to let the user know the item has been marked a favorite. This is all working well, EXCEPT that it causes a force close the very first time this is done. Every other time works just fine. Here is the relevant code:

    @Override  
public boolean onContextItemSelected(MenuItem item) {  
    AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    switch (item.getItemId()) {
      case 0:
          mDbHelper.updateFavorite(info.id, 1);
          new bgRequery().execute();
          return true;
      case 1:
          mDbHelper.updateFavorite(info.id, 0);
          new bgRequery().execute();
          return true;
      default:
          return super.onContextItemSelected(item);
      }
}

private class bgRequery extends AsyncTask<Void, Integer, Void> {
    @Override
    protected Void doInBackground(Void... voids ) {
        mSpellCursor = fetchCursor();
        return null;
    }

    @Override
    protected void onPostExecute(Void voids) {
        spellsAdapter.changeCursor(mSpellCursor);
    }
}

Here is the exception:

D/SpellBook( 5362): fetching Cursor E/AndroidRuntime( 5362): FATAL EXCEPTION: background thread E/AndroidRuntime( 5362): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. E/AndroidRuntime( 5362): at android.view.ViewRoot.checkThread(ViewRoot.java:2932) E/AndroidRuntime( 5362): at android.view.ViewRoot.requestLayout(ViewRoot.java:629) E/AndroidRuntime( 5362): at android.view.View.requestLayout(View.java:8267) E/AndroidRuntime( 5362): at android.view.View.requestLayout(View.java:8267) E/AndroidRuntime( 5362): at android.view.View.requestLayout(View.java:8267) E/AndroidRuntime( 5362): at android.view.View.requestLayout(View.java:8267) E/AndroidRuntime( 5362): at android.widget.AbsListView.requestLayout(AbsListView.java:1102) E/AndroidRuntime( 5362): at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:790) E/AndroidRuntime( 5362): at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:31) E/AndroidRuntime( 5362): at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50) E/AndroidRuntime( 5362): at android.widget.CursorAdapter.changeCursor(CursorAdapter.java:260) E/AndroidRuntime( 5362): at com.zalzala.spellbookpf.SpellsAz$bgRequery.onPostExecute(SpellsAz.java:228) E/AndroidRuntime( 5362): at com.zalzala.spellbookpf.SpellsAz$bgRequery.onPostExecute(SpellsAz.java:1) E/AndroidRuntime( 5362): at android.os.AsyncTask.finish(AsyncTask.java:417)

So the error occurs when spellsAdapter.changeCursor(mSpellCursor); is called in onPostExecute. Since that function runs in the ui thread, I'm having a hard time understanding why I'm getting a background Tread error. What's making this harder to understand and debug is that it really only happens the very first time I do this. Every other time it works fine, even when the app starts fresh or the phone has rebooted. The only way to reproduce the bug is to uninstall the app and install it fresh.

Just in case anyone needs it, I'm including the code of my adapter:

public class SpellListAdapter extends CursorAdapter {
private LayoutInflater mLayoutInflater;
private Context mContext;
public SpellListAdapter(Con开发者_JAVA技巧text context, Cursor c) {
    super(context, c);
    mContext = context;
    mLayoutInflater = LayoutInflater.from(context); 
}

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
    View v = mLayoutInflater.inflate(R.layout.list_item_fave, parent, false);
    return v;
}

@Override
public void bindView(View v, Context context, Cursor c) {
    String spell = c.getString(c.getColumnIndexOrThrow(SpellDbAdapter.KEY_SPELL));
    int fave = c.getInt(c.getColumnIndexOrThrow(SpellDbAdapter.KEY_FAVORITE));

    /**
     * Next set the title of the entry.
     */

    TextView Spell = (TextView) v.findViewById(R.id.text);
    if (Spell != null) {
        Spell.setText(spell);
    }

    //Set Fave Icon
    TextView Fave = (TextView) v.findViewById(R.id.fave_icon);
    Fave.setVisibility(View.INVISIBLE);
    if (fave == 1){
        Fave.setVisibility(View.VISIBLE);
    }
}

public void update() {
    notifyDataSetChanged();
}
}

Thanks for your help.

EDIT: I can work around this issue by not calling the cursor in a separate thread, but I thought it was good practice to use another thread for it instead of using the ui thread, so I would still love some help figuring this out.


The async task may overflow sometimes. You'd better use you own thread in the onContentChanged of your CursorAdapter. Here is my code for your reference:

private class NearbyLocationListAdapter extends CursorAdapter
{
    private static final String TAG = "NearbyLocationListAdapter";

    private NearbyLocationActivity mActivity = null;
    private Thread mContentChanageHanderThread = null;

    public NearbyLocationListAdapter(NearbyLocationActivity activity, Cursor c) {
        super(activity, c, false);
        this.mActivity = activity;
    }

    // called when adding location info into location db
    @Override
    protected void onContentChanged() {
        // AsyncTask will bring RejectedExecutionException, change to use Thread
        // interrupt the original thread since data is updated again
        clearContentChanageHanderThread();

        mContentChanageHanderThread = new Thread(new Runnable() {
            // set as synchronized in case called by multiple thread
            @Override
            public synchronized void run() {
                if (Thread.currentThread().isInterrupted()) {
                    // Log.d(TAG, "Thread interrupted. Aborting.");
                    return;
                }

                final Cursor cursor = getLocationCursor();

                if (Thread.currentThread().isInterrupted()) {
                    // Log.d(TAG, "Thread interrupted after getting cursor. Aborting.");
                    if (Util.isValid(cursor)) {
                        cursor.close();
                    }

                    return;
                }

                NearbyLocationActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (Util.isValid(cursor)) {
                            // will close the original cursor and switch to new one
                            // notifyDataSetChanged will be called inside
                            // Log.d(TAG, "onContentChanged: will changeCursor");
                            changeCursor(cursor);
                        }
                        else {
                            Log.e(TAG, "onContentChanged: cursor is null or closed");
                        }
                    }
                });

            }
        });

        mContentChanageHanderThread.start();

    }

.....................................
public void clearContentChanageHanderThread() {
        //Log.v(TAG,"clearContentChanageHanderThread");

        if (mContentChanageHanderThread != null && mContentChanageHanderThread.isAlive()) {
            mContentChanageHanderThread.interrupt();
            mContentChanageHanderThread = null;
        }
    }

}


Have you tried using the runOnUiThread method? Basically it would look like this:

runOnUiThread(new Runnable() {
    public void run() {
        spellsAdapter.changeCursor(mSpellCursor);
    }
});

That way you can do whatever long computation in a separate thread but when it comes time to update the UI, that is done in the correct thread.


You must use a Handler in order to manage with UI objects . You can't (and musn't ) use or touch spellsAdapter into an AsyncTask without using a Handler with a runnable.

At first it was horrible for me understand this type of problems and deal with them . But not so much when i think of it like two roads with different speeds . You need some bridge and control to manage the cars ;) info each other. That's why the Handler exist

0

精彩评论

暂无评论...
验证码 换一张
取 消