17、联系人通知:监听联系人变化(ContentObserver)、通知栏提醒生日/纪念日、NotificationChannel适配
说实话,做联系人系统开发,最头疼的不是怎么存数据,而是怎么让App感知到「数据变了」。你想想看,用户在系统通讯录里新增了一个好友,或者改了生日,你的App如果还傻乎乎地显示旧数据,那体验就太差了。
我个人习惯,只要涉及联系人数据的实时性,一定会用 ContentObserver 来监听变化。这玩意儿是Android系统提供的一个观察者模式接口,专门用来监听ContentProvider的数据变更。说白了,就是给数据源装个「门铃」,一有动静就通知你。
17.1 用ContentObserver监听联系人变化
我记得第一次做联系人同步功能时,踩了个大坑。当时我直接用定时器每隔5秒去查一次ContactsContract,结果被用户骂「耗电怪兽」。后来才意识到,用ContentObserver才是正统做法——系统会在数据真正变化时才回调,省电又高效。
实现起来其实不复杂。你需要注册一个观察者,指定要监听的URI。系统联系人数据库的URI是 ContactsContract.Contacts.CONTENT_URI。一旦联系人表有增删改,你的观察者就会收到通知。
// 注册ContentObserver的示例
getContentResolver().registerContentObserver(
ContactsContract.Contacts.CONTENT_URI,
true, // 是否监听子路径
new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
// 这里就是联系人数据变化的回调
// 我一般在这里重新查询联系人列表,刷新UI
Log.d("ContactObserver", "联系人数据变了,URI: " + uri.toString());
loadContacts(); // 你的刷新方法
}
}
);
这里有个细节要注意:selfChange 参数表示是不是你自己改的。如果你自己的App修改了联系人,这个值会是true。我曾经在项目里没判断这个,结果自己改完数据又触发了一次刷新,形成了死循环。嗯,后来加了个判断就解决了。
ContactsContract.Contacts.CONTENT_URI.buildUpon().appendPath(id).build()。这样回调次数更少,性能更好。
17.2 通知栏提醒生日/纪念日
联系人系统里,生日和纪念日提醒是个很贴心的功能。我做过一个版本,用户反馈说「你们App居然记得我老婆生日,比我自己还靠谱」。其实实现起来就是两步:查数据库 + 发通知。
首先,你得从联系人数据里把生日字段捞出来。Android的联系人数据库里,生日信息存在 ContactsContract.CommonDataKinds.Event 表中,类型是 TYPE_BIRTHDAY。
// 查询联系人生日的示例
String[] projection = new String[]{
ContactsContract.Data.CONTACT_ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Event.START_DATE
};
String selection = ContactsContract.Data.MIMETYPE + " = ? AND " +
ContactsContract.CommonDataKinds.Event.TYPE + " = ?";
String[] selectionArgs = new String[]{
ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE,
String.valueOf(ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY)
};
Cursor cursor = getContentResolver().query(
ContactsContract.Data.CONTENT_URI,
projection,
selection,
selectionArgs,
null
);
拿到生日数据后,你需要判断「今天是不是某人的生日」。这里有个坑——年份问题。很多联系人只存了月日,没有年份。我建议你只比较月日,忽略年份。否则你可能会错过很多提醒。
然后就是发通知了。Android 8.0(API 26)之后,所有通知都必须走NotificationChannel。如果你不创建Channel,通知根本不会显示。我刚开始适配时也懵过,后来发现其实很简单。
17.3 NotificationChannel适配详解
NotificationChannel说白了就是给通知分个类。比如生日提醒是一个Channel,纪念日提醒是另一个Channel。用户可以在系统设置里分别控制每个Channel的开关、声音、震动等。
我个人习惯在Application的onCreate里统一创建所有Channel。这样App启动时就已经准备好了,后续发通知直接引用Channel ID就行。
// 创建NotificationChannel的示例
private void createNotificationChannels() {
// 生日提醒Channel
NotificationChannel birthdayChannel = new NotificationChannel(
"channel_birthday", // Channel ID
"生日提醒", // Channel名称(用户可见)
NotificationManager.IMPORTANCE_HIGH // 重要性
);
birthdayChannel.setDescription("提醒你联系人的生日");
birthdayChannel.enableVibration(true);
birthdayChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
// 纪念日Channel
NotificationChannel anniversaryChannel = new NotificationChannel(
"channel_anniversary",
"纪念日提醒",
NotificationManager.IMPORTANCE_DEFAULT
);
anniversaryChannel.setDescription("提醒你联系人的纪念日");
anniversaryChannel.setSound(null, null); // 纪念日可以静音
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(birthdayChannel);
manager.createNotificationChannel(anniversaryChannel);
}
发通知的时候,只需要在Builder里指定Channel ID即可:
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel_birthday")
.setSmallIcon(R.drawable.ic_birthday)
.setContentTitle("生日提醒")
.setContentText("今天是 " + contactName + " 的生日!")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
NotificationManagerCompat.from(this).notify(contactId, builder.build());
这里有个小细节:setPriority 在Android 8.0+上其实已经被Channel的importance替代了。但为了兼容低版本,我还是会保留这个设置。说白了,就是「向下兼容,向上适配」。
17.4 整体流程梳理
我把整个联系人通知系统的流程画了张图,方便你理解各个模块之间的关系:
从图上你可以看到,整个流程是闭环的。用户一操作,系统数据库就变了,ContentObserver立刻感知到,然后触发查询逻辑,匹配到生日或纪念日就发通知。一气呵成。
17.5 避坑指南
最后分享几个我踩过的坑,希望能帮你省点时间:
- ContentObserver记得反注册:在Activity或Service销毁时,一定要调用
getContentResolver().unregisterContentObserver(observer)。我曾经忘了反注册,导致内存泄漏,被用户投诉App越来越卡。 - 通知不要重复发:如果用户一天内多次打开联系人页面,可能会触发多次查询。我建议用SharedPreferences记录一下「今天是否已经发过某人的生日提醒」,避免重复打扰用户。
- Channel一旦创建就不能改:NotificationChannel的配置(比如重要性、声音)只能在创建时设置。如果你想修改,只能删除Channel再重建,或者让用户去系统设置里手动改。所以创建前想清楚。
- 低版本兼容:如果你的App还要支持Android 7.0及以下,记得用
NotificationCompat和NotificationManagerCompat。这些兼容库会自动处理版本差异。
好了,关于联系人通知这块,核心就是这些。ContentObserver监听变化,NotificationChannel适配新版本,再加上生日/纪念日的匹配逻辑,一套完整的通知系统就搭起来了。代码量不大,但细节不少,多测试几遍就稳了。
公众号:蓝海资料掘金营,微信deep3321