这是几乎与您的另一个问题相同的问题,答案相同:When deleting a contact on android,other random contacts id's being changed
您对无法确定的联系人ID进行了一些假设-没有人保证联系人ID是递增的,也没有人保证联系人ID稳定,实际上它们绝对不是。
您可以在应用运行时使用查询的联系人ID,极少的机会在几分钟之内即可更改它们,但有时也有可能偶尔更改现有用户的ID。
不仅如此,相同的ID还可以指向今天的某些联系人,而可以指向明天的完全不同的联系人。
如果您将本地联系人的某些副本保留在云中,则应使用以下组合ID来引用联系人:
Contacts.CONTACT_ID,Contacts.LOOKUP_KEY,Contacts.DISPLAY_NAME
在此处查看我的答案以获取更多详细信息:How to uniquely identify a contact on ContactsContract.Contacts table
这不是一个完美的解决方案,但这是我们拥有的最好的解决方案
,
我已经对该解决方案进行了几天的测试,似乎还可以,但是我认为我需要对其进行更多的测试。如果您使用这种方法,请进行自己的测试,最重要的是,如果我有任何遗漏,请让我知道,不要着急降级。谢谢!
- 我构建了一个App类,该类扩展了Application并实现了
ActivityLifecycleCallbacks。在其中我为创建一个ContactSync类
第一次,并在应用程序每次进入前途时激活它
- 在ContactSync类中,我正在使用Kotlin
withContext(Dispatchers.IO)
暂停任何代码以简化流程
- 我使用.get()从Firestore获取与当前用户相关的所有联系人
- 在.get()addOnSuccessListener上,我将所有联系人添加到HashMap中,并将规范化的电话号码作为键,并将名称+ firestore ID作为值(使用内部类)
- 在制作HashMap时,我还要确保Firestore上没有重复的smae电话号码,如果有,请删除它们(使用批处理)
然后,我从Android手机检索所有联系人。我先按NORMALIZED_NUMBER和DISPLAY_NAME对其进行排序(稍后说明)
- 我现在正在创建一个带有索引和计数的
batchArray
,以避免超过500个限制
- 我开始通过联系人光标进行扫描,
- 我首先获取归一化的号码,如果不可用(空),则使用我创建的函数自行创建归一化的号码(可能是因为不正确格式的电话号码(不确定)仅返回空值)
- 然后我将归一化的数字与先前的光标值进行比较。如果相同,我会忽略它以避免在Firestore中重复(请记住,光标按NORMALIZED_NUMBER排序)
- 然后我检查HashMap中是否已存在标准化的数字。
- 如果在HashMap中:我将HashMap中的名称与游标名称进行比较。如果不同,则我推断名称已更改,并更新批处理数组中的Firestore联系人(请记住要增加计数器,如果超过500,则增加索引)。然后,我从HashMap中删除规范化的数字,以避免以后删除它。
- 如果不在HashMap中:我认为该联系人是新联系人,并通过批处理将其添加到firestore
- 我遍历所有游标直到完成。
- 完成光标后,我将其关闭
- 在HashMap中找到的所有其余记录都是在Firestore上找不到的记录,因此被删除了。我使用批处理进行迭代并删除
- 同步在电话端完成
现在,由于进行实际同步需要所有用户的访问权,因此我在节点中使用了Firebase功能。我创建了2个函数:
- 在创建新用户(通过电话签名)时触发的功能
- 在创建新的联系人文档时触发的功能。
这两个函数会将用户与文档中的规范化数字进行比较,如果匹配,则将该用户的uid写入Firestore文档的“ friend_uid”字段中。
请注意,如果您尝试在免费的Firebase计划中使用这些功能,则可能会出现错误。我建议更改为Blaze计划,并将收费限制在几美元。通过更改为Blaze,Google还可以为您提供免费的附加功能,并避免实际付款
到此,同步完成。同步仅需几秒钟
要显示该应用程序用户的所有联系人,请使用“ friend_uid”查询所有不为空的用户联系人。
一些注意事项:
- .get()将在每次进行同步时检索所有联系人。如果用户有数百个联系人,那么可能会有很多阅读。为了最小化,我在启动应用程序时使用
.get(Source.DEFAULT)
,在其他时间使用.get(Source.CACHE)
。由于这些文档的名称和编号仅由用户修改,因此我相信大多数时候(仍在测试中)将不会有问题
- 为了最大限度地减少同步过程,我仅在任何联系人更改其时间戳时才启动它。我将最后一个时间戳保存到SharedPreferences并进行比较。我发现当应用重新快速打开时,它主要可以保存同步。
- 我还会保存上次登录的用户。如果用户有任何更改,我会重新初始化当前用户联系人
一些源代码(仍在测试中,如果有任何错误,请告知我)
private fun getContacts(): Cursor? {
val projection = arrayOf(
ContactsContract.CommonDataKinds.Phone._ID,ContactsContract.CommonDataKinds.Phone.NUMBER,ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER,ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,ContactsContract.CommonDataKinds.Phone.CONTACT_LAST_UPDATED_TIMESTAMP)
//sort by NORMALIZED_NUMBER to detect duplicates and then by name to keep order and avoiding name change
val sortOrder = ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER + " ASC," +
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
return mContentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,projection,null,sortOrder)
}
private suspend fun syncContactsAsync() = withContext(Dispatchers.IO) {
if (isAnythingChanged() || mFirstRun) {
if (getValues() == Result.SUCCESS) {
myPrintln("values retrieved success")
} else {
myPrintln("values retrieved failed. Aborting.")
return@withContext
}
val cursor: Cursor? = getContacts()
if (cursor == null) {
myPrintln("cursor cannot be null")
mFireContactHashMap.clear()
return@withContext
}
if (cursor.count == 0) {
cursor.close()
mFireContactHashMap.clear()
myPrintln("cursor empty")
return@withContext
}
var contactName: String?
var internalContact: InternalContact?
val batchArray = mutableListOf(FirebaseFirestore.getInstance().batch())
var batchIndex = 0
var batchCount = 0
var normalizedNumber:String?
var prevNumber = ""
var firestoreId: String
while (cursor.moveToNext()) {
normalizedNumber = cursor.getString(COLUMN_UPDATED_NORMALIZED_NUMBER)
if (normalizedNumber == null) {
normalizedNumber = cursor.getString(COLUMN_UPDATED_PHONE_NUMBER)
normalizedNumber = Phone.getParsedPhoneNumber(mDeviceCountryIso,normalizedNumber,mContext)
}
//cursor sorted by normalized numbers so if same as previous,do not check
if (normalizedNumber != prevNumber) {
prevNumber = normalizedNumber
contactName = cursor.getString(COLUMN_UPDATED_DISPLAY_NAME)
internalContact = mFireContactHashMap[normalizedNumber]
//if phone number exists on firestore
if (internalContact != null) {
//if name changed,update in firestore
if (internalContact.name != contactName) {
myPrintln("updating $normalizedNumber from name: ${internalContact.name} to: $contactName")
batchArray[batchIndex].update(
mFireContactRef.document(internalContact.id),FireContact.COLUMN_NAME,contactName)
batchCount++
}
//remove to avoid deletions
mFireContactHashMap.remove(normalizedNumber)
} else {
//New item. Insert
if (normalizedNumber != mUserPhoneNumber) {
myPrintln("adding $normalizedNumber / $contactName")
firestoreId = mFireContactRef.document().id
batchArray[batchIndex].set(mFireContactRef.document(firestoreId),FireContact(firestoreId,-1,contactName,cursor.getString(COLUMN_UPDATED_PHONE_NUMBER),normalizedNumber))
batchCount++
}
}
if (BATCH_HALF_MAX < batchCount ) {
batchArray += FirebaseFirestore.getInstance().batch()
batchCount = 0
batchIndex++
}
}
}
cursor.close()
//Remaining contacts not found on cursor so assumed deleted. Delete from firestore
mFireContactHashMap.forEach { (key,value) ->
myPrintln("deleting ${value.name} / $key")
batchArray[batchIndex].delete(mFireContactRef.document(value.id))
batchCount++
if (BATCH_HALF_MAX < batchCount ) {
batchArray += FirebaseFirestore.getInstance().batch()
batchCount = 0
batchIndex++
}
}
//execute all batches
if ((batchCount > 0) || (batchIndex > 0)) {
myPrintln("committing changes...")
batchArray.forEach { batch ->
batch.commit()
}
} else {
myPrintln("no records to commit")
}
myPrintln("end sync")
mFireContactHashMap.clear()
mPreferenceManager.edit().putLong(PREF_LAST_TIMESTAMP,mLastContactUpdated).apply()
mFirstRun = false
} else {
myPrintln("no change in contacts")
}
}
private suspend fun putAllUserContactsToHashMap() : Result {
var result = Result.FAILED
val batchArray = mutableListOf(FirebaseFirestore.getInstance().batch())
var batchIndex = 0
var batchCount = 0
mFireContactHashMap.clear()
var source = Source.CACHE
if (mFirstRun) {
source = Source.DEFAULT
myPrintln("get contacts via Source.DEFAULT")
} else {
myPrintln("get contacts via Source.CACHE")
}
mFireContactRef.whereEqualTo( FireContact.COLUMN_USER_ID,mUid ).get(source)
.addOnSuccessListener {documents ->
var fireContact : FireContact
for (doc in documents) {
fireContact = doc.toObject(FireContact::class.java)
if (!mFireContactHashMap.containsKey(fireContact.paPho)) {
mFireContactHashMap[fireContact.paPho] = InternalContact(fireContact.na,doc.id)
} else {
myPrintln("duplicate will be removed from firestore: ${fireContact.paPho} / ${fireContact.na} / ${doc.id}")
batchArray[batchIndex].delete(mFireContactRef.document(doc.id))
batchCount++
if (BATCH_HALF_MAX < batchCount) {
batchArray += FirebaseFirestore.getInstance().batch()
batchCount = 0
batchIndex++
}
}
}
result = Result.SUCCESS
}.addOnFailureListener { exception ->
myPrintln("Error getting documents: $exception")
}.await()
//execute all batches
if ((batchCount > 0) || (batchIndex > 0)) {
myPrintln("committing duplicate delete... ")
batchArray.forEach { batch ->
batch.commit()
}
} else {
myPrintln("no duplicates to delete")
}
return result
}
本文链接:https://www.f2er.com/2714350.html