阅读导航:

  一、功能说明

  二、代码实现

  三、源码获取

  四、参考资料

  五、后面计划

  一、功能说明

  完整思维导图: 郑州市妇科医院:http://www.zztjfk.com/郑州妇科医院哪家好:http://www.zztjfk.com/郑州妇科医院做检查多少钱:http://www.zztjfk.com/

  

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端_第1张图片


  本文介绍图中右侧画红圈处的功能,即使用Xamarin.Forms获取和展示Android和iOS的通讯录信息,下面是最终效果,由于使用的是真实手机,所以联系人姓名及电话号码打码显示。

  

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端_第2张图片


  并简单的进行了搜索功能处理,之所以说简单,是因为通讯录列表是全部读取出来了,搜索是直接从此列表进行过滤的。

二、代码实现

1、共享库工程创建联系人实体类:Contacts.cs

namespace TerminalMACS.Clients.App.Models
{    /// 
    /// 通讯录
    /// 
    public class Contact
    {
        /// 
        /// 获取或者设置名称
        /// 
        public string Name { get; set; }        /// 
        /// 获取或者设置 头像
        /// 
        public string Image { get; set; }        /// 
        /// 获取或者设置 邮箱地址
        /// 
        public string[] Emails { get; set; }        /// 
        /// 获取或者设置 手机号码
        /// 
        public string[] PhoneNumbers { get; set; }
    }
}

2、共享库创建通讯录服务接口:IContactsService.cs

包括:

  • 一个通讯录获取请求接口:RetrieveContactsAsync

  • 一个读取一条通讯结果通知事件:OnContactLoaded

using System;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;using TerminalMACS.Clients.App.Models;namespace TerminalMACS.Clients.App.Services
{    /// 
    /// 通讯录事件参数
    /// 
    public class ContactEventArgs:EventArgs
    {        public Contact Contact { get; }        public ContactEventArgs(Contact contact)
        {
            Contact = contact;
        }
    }    /// 
    /// 通讯录服务接口,android和iOS终端具体的通讯录获取服务需要继承此接口
    /// 
    public interface IContactsService
    {        /// 
        /// 读取一条数据通知
        /// 
        event EventHandler OnContactLoaded;        /// 
        /// 是否正在加载
        /// 
        bool IsLoading { get; }        /// 
        /// 尝试获取所有通讯录
        /// 
        /// 
        /// 
        Task> RetrieveContactsAsync(CancellationToken? token = null);
    }
}

3、iOS工程中添加通讯录服务,实现IContactsService接口:

