新联系人创建而不是更新现有联系人

New contact creating rather than updating existing contact

我正在将我的应用程序与 android 默认联系人集成 application.I 想在每个联系人中显示一个选项 "xyz using MyApp" Detail.I 可以在帐户中看到我的应用程序带有同步联系人选项的部分,但我的应用程序仍然没有与现有联系人合并,而是创建一个新联系人并合并到其中。

performSync() 方法

private static void addContact(ContentResolver contentResolver,int name, int phoneNumber) {
    Log.i("XYZ", "Adding contact: " + name);
    ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();

    //Create our RawContact
    ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
    builder.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, name);
    builder.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, "com.example.xyz.myapplication");
    builder.withValue(ContactsContract.RawContacts.SYNC1, phoneNumber);
    operationList.add(builder.build());

    //Create a Data record of common type 'StructuredName' for our RawContact
    builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
    builder.withValueBackReference(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, 0);
    builder.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
    builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
    operationList.add(builder.build());

    //Create a Data record of custom type "vnd.android.cursor.item/vnd.com.example.xyz.myapplication.profile" to display a link to the Last.fm profile
    builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
    builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0);
    builder.withValue(ContactsContract.Data.MIMETYPE, "vnd.android.cursor.item/vnd.com.example.xyz.myapplication.profile");
    builder.withValue(ContactsContract.Data.DATA1, phoneNumber);
    builder.withValue(ContactsContract.Data.DATA2, "Last.fm Profile");
    builder.withValue(ContactsContract.Data.DATA3, "View profile");
    operationList.add(builder.build());

    try {
        contentResolver.applyBatch(ContactsContract.AUTHORITY, operationList);
    } catch (Exception e) {
        Log.e("XYZ", "Something went wrong during creation! " + e);
        e.printStackTrace();
    }
}

在您的 addContact 代码中,您缺少告诉 Contacts DB 将您的新原始联系人加入现有联系人的部分,因此该联系人现在将包含您的原始联系人和您的在联系人应用程序中打开该联系人时,将显示特定于应用程序的行。

查看此答案,了解如何将 RawContact 加入现有联系人:why won't contacts aggregate?

您可能需要将一些 RawContact ID 传递给您的 addContact 方法,以便它能够将两者连接在一起。

更新

与其将聚合操作与 RawContact 插入操作一起应用,不如尝试分成两个 applyBatch 调用,同时,让我们将新的原始联系人与 聚合所有 个现有的原始联系人,而不仅仅是其中一个。 试试下面的代码,确保你传递给它现有的contact-id(不是原始联系人id)和你的新原始联系人id.

private void joinIntoExistingContact(long existingContactId, long newRawContactId) {

    // get all existing raw-contact-ids that belong to the contact-id
    List<Long> existingRawIds = new ArrayList<>();
    Cursor cur = getContentResolver().query(RawContacts.CONTENT_URI, new String[] { RawContacts._ID }, RawContacts.CONTACT_ID + "=" + existingContactId, null, null);
    while (cur.moveToNext()) {
        existingRawIds.add(cur.getLong(0));
    }
    cur.close();
    Log.i("Join", "Found " + existingRawIds.size() + " raw-contact-ids");

    List<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

    // go over all existing raw ids, and join with our new one
    for (Long existingRawId : existingRawIds) {
        Builder builder = ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI);
        builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
        builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, newRawContactId);
        builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, existingRawId);
        ops.add(builder.build());
    }

    contentResolver.applyBatch(ContactsContract.AUTHORITY, ops);
}

P.S.
别开两个duplicate questions,一个就够了

另一个更新

您似乎对 ID 有点困惑。

Data个ID,RawContact个ID,Contact个ID。

CommonDataKinds.Phone._ID 将 return 一个 Data ID,标识数据 table 中存储该 phone 数字的特定行。

