Kevin Bradshaw
Kevin Bradshaw

Reputation: 6427

Content Observer onChange method called twice after 1 change in cursor

I have an app in which I am hoping to send details in the android contact list to a remote server, so that the user can see his contacts online. To do this I want to notify the remote server of any changes made on the phone to the contacts list.

I have set up a ContentObserver on the 'ContactsContract.Contacts.CONTENT_URI' from a service that gets started when the phone is booted.

I have a number of quiestions, the first 2 are incidental, the third is my major concern.

1: Once I have set up a service that registers a ContentObserver on my Cursor, does that observer only exist within the service? I mean, if the service is killed, does the contentObserver continue to observe?

2: I suspect the answer is no, but I will ask anyway. Is there anyway of knowing which contact being updated is triggering the onchange method of my contentObserver? currently I have to compile the list of all teh contacts on the phone and send them off to my remote server, it would be so much easier to just send details of the contacts being updated.

3: This is my main question, when I make a change to my Contact List the onChange method is being fired twice in quick succession. 1 change, 2 calls. Is there anyway of managing this?

    public class ContactService extends Service {

    JSONArray contactList;

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {
        Log.i("C2DM","content observers initialised");
        super.onCreate();



        //Call Log Content Provider observer

        MyContentContactsObserver contactsObserver = new MyContentContactsObserver();
        ContactService.this.getContentResolver().registerContentObserver (ContactsContract.Contacts.CONTENT_URI, true, contactsObserver);   

    }

    private class MyContentContactsObserver extends ContentObserver {

        public MyContentContactsObserver() {
            super(null);
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);    
            Log.i("LOG","detected change in contacts: "+selfChange);
        }
    }
}

REsults in 2 quick lines in my logCat:

 detected change in contacts: false 
 detected change in contacts: false

Upvotes: 17

Views: 14173

Answers (5)

N-JOY
N-JOY

Reputation: 7635

i did a similar implementation and had set a ContentObserver over calendar's events URI. and i faced all the problems, you are facing now. So your question with my solutions/suggestions goes here....

1) Once I have set up a service that registers a ContentObserver on my Cursor, does that observer only exist within the service? I mean, if the service is killed, does the contentObserver continue to observe?

No, it will not observe the contents your contents over URI. but there is solution to this. you can create an always running service. This service will be created again as soon as it is dismissed due to some memory issue or other unexpected dismissal. It will always continue to run unless it is dismissed explicitly. To create such service override OnStartCommand(Intent intent, int flags, final int startId) in your Service class and return START_STICKY. Have a look at the code below.

public int onStartCommand(Intent intent, int flags, final int startId) {

    String authenticationKey = ((MainApplication) getApplicationContext()).getDbConnector().getUserAuthDefault() ;
    // if user is not registered yet, stop this service, it may be called from boot reciever.
    if(authenticationKey == null){
        stopSelf();
    }

    // restart, if we get killed.
    return START_STICKY;
}  

.

2: I suspect the answer is no, but I will ask anyway. Is there anyway of knowing which contact being updated is triggering the onchange method of my contentObserver? currently I have to compile the list of all teh contacts on the phone and send them off to my remote server, it would be so much easier to just send details of the contacts being updated.

You again guessed it right.:). the answer is No.
There is no way to know specifically which contact has been updated. ContentObserver just provide an event to let you know that some change has been made to the data pointed by the uri. But i think you are handling the situation inefficiently, as you are compiling list of all the contacts and sending them back to the server.
I would suggest you to maintain a list of contacts, which are successfully sent to server, in the local storage. And next time when you get a call in onChange() method of contentObserver you fetch all contacts from content_URI, compare them with previously stored list and send only updated contacts to server.

3: This is my main question, when I make a change to my Contact List the onChange method is being fired twice in quick succession. 1 change, 2 calls. Is there anyway of managing this?

Yes this happens, but i guess you are in wrong impression. In my case it used to fire randomly twice or thrice or even more times. so i had set a threshold_time interval. A time interval within which if ContentObserver is fired again, then i would not process it. I did something like this..

long lastTimeofCall = 0L;
long lastTimeofUpdate = 0L;
long threshold_time = 10000;

    /* (non-Javadoc)
     * @see android.database.ContentObserver#onChange(boolean)
     */
    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);

        Log.d("content service", "on change called");

        lastTimeofCall = System.currentTimeMillis();

        if(lastTimeofCall - lastTimeofUpdate > threshold_time){

         //write your code to find updated contacts here

          lastTimeofUpdate = System.currentTimeMillis();
        }

    }  

Hope these suggestions/solutions would help.

Upvotes: 31

姚广东
姚广东

Reputation: 1

you may have two or more instances of ContactService which all of them are registed the same ContactsContract.Contacts.CONTENT_URI.

Upvotes: 0

fusiller
fusiller

Reputation: 1

Faced the same problem in my own code and found out that I was calling registerContentObserver() twice. Although it was for the exact same URI and within the same class my onChange(boolean selfChange) method was called twice as a result.

Upvotes: -1

Tomasz Gawel
Tomasz Gawel

Reputation: 8520

Consider how you register your observer. The second argument of registerContentObserver is boolean notifyForDescendents, you have set it to true. so you will be notified when either ContactsContract.Contacts.CONTENT_URI or any of its descandants uris were changed. It may be that some operation for contact with given id triggers notification both for the specific contact uri and global contacts uri - with second argument being true your observer observes both.

Another thing is that You can register content observer for specific contact id uri - ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId). This however has nothing to do with your issue but registering separate observer for every contact looks rather ugly ;)

Another suggestion: Maybe keep observer as final field not recreate it in onCreate (but only register it there).

public class ContactService extends Service {

    private final ContentObserver contactsObserver = new ContentObserver(null) {

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);    
            Log.i("LOG","detected change in contacts: "+selfChange);

            //your code here
        }
    };

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {

        super.onCreate();

        getContentResolver().registerContentObserver(
                ContactsContract.Contacts.CONTENT_URI, true, contactsObserver);   
    }

    @Override
    public void onDestroy() {

        getContentResolver().unregisterContentObserver(contactsObserver);

        super.onDestroy();
    }
}

Upvotes: 13

dizzl
dizzl

Reputation: 51

To come right to your nr. 3: How much time is in between the two calls to your observer? I think your observer gets called twice because of the contacts sync, which also updates the database. Ie. first time is your actual update, second time is when the sync finished and registers its sync values in the same contact row.

Could you turn off sync and see if it still gets called twice?

Upvotes: 5

Related Questions