using Contacts;using Foundation;using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Threading;using System.Threading.Tasks;using TerminalMACS.Clients.App.Models;using TerminalMACS.Clients.App.Services;namespace TerminalMACS.Clients.App.iOS.Services
{    /// 
    /// 通讯录获取服务
    /// 
    public class ContactsService : NSObject, IContactsService
    {        const string ThumbnailPrefix = "thumb";        bool requestStop = false;        public event EventHandler OnContactLoaded;        bool _isLoading = false;        public bool IsLoading => _isLoading;        /// 
        /// 异步请求权限
        /// 
        /// 
        public async Task RequestPermissionAsync()
        {
            var status = CNContactStore.GetAuthorizationStatus(CNEntityType.Contacts);

            Tuple authotization = new Tuple(status == CNAuthorizationStatus.Authorized, null);            if (status == CNAuthorizationStatus.NotDetermined)
            {                using (var store = new CNContactStore())
                {
                    authotization = await store.RequestAccessAsync(CNEntityType.Contacts);
                }
            }            return authotization.Item1;

        }        /// 
        /// 异步请求通讯录,此方法由界面真正调用
        /// 
        /// 
        /// 
        public async Task> RetrieveContactsAsync(CancellationToken? cancelToken = null)
        {
            requestStop = false;            if (!cancelToken.HasValue)
                cancelToken = CancellationToken.None;            // 我们创建了一个十进制的TaskCompletionSource
            var taskCompletionSource = new TaskCompletionSource>();            // 在cancellationToken中注册lambda
            cancelToken.Value.Register(() =>
            {                // 我们收到一条取消消息,取消TaskCompletionSource.Task
                requestStop = true;
                taskCompletionSource.TrySetCanceled();
            });

            _isLoading = true;

            var task = LoadContactsAsync();            // 等待两个任务中的第一个任务完成
            var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
            _isLoading = false;            return await completedTask;

        }        /// 
        /// 异步加载通讯录,具体的通讯录读取方法
        /// 
        /// 
        async Task> LoadContactsAsync()
        {
            IList contacts = new List();
            var hasPermission = await RequestPermissionAsync();            if (hasPermission)
            {

                NSError error = null;
                var keysToFetch = new[] { CNContactKey.PhoneNumbers, CNContactKey.GivenName, CNContactKey.FamilyName, CNContactKey.EmailAddresses, CNContactKey.ImageDataAvailable, CNContactKey.ThumbnailImageData };

                var request = new CNContactFetchRequest(keysToFetch: keysToFetch);
                request.SortOrder = CNContactSortOrder.GivenName;                using (var store = new CNContactStore())
                {
                    var result = store.EnumerateContacts(request, out error, new CNContactStoreListContactsHandler((CNContact c, ref bool stop) =>
                    {                        string path = null;                        if (c.ImageDataAvailable)
                        {
                            path = path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");                            if (!File.Exists(path))
                            {
                                var imageData = c.ThumbnailImageData;
                                imageData?.Save(path, true);


                            }
                        }

                        var contact = new Contact()
                        {
                            Name = string.IsNullOrEmpty(c.FamilyName) ? c.GivenName : $"{c.GivenName} {c.FamilyName}",
                            Image = path,
                            PhoneNumbers = c.PhoneNumbers?.Select(p => p?.Value?.StringValue).ToArray(),
                            Emails = c.EmailAddresses?.Select(p => p?.Value?.ToString()).ToArray(),

                        };                        if (!string.IsNullOrWhiteSpace(contact.Name))
                        {
                            OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));

                            contacts.Add(contact);
                        }

                        stop = requestStop;

                    }));
                }
            }            return contacts;
        }


    }
}

4、在iOS工程中的Info.plist文件添加通讯录权限使用说明

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端_第3张图片

5、在Android工程中添加读取通讯录权限配置:AndroidManifest.xml

完整权限配置如下


	
	
	
  
  

6、在Android工程中添加通讯录服务,实现IContactServer接口:ContactsService.cs