您也可以从 Phone table 其他 ID 中获取一个,使用: CommonDataKinds.Phone.RAW_CONTACT_ID CommonDataKinds.Phone.CONTACT_ID

您可以在这里阅读更多内容:

试试这个,这是适合我的工作代码

MainActivity

public class MainActivity extends AppCompatActivity {

    private ArrayList<String> mNames = new ArrayList<>();
    private ArrayList<String> mIDs = new ArrayList<>();
    private ArrayList<String> mNumbers = new ArrayList<>();

    @SuppressLint("StaticFieldLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED &&
                ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                        != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 123);

        } else {


//      Retrieve names from phone's contact list and save in mNames
            getContactDataBefore();

//      Apply changes to phone's contact list
            new AsyncTask<String, String, String>() {

                @Override
                protected String doInBackground(String... params) {
                    String name, number, id;
                    for (int i = 0; i < mIDs.size(); i++) {
//                    name = mNames.get(i);
                        id = mIDs.get(i);
                        number = mNumbers.get(i);
                        ContactsManager.addContact(MainActivity.this, new MyContact(id, number));
                    }
                    return null;
                }

                @Override
                protected void onPostExecute(String s) {
                    getContactDataAfter();
                }
            }.execute();
        }

    }

    private void getContactDataBefore() {
        int i = 0;

        // query all contact id's from device
        Cursor c1 = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
                new String[]{ContactsContract.Contacts._ID}, null, null, null);

        if ((c1 != null) && c1.moveToFirst()) {


            do {
                mIDs.add(c1.getString(c1.getColumnIndexOrThrow(ContactsContract.Contacts._ID)));

                i++;
            } while (c1.moveToNext() && i < c1.getCount());

            c1.close();

        }

        getPhoneNumber();
    }

    private void getPhoneNumber(){


        for (String data:mIDs){

            Cursor cursor = getContentResolver().query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    null,
                    ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?",
                    new String[]{data}, null);

            while (cursor.moveToNext())
            {
                mNumbers.add(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
            }

            cursor.close();
        }

    }
    /**
     * Method to fetch contacts after updation (for logging purposes)
     */
    private void getContactDataAfter() {
        Cursor c = getContentResolver()
                .query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);

        List<String> RIds = new ArrayList<>();
        mIDs = new ArrayList<>();
        mNumbers = new ArrayList<>();
        int i = 0;

        if (c != null && c.moveToFirst()) {
            do {
                mIDs.add(c.getString(c
                        .getColumnIndexOrThrow(ContactsContract.Contacts._ID)));
                mNames.add(c.getString(c
                        .getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)));

                Cursor c2 = getContentResolver()
                        .query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                                new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER},
                                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
                                new String[]{mIDs.get(i)}, null);

                if (c2 != null && c2.moveToFirst()) {
                    do {
                        mNumbers.add(c2.getString(c2
                                .getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER)));
                    } while (c2.moveToNext());
                    c2.close();
                }

                Cursor rawcontacts = getContentResolver()
                        .query(ContactsContract.RawContacts.CONTENT_URI,
                                new String[]{ContactsContract.RawContacts._ID},
                                ContactsContract.RawContacts.CONTACT_ID + "=?",
                                new String[]{mIDs.get(i)}, null);

                if (rawcontacts != null && rawcontacts.moveToFirst()) {
                    do {
                        RIds.add(rawcontacts.getString(rawcontacts
                                .getColumnIndexOrThrow(ContactsContract.RawContacts._ID)));
                    } while (rawcontacts.moveToNext());
                    rawcontacts.close();
                }

                i++;
            } while (c.moveToNext());
            c.close();
        }
    }
}

AuthenticatorActivity

public class AuthenticatorActivity extends AccountAuthenticatorActivity {

    private AccountManager mAccountManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_authenticator);


        Intent res = new Intent();
        res.putExtra(AccountManager.KEY_ACCOUNT_NAME, Constants.ACCOUNT_NAME);
        res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
        res.putExtra(AccountManager.KEY_AUTHTOKEN, Constants.ACCOUNT_TOKEN);
        Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE);
        mAccountManager = AccountManager.get(this);
        mAccountManager.addAccountExplicitly(account, null, null);
