使用MAUI编辑保存联系人详情的Android代码问题求助
我用VS2022开发MAUI项目,目标平台是iOS和Android,目前优先调试Android端。MAUI可以通过var contact = await Microsoft.Maui.ApplicationModel.Communication.Contacts.Default.PickContactAsync();轻松选择联系人,但原MAUI Essentials的联系人保存方法已移除,需自行编写Android原生代码。
参考旧帖改写代码后,仅能修改名字这类单一字段,编辑或添加邮箱时,内容会错误写入名字字段;最新版本的代码甚至完全无法保存内容。
我查阅Android开发者文档后仍未理清,希望有人指出代码问题:
- 我对
ContentProviderOperation.Builder的WithSelection参数逻辑理解是否正确? - 如何同时修改姓名这类静态字段并添加新邮箱?
- 如何关联特定联系人ID下的指定邮箱地址?
初始问题代码
public class SaveContact { public void SaveContactToPhoneBook(string id,string fname, string sname, string Email) { #if ANDROID ContentProviderOperation.Builder builder =ContentProviderOperation.NewUpdate(ContactsContract.Data.ContentUri); List<ContentProviderOperation> ops = new List<ContentProviderOperation>(); builder.WithValue(ContactsContract.CommonDataKinds.StructuredName.FamilyName,sname); ops.Add(builder.Build()); // 尝试保存新邮箱 String emailSelection = ContactsContract.Data.InterfaceConsts.Id + " = ? " +ContactsContract.Data.InterfaceConsts.Mimetype + " = ? "; String[] emailSelectionArgs = {id,ContactsContract.CommonDataKinds.Email.ContentItemType}; // 我不确定上面的参数怎么工作——它们显然是传给builder.WithSelection()的,我猜Args会替换掉'?' // 用来构造sqlite语句,但具体是怎么在底层构造的? builder = ContentProviderOperation.NewUpdate(ContactsContract.Data.ContentUri); builder.WithSelection(emailSelection, emailSelectionArgs); builder.WithValue(ContactsContract.CommonDataKinds.Email.Address, Email); ops.Add(builder.Build()); // 更新联系人 ContentProviderResult[] res; try { res = Android.App.Application.Context.ContentResolver.ApplyBatch(ContactsContract.Authority, ops); ops.Clear();//添加这行 } catch (Exception e) { System.Diagnostics.Debug.WriteLine( e.Message); } #elif IOS #endif } }
最新版本代码(无法保存内容)
List<ContentProviderOperation> ops = new List<ContentProviderOperation>(); // 姓名 String nameSelection = ContactsContract.Data.InterfaceConsts.RawContactId + " = ? AND " + ContactsContract.Data.InterfaceConsts.Mimetype + " = ? "; String[] nameSelectionArgs = { id, ContactsContract.CommonDataKinds.StructuredName.ContentItemType }; ContentProviderOperation.Builder builder = ContentProviderOperation.NewUpdate(ContactsContract.Data.ContentUri); builder.WithSelection(nameSelection, nameSelectionArgs); builder.WithValue(ContactsContract.CommonDataKinds.StructuredName.GivenName, fname); builder.WithValue(ContactsContract.CommonDataKinds.StructuredName.FamilyName, sname); ops.Add(builder.Build()); String emailSelection = ContactsContract.Data.InterfaceConsts.RawContactId + " = ? "; String[] emailSelectionArgs = {id}; builder = ContentProviderOperation.NewUpdate(ContactsContract.Data.ContentUri); builder.WithSelection(emailSelection, emailSelectionArgs); builder.WithValue(ContactsContract.CommonDataKinds.Email.Address, Email); ops.Add(builder.Build()); try { var result = Android.App.Application.Context.ContentResolver.ApplyBatch(ContactsContract.Authority, ops); ops.Clear(); } catch(Exception e) {var ee = e.Message;}
问题分析与修正方案
1. WithSelection参数逻辑理解误区
WithSelection的作用是筛选要更新的Data表行,参数格式和SQL的WHERE子句一致:第一个参数是筛选条件(用?作为占位符),第二个参数是对应占位符的实际值。Android Contacts系统中,Data表存储了联系人的所有细分数据(姓名、邮箱、电话等),每条行对应一个数据项,通过RawContactId关联到具体联系人,Mimetype区分数据类型(比如姓名是vnd.android.cursor.item/name,邮箱是vnd.android.cursor.item/email_v2)。
你的代码核心问题:
- 初始版本中姓名更新操作未加筛选条件,会把所有联系人的姓氏改成
sname,完全错误;邮箱筛选用了Data.Id而非RawContactId,若传入的id是联系人的RawContactId,会导致筛选不到任何行。 - 最新版本中邮箱更新仅用
RawContactId筛选,未限定Mimetype,会把该联系人所有Data行(包括姓名、电话)的Email.Address字段覆盖,既无效还会破坏其他数据。
2. 同时修改姓名和添加/更新邮箱的正确做法
需分两种情况处理邮箱:更新已有邮箱和添加新邮箱。添加新邮箱要用NewInsert而非NewUpdate;更新已有邮箱则需精准筛选到对应邮箱行。
以下是修正后的完整代码:
#if ANDROID using Android.Content; using Android.Provider; using System.Collections.Generic; public class SaveContact { public void SaveContactToPhoneBook(string rawContactId, string givenName, string familyName, string emailAddress) { var ops = new List<ContentProviderOperation>(); // 1. 更新联系人姓名 var nameSelection = $"{ContactsContract.Data.InterfaceConsts.RawContactId} = ? AND {ContactsContract.Data.InterfaceConsts.Mimetype} = ?"; var nameSelectionArgs = new[] { rawContactId, ContactsContract.CommonDataKinds.StructuredName.ContentItemType }; var nameBuilder = ContentProviderOperation.NewUpdate(ContactsContract.Data.ContentUri) .WithSelection(nameSelection, nameSelectionArgs) .WithValue(ContactsContract.CommonDataKinds.StructuredName.GivenName, givenName) .WithValue(ContactsContract.CommonDataKinds.StructuredName.FamilyName, familyName); ops.Add(nameBuilder.Build()); // 2. 检查该联系人是否已有邮箱,有则更新,无则添加 var cursor = Android.App.Application.Context.ContentResolver.Query( ContactsContract.Data.ContentUri, new[] { ContactsContract.Data.InterfaceConsts.Id }, $"{ContactsContract.Data.InterfaceConsts.RawContactId} = ? AND {ContactsContract.Data.InterfaceConsts.Mimetype} = ?", new[] { rawContactId, ContactsContract.CommonDataKinds.Email.ContentItemType }, null); if (cursor != null && cursor.MoveToFirst()) { // 已有邮箱,更新第一条(若要更新指定邮箱,需额外筛选邮箱地址) var dataId = cursor.GetString(cursor.GetColumnIndex(ContactsContract.Data.InterfaceConsts.Id)); var emailUpdateSelection = $"{ContactsContract.Data.InterfaceConsts.Id} = ?"; var emailUpdateArgs = new[] { dataId }; var emailUpdateBuilder = ContentProviderOperation.NewUpdate(ContactsContract.Data.ContentUri) .WithSelection(emailUpdateSelection, emailUpdateArgs) .WithValue(ContactsContract.CommonDataKinds.Email.Address, emailAddress); ops.Add(emailUpdateBuilder.Build()); cursor.Close(); } else { // 无邮箱,插入新邮箱 var emailInsertBuilder = ContentProviderOperation.NewInsert(ContactsContract.Data.ContentUri) .WithValue(ContactsContract.Data.InterfaceConsts.RawContactId, rawContactId) .WithValue(ContactsContract.Data.InterfaceConsts.Mimetype, ContactsContract.CommonDataKinds.Email.ContentItemType) .WithValue(ContactsContract.CommonDataKinds.Email.Address, emailAddress) .WithValue(ContactsContract.CommonDataKinds.Email.Type, ContactsContract.CommonDataKinds.Email.TypeHome); // 指定邮箱类型,可选 ops.Add(emailInsertBuilder.Build()); } // 执行批量操作 try { Android.App.Application.Context.ContentResolver.ApplyBatch(ContactsContract.Authority, ops); } catch (Exception e) { System.Diagnostics.Debug.WriteLine($"保存联系人失败:{e.Message}"); } } } #endif
3. 关联特定联系人ID下的指定邮箱地址
若要更新联系人的指定邮箱(而非第一条),需在查询和筛选时添加邮箱地址条件:
// 查询指定邮箱的Data行ID var cursor = Android.App.Application.Context.ContentResolver.Query( ContactsContract.Data.ContentUri, new[] { ContactsContract.Data.InterfaceConsts.Id }, $"{ContactsContract.Data.InterfaceConsts.RawContactId} = ? AND {ContactsContract.Data.InterfaceConsts.Mimetype} = ? AND {ContactsContract.CommonDataKinds.Email.Address} = ?", new[] { rawContactId, ContactsContract.CommonDataKinds.Email.ContentItemType, "旧邮箱地址" }, null);
拿到对应的Data.Id后,用它作为筛选条件进行更新即可。
内容的提问来源于stack exchange,提问作者john murray

