我必须显示添加的新联系人或编辑的联系人。我能够获取新添加的联系人,但无法获取上次编辑的联系人。我尝试根据 CONTACT_LAST_UPDATED_TIMESTAMP 检索已编辑的联系人,但如果我们正在拨打任何电话,则被叫联系人的 CONTACT_LAST_UPDATED_TIMESTAMP 会在 ContactsProvider 中进行修改,因此它会返回我上次呼叫的联系人作为上次编辑的联系人。我已经写了如下查询:
Cursor cursor = context.getContentResolver().query(uri, null,
null,
null,
ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " DESC LIMIT 1");
您应该使用 ContactsContract.Contacts._ID 来代替 ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
Cursor cursor = context.getContentResolver().query(uri, null,
null,
null,
ContactsContract.Contacts._ID + " DESC LIMIT 1");
您可以使用
registerContentObserver(ContactsContract.Contacts.CONTENT_URI...)
监控特定联系人的变化。
然后检查 CONTACT_LAST_UPDATED_TIMESTAMP 的值,并与之前的值进行比较。
当不同时,就该比较新字段和旧字段(如果没有旧字段则初始化。
我准备了一个可以帮助监控的ViewModel。你可以观察它的
contactStateLiveData
,当它发生变化时,就该查询联系人的字段了。您可以在contactStateLiveData
内的viewModel本身中获取更多为您准备的字段,或者您可以在每次更改时以不同的方式进行查询。
我还让它每 500 毫秒更改一次,因为它有时会被多次调用。
class ABContactDetailsFragmentViewModel(application: Application) : BaseViewModel(application) {
private val updateContactDebounceJob = DebounceJob()
private var contactKeyToObserver: Pair<Uri, ContentObserver>? = null
val contactStateLiveData = MutableLiveData<ContactState>(ContactState.Unknown)
sealed class ContactState {
data object Unknown : ContactState()
class Exists(val lastUpdated: Long) : ContactState()
data object Removed : ContactState()
}
/**monitor for changes of the specific contactKey of the contact.
* Note that you should also call [checkUpdatesOfContactIfNeeded] after that, preferably on onResume as it might get the callback a bit late*/
@RequiresPermission(permission.READ_CONTACTS)
@UiThread
fun monitorContact(contactKey: String) {
val contentResolver = applicationContext.contentResolver
val lookupUri: Uri = ContactsContract.Contacts.getLookupUri(contentResolver, Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, contactKey))
monitorContact(lookupUri)
}
/**monitor for changes of the specific uri of the contact.
* Note that you should also call [checkUpdatesOfContactIfNeeded] after that, preferably on onResume as it might get the callback a bit late*/
@RequiresPermission(permission.READ_CONTACTS)
@UiThread
fun monitorContact(lookupUri: Uri) {
val contentResolver = applicationContext.contentResolver
contactKeyToObserver?.let { contactKeyToObserver ->
if (contactKeyToObserver.first == lookupUri) return
applicationContext.contentResolver.unregisterContentObserver(contactKeyToObserver.second)
[email protected] = null
contactStateLiveData.value = ContactState.Unknown
}
val observer = object : ContentObserver(Executors.uiHandler) {
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
checkUpdatesOfContactIfNeeded()
}
}
contentResolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, false, observer)
onClearedListeners.add {
contentResolver.unregisterContentObserver(observer)
}
this.contactKeyToObserver = Pair(lookupUri, observer)
}
@UiThread
fun checkUpdatesOfContactIfNeeded() {
// Log.d("AppLog", "ABContactDetailsFragmentViewModel checkUpdatesOfContactIfNeeded")
val lookupUri = contactKeyToObserver?.first ?: return
updateContactDebounceJob.debounce(scope = viewModelScope, runnable = {
viewModelScope.launch {
runInterruptible(Dispatchers.IO) {
// Log.d("AppLog", "ABContactDetailsFragmentViewModel scan for changes of monitored contact in background thread")
checkIfContactUpdated(lookupUri)
}
}
})
}
@WorkerThread
private fun checkIfContactUpdated(lookupUri: Uri) {
// Log.d("AppLog", "ABContactDetailsFragmentViewModel checkIfContactUpdated lookupUri:$lookupUri")
val state = runOnUiThreadWithResult {
contactStateLiveData.value
}
val lastUpdatedTimeStampMs: Long? = if (state is ContactState.Exists) {
state.lastUpdated
} else null
val projection: Array<out String> = arrayOf(
// ContactsContract.Contacts._ID,
// ContactsContract.Contacts.LOOKUP_KEY,
// ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
)
val cursor = applicationContext.contentResolver.query(lookupUri, projection, null, null, null)
if (cursor == null || cursor.count == 0) {
// Log.d("AppLog", "ABContactDetailsFragmentViewModel contact not found")
cursor?.closeQuietly()
contactStateLiveData.postValue(ContactState.Removed)
return
}
cursor.use {
cursor.moveToNext()
val timestampIdx = cursor.getColumnIndex(ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)
val timestampMs = cursor.getLong(timestampIdx)
val detectedChange = lastUpdatedTimeStampMs != timestampMs
// Log.d("AppLog", "ABContactDetailsFragmentViewModel detected change ? $detectedChange $lastUpdatedTimeStampMs->$timestampMs ${DatabaseUtils.dumpCurrentRowToString(cursor)} ")
if (detectedChange) {
contactStateLiveData.postValue(ContactState.Exists(timestampMs))
}
}
}
}
//https://stackoverflow.com/questions/50858684/kotlin-android-debounce
class DebounceJob(private val defaultDebounceDelayMs:Long=500L) {
private var job: Job? = null
@UiThread
fun debounce(delayMs: Long = defaultDebounceDelayMs, scope: CoroutineScope, runnable: Runnable) {
job?.cancel()
job = scope.launch {
delay(delayMs)
runnable.run()
}
}
}