using Acr.UserDialogs;using Android;using Android.App;using Android.Content;using Android.Content.PM;using Android.Database;using Android.Provider;using Android.Runtime;using Android.Support.V4.App;using Plugin.CurrentActivity;using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Threading;using System.Threading.Tasks;using TerminalMACS.Clients.App.Models;using TerminalMACS.Clients.App.Services;namespace TerminalMACS.Clients.App.Droid.Services
{    /// 
    /// 通讯录获取服务
    /// 
    public class ContactsService : IContactsService
    {        const string ThumbnailPrefix = "thumb";        bool stopLoad = false;        static TaskCompletionSource contactPermissionTcs;        public string TAG
        {
            get
            {                return "MainActivity";
            }
        }        bool _isLoading = false;        public bool IsLoading => _isLoading;        //权限请求状态码
        public const int RequestContacts = 1239;        /// 
        /// 获取通讯录需要的请求权限
        /// 
        static string[] PermissionsContact = {
            Manifest.Permission.ReadContacts
        };        public event EventHandler OnContactLoaded;        /// 
        /// 异步请求通讯录权限
        /// 
        async void RequestContactsPermissions()
        {            //检查是否可以弹出申请读、写通讯录权限
            if (ActivityCompat.ShouldShowRequestPermissionRationale(CrossCurrentActivity.Current.Activity, Manifest.Permission.ReadContacts)
                || ActivityCompat.ShouldShowRequestPermissionRationale(CrossCurrentActivity.Current.Activity, Manifest.Permission.WriteContacts))
            {                // 如果未授予许可,请向用户提供其他理由用户将从使用权限的附加上下文中受益。
                // 例如,如果请求先前被拒绝。
                await UserDialogs.Instance.AlertAsync("通讯录权限", "此操作需要“通讯录”权限", "确定");
            }            else
            {                // 尚未授予通讯录权限。直接请求这些权限。
                ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, PermissionsContact, RequestContacts);
            }
        }        /// 
        /// 收到用户响应请求权限操作后的结果
        /// 
        /// 
        /// 
        /// 
        public static void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {            if (requestCode == RequestContacts)
            {                // 我们请求了多个通讯录权限,因此需要检查相关的所有权限
                if (PermissionUtil.VerifyPermissions(grantResults))
                {                    // 已授予所有必需的权限,显示联系人片段。
                    contactPermissionTcs.TrySetResult(true);
                }                else
                {
                    contactPermissionTcs.TrySetResult(false);
                }

            }
        }        /// 
        /// 异步请求权限
        /// 
        /// 
        public async Task RequestPermissionAsync()
        {
            contactPermissionTcs = new TaskCompletionSource();            // 验证是否已授予所有必需的通讯录权限。
            if (Android.Support.V4.Content.ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, Manifest.Permission.ReadContacts) != (int)Permission.Granted
                || Android.Support.V4.Content.ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, Manifest.Permission.WriteContacts) != (int)Permission.Granted)
            {                // 尚未授予通讯录权限。
                RequestContactsPermissions();
            }            else
            {                // 已授予通讯录权限。
                contactPermissionTcs.TrySetResult(true);
            }            return await contactPermissionTcs.Task;
        }        /// 
        /// 异步请求通讯录,此方法由界面真正调用
        /// 
        /// 
        /// 
        public async Task> RetrieveContactsAsync(CancellationToken? cancelToken = null)
        {
            stopLoad = false;            if (!cancelToken.HasValue)
                cancelToken = CancellationToken.None;            // 我们创建了一个十进制的TaskCompletionSource
            var taskCompletionSource = new TaskCompletionSource>();            // 在cancellationToken中注册lambda
            cancelToken.Value.Register(() =>
            {                // 我们收到一条取消消息,取消TaskCompletionSource.Task
                stopLoad = true;
                taskCompletionSource.TrySetCanceled();
            });

            _isLoading = true;

            var task = LoadContactsAsync();            // 等待两个任务中的第一个任务完成
            var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
            _isLoading = false;            return await completedTask;
        }        /// 
        /// 异步加载通讯录,具体的通讯录读取方法
        /// 
        /// 
        async Task> LoadContactsAsync()
        {
            IList contacts = new List();
            var hasPermission = await RequestPermissionAsync();            if (!hasPermission)
            {                return contacts;
            }

            var uri = ContactsContract.Contacts.ContentUri;
            var ctx = Application.Context;
            await Task.Run(() =>
            {                // 暂时只请求通讯录Id、DisplayName、PhotoThumbnailUri,可以扩展
                var cursor = ctx.ApplicationContext.ContentResolver.Query(uri, new string[]
                {
                        ContactsContract.Contacts.InterfaceConsts.Id,
                        ContactsContract.Contacts.InterfaceConsts.DisplayName,
                        ContactsContract.Contacts.InterfaceConsts.PhotoThumbnailUri
                }, null, null, $"{ContactsContract.Contacts.InterfaceConsts.DisplayName} ASC");                if (cursor.Count > 0)
                {                    while (cursor.MoveToNext())
                    {
                        var contact = CreateContact(cursor, ctx);                        if (!string.IsNullOrWhiteSpace(contact.Name))
                        {                            // 读取出一条,即通知界面展示
                            OnContactLoaded?.Invoke(this, new ContactEventArgs(contact));
                            contacts.Add(contact);
                        }                        if (stopLoad)                            break;
                    }
                }
            });            return contacts;

        }        /// 
        /// 读取一条通讯录数据
        /// 
        /// 
        /// 
        /// 
        Contact CreateContact(ICursor cursor, Context ctx)
        {
            var contactId = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.Id);

            var numbers = GetNumbers(ctx, contactId);
            var emails = GetEmails(ctx, contactId);

            var uri = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.PhotoThumbnailUri);            string path = null;            if (!string.IsNullOrEmpty(uri))
            {                try
                {                    using (var stream = Android.App.Application.Context.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(uri)))
                    {
                        path = Path.Combine(Path.GetTempPath(), $"{ThumbnailPrefix}-{Guid.NewGuid()}");                        using (var fstream = new FileStream(path, FileMode.Create))
                        {
                            stream.CopyTo(fstream);
                            fstream.Close();
                        }

                        stream.Close();
                    }


                }                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex);
                }

            }
            var contact = new Contact
            {
                Name = GetString(cursor, ContactsContract.Contacts.InterfaceConsts.DisplayName),
                Emails = emails,
                Image = path,
                PhoneNumbers = numbers,
            };            return contact;
        }        /// 
        /// 读取联系人电话号码
        /// 
        /// 
        /// 
        /// 
        string[] GetNumbers(Context ctx, string contactId)
        {
            var key = ContactsContract.CommonDataKinds.Phone.Number;

            var cursor = ctx.ApplicationContext.ContentResolver.Query(
                ContactsContract.CommonDataKinds.Phone.ContentUri,
                null,
                ContactsContract.CommonDataKinds.Phone.InterfaceConsts.ContactId + " = ?",                new[] { contactId },
                null
            );            return ReadCursorItems(cursor, key)?.ToArray();
        }        /// 
        /// 读取联系人邮箱地址
        /// 
        /// 
        /// 
        /// 
        string[] GetEmails(Context ctx, string contactId)
        {
            var key = ContactsContract.CommonDataKinds.Email.InterfaceConsts.Data;

            var cursor = ctx.ApplicationContext.ContentResolver.Query(
                ContactsContract.CommonDataKinds.Email.ContentUri,
                null,
                ContactsContract.CommonDataKinds.Email.InterfaceConsts.ContactId + " = ?",                new[] { contactId },
                null);            return ReadCursorItems(cursor, key)?.ToArray();
        }

        IEnumerable ReadCursorItems(ICursor cursor, string key)
        {            while (cursor.MoveToNext())
            {
                var value = GetString(cursor, key);
                yield return value;
            }

            cursor.Close();
        }        string GetString(ICursor cursor, string key)
        {            return cursor.GetString(cursor.GetColumnIndex(key));
        }

    }
}

