如何使有NotifyChange()两项活动之间的工作? [英] How to make notifyChange() work between two activities?

查看:149
本文介绍了如何使有NotifyChange()两项活动之间的工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个活动ActitvityA持有填充由CursorLoader列表视图。我想切换到ActivityB和改变一些数据,看看那些反映在列表视图中ActivityA变化。

I have an activity ActitvityA that holds a listview populated by a CursorLoader. I want to switch to ActivityB and change some data and see those changes reflected in listview in ActivityA.

public class ActivityA implements LoaderManager.LoaderCallbacks<Cursor>
{ 
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        getSupportLoaderManager().initLoader(LOADER_ID, null, this);
        mCursorAdapter = new MyCursorAdapter(   
            this,
            R.layout.my_list_item,
            null,
            0 );
    }
        .
        .
        .

    /** Implementation of LoaderManager.LoaderCallbacks<Cursor> methods */
    @Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle arg1) {
        CursorLoader result;
        switch ( loaderId ) {           
        case LOADER_ID:
            /* Rename v _id is required for adapter to work */
            /* Use of builtin ROWID http://www.sqlite.org/autoinc.html */
            String[] projection = {
                    DBHelper.COLUMN_ID + " AS _id",     //http://www.sqlite.org/autoinc.html
                    DBHelper.COLUMN_NAME    // columns in select
            }
            result = new CursorLoader(  ActivityA.this,
                                        MyContentProvider.CONTENT_URI,
                                        projection,
                                        null,
                                        new String[] {},
                                        DBHelper.COLUMN_NAME + " ASC");
            break;
        default: throw new IllegalArgumentException("Loader id has an unexpectd value.");
    }
    return result;
}


    /** Implementation of LoaderManager.LoaderCallbacks<Cursor> methods */
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        switch (loader.getId()) {
            case LOADER_ID:
                mCursorAdapter.swapCursor(cursor);
                break;
            default: throw new IllegalArgumentException("Loader has an unexpected id.");
        }
    }
        .
        .
        .
}

从ActivityA我切换到ActivityB,我改变了基础数据。

From ActivityA I switch to ActivityB where I change the underlying data.

// insert record into table TABLE_NAME
ContentValues values = new ContentValues();
values.put(DBHelper.COLUMN_NAME, someValue);
context.getContentResolver().insert( MyContentProvider.CONTENT_URI, values);

MyContentProvider的细节:

The details of MyContentProvider:

public class MyContentProvider extends ContentProvider {
    .
    .
    .

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        int uriCode = sURIMatcher.match(uri);
        SQLiteDatabase database = DBHelper.getInstance().getWritableDatabase();
        long id = 0;
        switch (uriType) {
        case URI_CODE:
            id = database.insertWithOnConflict(DBHelper.TABLE_FAVORITE, null, values,SQLiteDatabase.CONFLICT_REPLACE);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);  // I call the notifyChange with correct uri
        return ContentUris.withAppendedId(uri, id);
    }


    @Override
    public Cursor query(Uri uri,
                        String[] projection,
                        String selection,
                        String[] selectionArgs,
                        String sortOrder) {

        // Using SQLiteQueryBuilder instead of query() method
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

        int uriCode = sURIMatcher.match(uri);
        switch (uriCode) {
        case URI_CODE:
            // Set the table
            queryBuilder.setTables(DBHelper.TABLE_NAME);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
        }
        SQLiteDatabase database = DBHelper.getInstance().getWritableDatabase();
        Cursor cursor = queryBuilder.query( database, projection, selection, selectionArgs, null, null, sortOrder);
        // Make sure that potential listeners are getting notified
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }
}

据我所知去这应该是足够了。但是,这是行不通的。 在返回到ActivityA列表视图是不变的

我按照事情的调试器,这是发生了什么。

I have followed things with debugger and this is what happens.

第一次ActivityA,被称为在顺序的方法

First visit ActivityA, the methods that are called in that order

MyContentProvider.query()    
ActivityA.onLoadFinished()

列表视图显示正确的价值观。 现在,我切换到activityB和更改数据

The listview displays the correct values. And now I switch to activityB and change the data

MyContentProvider.insert()  // this one calls getContext().getContentResolver().notifyChange(uri, null);
MyContentProvider.query()
//As we can see the MyContentProvider.query is executed. I guess in response to notifyChange().
// What I found puzzling why now, when ActivityB is still active ?

返回ActivityA

Return to ActivityA

!!! ActivityA.onLoadFinished() is not called    

我看过什么是我能做这个,花了近看很多的StackOverflow问题但所有这些问题/答案围绕setNotificationUri()和notifyChangeCombo(),我实现。为什么这个不能跨活动工作?

I have read anything I could about this, took a close look at a lot of stackoverflow questions yet all those question/answers revolve around setNotificationUri() and notifyChangeCombo() which I implemented. Why does this not work across activities?

例如,如果强行刷新在ActivityA.onResume()与

If for example force refresh in ActivityA.onResume() with

getContentResolver().notifyChange(MyContentProvider.CONTENT_URI, null, false);