//      mAccountManager.setAuthToken(account, Constants.AUTHTOKEN_TYPE_FULL_ACCESS, Constants.ACCOUNT_TOKEN);
        ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
        setAccountAuthenticatorResult(res.getExtras());
        setResult(RESULT_OK, res);
        finish();
    }
}

SyncAdapter

public class SyncAdapter extends AbstractThreadedSyncAdapter {

    private Context mContext;

    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        mContext = context;
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority,
                              ContentProviderClient provider, SyncResult syncResult) {
    }
}

SyncService

public class SyncService extends Service {

    private static final Object sSyncAdapterLock = new Object();
    private static SyncAdapter mSyncAdapter = null;

    @Override
    public void onCreate() {
        synchronized (sSyncAdapterLock){
            if(mSyncAdapter == null){
                mSyncAdapter = new SyncAdapter(getApplicationContext(),true);
            }
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
}

AuthenticationService

public class AuthenticationService extends Service {

    private static final String TAG = "AuthenticationService";
    private Authenticator mAuthenticator;

    @Override
    public void onCreate() {
        if (android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE)) {
            android.util.Log.v(TAG, "SyncAdapter Authentication Service started.");
        }
        mAuthenticator = new Authenticator(this);
    }

    @Override
    public void onDestroy() {
        if (android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE)) {
            Log.v(TAG, "SyncAdapter Authentication Service stopped.");
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "getBinder()...  returning the AccountAuthenticator binder for intent "
                    + intent);
        }
        return mAuthenticator.getIBinder();
    }
}

Authenticator

public class Authenticator extends AbstractAccountAuthenticator {

    private final Context mContext;

    public Authenticator(Context context) {
        super(context);
        mContext = context;
    }

    @Override
    public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
        return null;
    }

    @Override
    public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
        final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);

        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
    }

    @Override
    public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
        return result;
    }

    @Override
    public String getAuthTokenLabel(String authTokenType) {
        return null;
    }

    @Override
    public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
        return null;
    }
}

Constants

public class Constants {

    public static final String ACCOUNT_TYPE = "com.example.ajay.contacts_4";
    public static final String ACCOUNT_NAME = "Nilesh_Rathod";
    public static final String ACCOUNT_TOKEN = "733N";
}

ContactsManager

public class ContactsManager {

    private static String MIMETYPE = "vnd.android.cursor.item/com.example.ajay.contacts_4";


    public static void addContact(Context context, MyContact contact) {
        ContentResolver resolver = context.getContentResolver();
        boolean mHasAccount = isAlreadyRegistered(resolver, contact.Id);

        if (mHasAccount) {
            Log.I("Account is Exist");
        } else {

            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

            // insert account name and account type
            ops.add(ContentProviderOperation
                    .newInsert(addCallerIsSyncAdapterParameter(ContactsContract.RawContacts.CONTENT_URI, true))
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, Constants.ACCOUNT_NAME)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE)
                    .withValue(ContactsContract.RawContacts.AGGREGATION_MODE,
                            ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT)
                    .build());

            // insert contact number
            ops.add(ContentProviderOperation
                    .newInsert(addCallerIsSyncAdapterParameter(ContactsContract.Data.CONTENT_URI, true))
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                    .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                    .withValue(CommonDataKinds.Phone.NUMBER, contact.number)
                    .build());