需要添加 Plugin.CurrentActivity 和 Acr.UserDialogs 包。

7、Android工程添加权限处理判断类

Permission.Util

using Android.Content.PM;namespace TerminalMACS.Clients.App.Droid
{    public static class PermissionUtil
    {
        /**
		* 通过验证给定数组中的每个条目的值是否为Permission.Granted,检查是否已授予所有给定权限。
		*
		* See Activity#onRequestPermissionsResult (int, String[], int[])
		*/
        public static bool VerifyPermissions(Permission[] grantResults)
        {            // 必须至少检查一个结果.
            if (grantResults.Length < 1)                return false;            // 验证是否已授予每个必需的权限,否则返回false.
            foreach (Permission result in grantResults)
            {                if (result != Permission.Granted)
                {                    return false;
                }
            }            return true;
        }
    }
}

MainActivity.OnRequestPermissionResult是权限申请结果处理函数,在此函数中调用ContactsService.OnRequestPermissionsResult通知通讯录服务权限处理结果。

MainActivity.cs

using Acr.UserDialogs;using Android.App;using Android.Content.PM;using Android.OS;using Android.Runtime;using TerminalMACS.Clients.App.Droid.Services;using TerminalMACS.Clients.App.Services;namespace TerminalMACS.Clients.App.Droid
{
    [Activity(Label = "TerminalMACS.Clients.App", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        IContactsService contactsService = new ContactsService();        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            UserDialogs.Init(() => this);            // 将通讯录服务实例传递给共享库,由共享库使用读取通讯录接口
            LoadApplication(new App(contactsService));
        }        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);            // 通讯录服务处理权限请求结果
            ContactsService.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

8、创建通讯录ViewModel,并使用通讯录服务

using System;using System.Collections;using System.Collections.Generic;using System.Collections.ObjectModel;using System.Linq;using System.Threading.Tasks;using System.Windows.Input;using TerminalMACS.Clients.App.Models;using TerminalMACS.Clients.App.Services;using Xamarin.Forms;namespace TerminalMACS.Clients.App.ViewModels
{    /// 
    /// 通讯录ViewModel
    /// 
    public class ContactViewModel : BaseViewModel
    {        /// 
        /// 通讯录服务接口
        /// 
        IContactsService _contactService;        /// 
        /// 标题
        /// 
        public new string Title => "通讯录";        private string _SearchText;        /// 
        /// 搜索关键字
        /// 
        public string SearchText
        {
            get { return _SearchText; }            set
            {
                SetProperty(ref _SearchText, value);
            }
        }        /// 
        /// 通讯录搜索命令
        /// 
        public ICommand RaiseSearchCommand { get; }        /// 
        /// 通讯录列表
        /// 
        public ObservableCollection Contacts { get; set; }        private List _FilteredContacts;        /// 
        /// 通讯录过滤列表
        /// 
        public List FilteredContacts

        {
            get { return _FilteredContacts; }            set
            {
                SetProperty(ref _FilteredContacts, value);
            }
        }        public ContactViewModel(IContactsService contactService)
        {
            _contactService = contactService;
            Contacts = new ObservableCollection();
            Xamarin.Forms.BindingBase.EnableCollectionSynchronization(Contacts, null, ObservableCollectionCallback);
            _contactService.OnContactLoaded += OnContactLoaded;
            LoadContacts();
            RaiseSearchCommand = new Command(RaiseSearchHandle);
        }        /// 
        /// 过滤通讯录
        /// 
        void RaiseSearchHandle()
        {            if (string.IsNullOrEmpty(SearchText))
            {
                FilteredContacts = Contacts.ToList();                return;
            }

            Func checkContact = (s) =>
            {                if (!string.IsNullOrWhiteSpace(s.Name) && s.Name.ToLower().Contains(SearchText.ToLower()))
                {                    return true;
                }                else if (s.PhoneNumbers.Length > 0 && s.PhoneNumbers.ToList().Exists(cu => cu.ToString().Contains(SearchText)))
                {                    return true;
                }                return false;
            };
            FilteredContacts = Contacts.ToList().Where(checkContact).ToList();
        }        /// 
        /// BindingBase.EnableCollectionSynchronization 为集合启用跨线程更新
        /// 
        /// 
        /// 
        /// 
        /// 
        void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
        {            // `lock` ensures that only one thread access the collection at a time
            lock (collection)
            {
                accessMethod?.Invoke();
            }
        }        /// 
        /// 收到事件通知,读取一条通讯录信息
        /// 
        /// 
        /// 
        private void OnContactLoaded(object sender, ContactEventArgs e)
        {
            Contacts.Add(e.Contact);
            RaiseSearchHandle();
        }        /// 
        /// 异步读取终端通讯录
        /// 
        /// 
        async Task LoadContacts()
        {            try
            {
                await _contactService.RetrieveContactsAsync();
            }            catch (TaskCanceledException)
            {
                Console.WriteLine("任务已经取消");
            }
        }
    }
}

9、添加通讯录页面展示通讯录数据


    
        
            
            
                
                    
                        
                            
                                
                                
                                    
                                    
                                    
                                
                            
                        
                    
                
            
        
    

三、源码获取

  • 1.完整源码

  • 2.Android客户端可成功取得通讯录数据,并可查询;

  • 3.iOS读取通讯录功能代码也已添加,但由于本人没有iOS测试环境,所以未验证,有条件的朋友可以测试下iOS的通讯录读取功能,如果代码不起作用,可参考本文参考的文章检查iOS代码。