然后刷新列表视图。但是,这将迫使刷新对每份简历,无论数据是否被更改或不。

then it refreshes the list view. But that would force refresh on every resume regardless if data was changed or not.

推荐答案

在抓我的头和无私的参与,从pskink的两个长两天我自己画的什么是错的图片。 我ActivityA是一个现实要复杂得多。它采用ViewPager与PagerAdapter与实例化列表视图。 起初,我的onCreate()方法是这样创造的那些组件:

After a two long two days of scratching my head and altruistic engagement from pskink I painted myself a picture of what was wrong. My ActivityA is in a reality a lot more complicated. It uses ViewPager with PagerAdapter with instantiates listviews. At first I created those components in onCreate() method something like this:

@Override
public void onCreate(Bundle savedInstanceState)
{
        ...
    super.onCreate(savedInstanceState);
    // 1 .ViewPager
    viewPager = (ViewPager) findViewById(R.id.viewPager);
    ...
    viewPager.setAdapter( new MyPagerAdapter() );
    viewPager.setOnPageChangeListener(this); */
    ...
    // 2. Loader
    getSupportLoaderManager().initLoader(LOADER_ID, null, this);
    ...
    // 3. CursorAdapter
    myCursorAdapter = new MyCursorAdapter(
                    this,
                    R.layout.list_item_favorites_history,
                    null,
      0);
}

某处,我注意到,这是创造了错误的命令行。为什么没有产生一些错误是因为PagerAdapter.instantiateItem()被调用aftter的onCreate()完成。我不知道为什么或如何这引起了原来的问题。或许真的没有和列表视图,适配器和内容观察家正确连接。我并没有深入到这一点。

Somewhere along the line I noticed that this is wrong order of creating. Why it didn't produce some error is because PagerAdapter.instantiateItem() is called aftter onCreate() finishes. I dont know why or how this caused the original problem. Maybe something did not wire correctly with listviews, adapters and content observers. I didn't dig into that.

我改变了以:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    ...
    // 1. CursorAdapter
    myCursorAdapter = new MyCursorAdapter(
                    this,
                    R.layout.list_item_favorites_history,
                    null,
                    0);
    ...
    // 2. Loader
    getSupportLoaderManager().initLoader(LOADER_ID, null, this);
    ...
    // 3 .ViewPager
    viewPager = (ViewPager) findViewById(R.id.viewPager);
    ...
    viewPager.setAdapter( new MyPagerAdapter() );
    viewPager.setOnPageChangeListener(this); */
    ...        
}

这神奇使它在约75%的情况下工作。当我研究的输出目录下载我注意到ActivityA()的onStop()被调用在不同的时间。当它工作它被称为很晚了,我可以在logcat中的onLoadFinished看到()执行。有时ActivityA.onStop()查询后立即执行,然后onLoadFinished()不被调用。这使我想起DeeV雅贴在他的有关游标正在取消从ContentResolver的答案。这也许会是这样。 是什么让事情以某种方式揭发的事实是简单的演示pskink坚持在做的工作,我的应用程序没有,尽管他们在关键点​​是相同的。这使我注意异步的东西,我的onCreate()方法。在现实中我ActivityB复杂,因此我们有足够的时间ActivityA停止。 我注意到也什么(这确实使事情变得更加困难排序)是:如果我跑我的75%的版本在调试模式(无断点),那么成功率下降到0。ActivityA光标加载完成,所以我onLoadFinished之前停止()永远不会调用和列表视图不会被更新。

This magically made it work in about 75% of the cases. When I studied CatLog output I noticed that ActivityA().onStop() is called at different times. When it works it is called late and I can see in logcat that onLoadFinished() executes. Sometimes ActivityA.onStop() executes right after query and then onLoadFinished() is not called at all. This brings me to what DeeV jas posted in his answer about cursors being unregistered from ContentResolver. This just might be the case. What made things to somehow came to light was the fact that simple demonstrator pskink insisted on did work and my app didn't although they were identical in key points. This brought my attention to asynchronous things and my onCreate() method. In reality my ActivityB is complicated so it gives enough time for ActivityA to stop. What I noticed also (and this did make things more difficult to sort) was that if I run my 75% version in debug mode (with no breakpoints) then the success rate falls to 0. ActivityA is stopped before cursor load finishes so my onLoadFinished() is never called and listviews are never updated.

两个关键点:

  • 创建不知怎的顺序OD ViewPager,CursorAdapter的和 CursorLoader很重要
  • ActivityA可能是(现在也是)之前​​停止 光标被加载。
  • Somehow the order of creation od ViewPager, CursorAdapter and CursorLoader is important
  • ActivityA may be (and is) stopped before cursor is loaded.

但是,即使这不是。如果我看一看简化序列,然后我看到ActivityA.onStop()被执行之前,内容提供商插入一条记录。我看不出有任何查询,同时ActivityB处于活动状态。但是,当我回到ActivityA一个查询execeuted laodFinished()以下和ListView被刷新。事实并非如此在我的应用程序。它总是同时还在ActivityB执行查询,为什么???这毁了我的理论有关的onStop()是罪魁祸首。