            // insert mime-type data
            ops.add(ContentProviderOperation
                    .newInsert(addCallerIsSyncAdapterParameter(ContactsContract.Data.CONTENT_URI, true))
                    .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                    .withValue(ContactsContract.Data.MIMETYPE, MIMETYPE)
                    .withValue(ContactsContract.Data.DATA1, 12345)
                    .withValue(ContactsContract.Data.DATA2, "Nilesh")
                    .withValue(ContactsContract.Data.DATA3, "ContactsDemo")
                    .build());


        }
    }

    /**
     * Check if contact is already registered with app
     */
    public static boolean isAlreadyRegistered(ContentResolver resolver, String id) {

        boolean isRegistered = false;
        List<String> str = new ArrayList<>();

        //query raw contact id's from the contact id
        Cursor c = resolver.query(RawContacts.CONTENT_URI, new String[]{RawContacts._ID},
                RawContacts.CONTACT_ID + "=?",
                new String[]{id}, null);

        //fetch all raw contact id's and save them in a list of string
        if (c != null && c.moveToFirst()) {
            do {
                str.add(c.getString(c.getColumnIndexOrThrow(RawContacts._ID)));
            } while (c.moveToNext());
            c.close();
        }

        //query account types and check the account type for each raw contact id
        for (int i = 0; i < str.size(); i++) {
            Cursor c1 = resolver.query(RawContacts.CONTENT_URI, new String[]{RawContacts.ACCOUNT_TYPE},
                    RawContacts._ID + "=?",
                    new String[]{str.get(i)}, null);

            if (c1 != null) {
                c1.moveToFirst();
                String accType = c1.getString(c1.getColumnIndexOrThrow(RawContacts.ACCOUNT_TYPE));
                if (accType != null && accType.equals("com.example.ajay.contacts_4")) {
                    isRegistered = true;
                    break;
                }
                c1.close();
            }
        }

        return isRegistered;
    }

    /**
     * Check for sync call
     */
    private static Uri addCallerIsSyncAdapterParameter(Uri uri, boolean isSyncOperation) {
        if (isSyncOperation) {
            return uri.buildUpon()
                    .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
                    .build();
        }
        return uri;
    }


}

authenticator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.example.ajay.contacts_4"
    android:icon="@drawable/icon"
    android:smallIcon="@drawable/icon"
    android:label="@string/app_name" />

contacts.xml

<?xml version="1.0" encoding="utf-8"?>
<ContactsSource
    xmlns:android="http://schemas.android.com/apk/res/android">
    <ContactsDataKind
        android:mimeType="vnd.android.cursor.item/com.example.ajay.contacts_4"
        android:icon="@drawable/icon"
        android:summaryColumn="data2"
        android:detailColumn="data3" />
</ContactsSource>

syncadapter.xml

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:contentAuthority="com.android.contacts"
    android:accountType="com.example.ajay.contacts_4"
    android:supportsUploading="false"
    android:userVisible="true" />

manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="neel.com.contactssyncingapp">

    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <service android:name=".utils.AuthenticationService" >
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>

            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator" />
        </service>

        <service android:name=".sync.SyncService" >
            <intent-filter>
                <action android:name="android.content.SyncAdapter" />
            </intent-filter>

            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/syncadapter" />
            <meta-data
                android:name="android.provider.CONTACTS_STRUCTURE"
                android:resource="@xml/contacts" />
        </service>

        <activity android:name=".activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>
        </activity>


        <activity
            android:name=".activity.ContactActivity"
            android:label="ContactActivity"
            android:screenOrientation="portrait"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.item/com.example.ajay.contacts_4" />
            </intent-filter>
        </activity>


        <activity android:name=".activity.AuthenticatorActivity" />
    </application>

</manifest>

输出

更新

public class ContactActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact);


        Uri intentData = getIntent().getData();
        if (!Uri.EMPTY.equals(intentData))
        {
            Cursor cursor = getContentResolver().query(intentData, null, null, null, null);
            if (cursor.moveToNext())
            {
                String username = cursor.getString(cursor.getColumnIndex("data2"));
                String number = cursor.getString(cursor.getColumnIndex("data3"));

                Log.e("USER_NAME",username);
                Log.e("USER_NUMBER",number);
            }
        }
    }
}