But even this is not. If I take a look at a sequence of simplified then I see that ActivityA.onStop() is executed before content provider inserts a record. I see no query while ActivityB is active. But when i return to ActivityA a query is execeuted laodFinished() follows and listview is refreshed. Not so in my app. It always executes a query while still in ActivityB, why??? This destroy my theory about onStop() being the culprit.

(非常感谢ps​​kink和DeeV)

(Big thanks to pskink and DeeV)

更新

在这个问题上有很多高腰的时间后,我终于钉这个问题的原因。

After a lot of waisted time on this issue I finally nailed the cause of the problem.

简短说明:

我有以下类:

ActivityA - contains a list view populated via cursor loader.
ActivityB - that changes data in database
ContentProvider - content provider used for data manipulation and also used by cursorloader.

问题:

数据操作ActivityB后所做的更改不会在ActivityA列表视图中显示。列表视图不会刷新。

After data manipulation in ActivityB the changes are not shown in list view in ActivityA. List view is not refreshed.

在我很多目测和研究logcat的痕迹,我看到的东西进行以下顺序:

After I lot of eyeballing and studying logcat traces I have seen that things proceed in following sequence:

ActivityA is started

    ActivityA.onCreate()
        -> getSupportLoaderManager().initLoader(LOADER_ID, null, this);

    ContentProvider.query(uri)  // query is executes as it should

    ActivityA.onLoadFinished()  // in this event handler we change cursor in list view adapter and listview is populated


ActivityA starts ActivityB

    ActivityA.startActivity(intent)

    ActivityB.onCreate()
        -> ContentProvider.insert(uri)      // data is changed in the onCreate() method. Retrieved over internet and written into DB.
            -> getContext().getContentResolver().notifyChange(uri, null);   // notify observers

    ContentProvider.query(uri)
    /*  We can see that a query in content provider is executed.
        This is WRONG in my case. The only cursor for this uri is cursor in cursor loader of ActivityA.
        But ActivityA is not visible any more, so there is no need for it's observer to observe. */

    ActivityA.onStop()
    /*  !!! Only now is this event executed. That means that ActivityA was stopped only now.
        This also means (I guess) that all the loader/loading of ActivityA in progress were stopped.
        We can also see that ActivityA.onLoadFinished() was not called, so the listview was never updated.
        Note that ActivityA was not destroyed. What is causing Activity to be stopped so late I do not know.*/


ActivityB finishes and we return to ActivityA

    ActivityA.onResume()

    /*  No ContentProvider.query() is executed because we have cursor has already consumed
        notification while ActivityB was visible and ActivityA was not yet stopped.
        Because there is no query() there is no onLoadFinished() execution and no data is updated in listview */

所以,问题不在于ActivityA停止很快,但它停下来晚了。的数据被更新,并通知 创建ActivityB和停止ActivityA的介于两者之间发送。 解决的办法是迫使装载机ActivityA停止加载ActivityB开始之前。

So the problem is not that ActivityA is stopped to soon but that it is stopped to late. The data is updated and notification sent somewhere between creation of ActivityB and stopping of ActivityA. The solution is to force loader in ActivityA to stop loading just before ActivityB is started.

ActivityA.getSupportLoaderManager().getLoader(LOADER_ID).stopLoading(); // <- THIS IS THE KEY
ActivityA.startActivity(intent)

该停止加载器和(我再猜)prevents光标消耗通知,而活动是在上述无人过问的状态。 事件的顺序现在

This stops the loader and (I guess again) prevents cursor to consume notification while activity is in the above described limbo state. The sequence of events now is:

ActivityA is started

    ActivityA.onCreate()
        -> getSupportLoaderManager().initLoader(LOADER_ID, null, this);

    ContentProvider.query(uri)  // query is executes as it should

    ActivityA.onLoadFinished()  // in this event handler we change cursor in list view adapter and listview is populated


ActivityA starts ActivityB

    ActivityA.getSupportLoaderManager().getLoader(LOADER_ID).stopLoading();
    ActivityA.startActivity(intent)

    ActivityB.onCreate()
    -> ContentProvider.insert(uri)
        -> getContext().getContentResolver().notifyChange(uri, null);   // notify observers

    /*  No ContentProvider.query(uri) is executed, because we have stopped the loader in ActivityA. */

    ActivityA.onStop()
    /*  This event is still executed late. But we have stopped the loader so it didn't consume notification. */


ActivityB finishes and we return to ActivityA

    ActivityA.onResume()

    ContentProvider.query(uri)  // query is executes as it should

    ActivityA.onLoadFinished()  // in this event handler we change cursor in list view adapter and listview is populated

/* The listview is now populated with up to date data */

这是最优雅的解决方案,我可以找到。无需重新启动装载机等。 但我仍想听到来自某人有更深入的了解这个问题的评论。

This was the most elegant solution I could find. No need to restart loaders and such. But still I would like to hear a comment on that subject from someone with a deeper insight.

这篇关于如何使有NotifyChange()两项活动之间的工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