Android 4.3 SDK Samples 阅读笔记之一 ContactManager

一、综述:

ContactManager是一个关于联系人管理的示例代码,共包含二个Activity,ContactManager是启动Activity,用于显示联系人列表及添加按钮;ContactAdder是添加联系人用的Activity。

在默认使用模拟器调试项目时,如果没有添加过联系人帐户,添加功能会报错。原因是Android对于联系人的管理与传统手机不一样,Android的联系人可以与帐户关联,这个帐户可以是一个电子邮箱地址或者是Exchange的帐户。当然,与可以不与某个帐户关联,这种被称为本地联系人。

进入Android自带的联系人管理界面,当没有任何联系人时界面如下:

 

其中登录帐户,就是指创建一个帐户,帐户在Android的系统设置中的帐户管理里也可以创建。在未创建帐户直接创建新联系人时界面如下:

 

在添加界面上方,即显示了“仅保存在手机中,不同步…”信息。

 

ContactManager项目中添加联系人,必须要选择所属的帐户,如果没有选择帐户,则会报错,这个错误,后文中将在改进部分中修复此问题。

 

二、阅读笔记:

1.ContactManager.java源码分析

该类中包含方法如下:

private void populateContactList():显示联系人列表的方法,在OnCreate方法的最后一行调用。

private Cursor getContacts():取得所有联系人的游标的方法,在populateContactList方法第一行调用。

protected void launchContactAdder():显示添加联系人窗体的方法,在mAddAccountButton按钮的 onClick事件的处理程序中调用。

 

1.1依照相关顺序,首先解析getContacts的源码,在getContacts方法中,主要使用了managerQuery方法,返回了一个游标。

managerQuery方法接受的参数意义依次为:

Uri uri:查询目标Content Provider的唯一标识,取值ContactsContract.Contacts.CONTENT_URI代表查询联系人。

String[] projection:返回字段的名称列表,SQL查询语句中的SELECT子句以数组方式书写。

String selection:查询的条件,SQL中WHERE子句。

String[] selectionArgs:查询的条件中的参数值列表,对应替换selection中“? ”占位符。

String sortOrder:排序方式,SQL中的ORDER BY子句

返回值Cursor是一种游标对象,用于顺序读取查询结果。

1.2 populateContactList方法中,首先调用了getContacts,获得一个包含所有联系人的游标对象。

随之,构造了一个SimpleCursorAdapter的适配器对象,用于将Cursor数据源中的数据绑定到视图中显示。

SimpleCursorAdapter对象的构造方法参数意义依次为:

Context contextListView相关联的SimpleListItemFactory运行的上下文对象,即ListView所在的Activity对象。

int layout:用于显示Cursor中返回的每一行数据的layout

Cursor c:数据源的游标对象。

String[] from:要显示数据的字段名称列表。

Int[] to:用于显示数据的控件的id列表。

根据源码可以得知,将使用layout下的contact_entry.xml作为游标中每一行数据显示的View,显示的字段只有一个,就是ContactsContract.Data.DISPLAY_NAME,显示数据的控件idcontactEntryText。

查看res文件夹下的layout文件夹下的contact_entry.xml可以发现,contactEntryText是一个TextView控件。

在本示例中,在managerQuery方法中的查询字段使用的是ContactsContract.Contacts.DISPLAY_NAME,而在populateContactList方法中显示字段时使用的是ContactsContract.Data.DISPLAY_NAME,两次使用的常量不同,但通过查询发现,两个常量的值都为“display_name”,可以交换使用,直接使用其值,程序也可正常运行。

 

1.2 launchContactAdder方法在mAddAccountButton控件的onClick事件的Listener中调用,主要作用是显示添加联系人的界面。

 

2.ContactAdder.java源码分析

ContactAdder类相对而言要复杂一些,本身实现了OnAccountsUpdateListener接口,这个接口内包含一个方法onAccountsUpdated。实现了该接口的类,可以由AccountManager注册成为OnAccountsUpdate事件的监听者,当系统中的帐户发生更新时,由AccountManager调用所有监听者的onAccountsUpdated方法。

ContactAdder类中实现了如下方法:

private static AuthenticatorDescription getAuthenticatorDescription(String type, AuthenticatorDescription[] dictionary):返回帐户的签发者的相关信息。在onAccountsUpdated方法中调用,用于读取帐户的显示数据。

private void updateAccountSelection():将mAccountSpinner下拉列表中当前选中的帐户信息赋给内部字段mSelectedAccount,该内部字段指示添加的联系人属于哪个帐户。此方法在mAccountSpinner控件的OnItemSelected事件的处理方法中调用。

private void onSaveButtonClicked():处理添加按钮的单击,在mContactSaveButton按钮的onClick事件处理程序中调用。

protected void createContactEntry():添加一个新的联系人,在onSaveButtonClicked事件中调用。

另外该类中还包括两个内部类:

AccountData:主要是帐户的显示信息,包括帐户名称,帐户的签发者的图标,签发者的名称。另外还有一个type字段,主要是type签发者的唯一识别标志,取图片和名称需要使用,并不用于显示。

AccountAdapter:用于帐户信息显示的适配器类,继承自ArrayAdapter。

在解析各个方法的代码前,先解析OnCreate方法中的内容。

在OnCreate方法中,构建了两个集合,一个为电话类型的集合,一个为Email类型的集合。在Android系统中,一个联系人,可以有多个电话,多个Email,这些电话和Email可以标识为办公,家庭等不同类型。使用了ArrayAdapter将mContactPhoneTypeSpinner控件和mContactEmailTypeSpinner分别与构建的两个集合关联,并使用getTypeLabel()方法查询对应的显示名称进行显示。

 

而且在OnCreate方法中,构造了内部类AccountAdapter的实例,追踪构造方法,发现使用setDropDownViewResource(R.layout.account_entry)方法,设置了下拉列表的View使用的layout,并使用AccountAdapter将内部字段mAccounts作为mAccountSpinner控件的数据源。mAccounts是一个AccountData类的集合,也即是帐户信息的集合。

在138行使用了AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true);将ContactAdder注册成为OnAccountsUpdate事件的监听者。此时,会立刻调用一次CContactAdder.onAccountsUpdated()方法。该方法先清除帐户信息集合,再读取所有的帐户,并根据帐户名称和签发者的相关信息构造帐户显示信息类的集合,赋值给mAccountSpinner控件的数据源内部变量mAccounts,,并调用AccountAdapter类的notifyDataSetChanged方法,通知Adapter更新mAccountSpinner控件的显示同步数据来源的变化。

最复杂的方法是CreateContactEntry方法,要理解该方法的代码,需要理解Android对通迅录的组织。

每个联系人属于一个帐户,首先是在该帐户下添加一个记录,获得RAW_CONTACT_ID。

然后使用RAW_CONTACT_ID添加联系人信息,添加电话信息,添加Email信息。

在帐户下添加记录时,操作的Uri为ContactsContract.RawContacts.CONTENT_URI,返回值为RAW_CONTACT_ID,而添加联系人信息,电话信息,和Email信息时操作的是ContactsContract.Data.CONTENT_URI,使用RAW_CONTACT_ID字段与帐户下的记录关联。

本例中使用了批量操作,故先使用以下代码构建了一个操作集合。

 

随后使用了一系列代码构建了批量操作,并添加到该集合中。构建批量操作中的第一个操作的代码如下:

 

第一个操作中为添加联系人记录,分别添加了帐户的类型和名称两个值,返回该记录的ID值。操作中使用了如下方法:

newInsert(Uri uri)方法表示,插入操作使用的Uri。

withValue(String name,Object value)方法表,要操作的字段名和其值。

build()方法表示,将其构建成为一个操作。

与构建第一个操作类似的代码构建了接下来的三个操作,分别用于添加联系人名称,电话号码,Email地址。以构建添加联系人的操作为例,其代码如下:

 

添加了联系人的名称操作使用了withValueBackReference方法。此方法中第一个参数表示添加记录的字段名,第二个参数表示了使用批量操作中下,标为0的操作的返回值也就是添加联系人记录的操作,其返回值即是联系人的记录Id。

添加电话号码和邮件地址,只是字段不同而已。

最后使用getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);执行了批量操作。getContentResolver方法用于获取ContentResolver对象,该对象是执行操作的执行者,applyBatch方法用于执行批量的操作,第一个参数是执行批量操作的ContentProvider的标识名称,第二个参数是要执行的操作集合。

三、改进源码

3.1 错误排除

首先要做的改进是,允许在没有帐户的情况下增加联系人。

在添加联系人的批量操作中的第一个,会使用mSelectedAccount中存储的对象的getType()和getName方法分别获取选中的帐户的类型和名称,如果系统中没有帐户,mSelectedAccount的值将为null,所以在使用前判定mSelectedAccount是否为null.

将代码:

 

修改为:

 

在没有帐户被选中的情况下,添加记录时,帐户的类型和名称都使用null值,这样添加的联系人记录就是仅本地存储,不与任何帐户关联的。

在操作中加入AGGREGATION_MODE字段,设其值为AGGREGATION_MODE_DISABLED,主要目的是阻止同名联系人的电话和Email等资料自动合并处理。

另外,还需要注释掉使用到mSelectedAccount的语句。

 

3.2 新版本的建议做法

在本示例代码中,可以发现ManagerQuery方法与SimpleCursorAdapter类的构造方法被标识为过时的方法。

在新版本的SDK的Document中建议使用CursorLoader类来替代ManagerQuery方法。CursorLoader类会创建一个用于游标的Loader类,该类由LoaderManager对象管理。需要实现三个方法:

onCreateLoader(int id,Bundle args):该方法在LoaderManager对象创建Loader时使用。

onLoadFinished(Loader<T> arg0,T arg1):该方法在Loader读取数据完成后调用。

onLoaderReset(Loader<T> arg0):该方法在Loader重置时调用。

重写的populateContactList2方法代码如下:

 

本示例代码中构建了populateContactList2方法,替换了原代码中的populateContactList和getContacts方法。

注意与原来代码的区别,依然是构造了一个SimpleCursorAdapter对象,只是将其中的第三个参数Cursor设为null值。主要原因是代码中使用CursorLoader去管理Cursor,不是直接在代码中构造Cursort,故在创建SimpleCursorAdapter对象时无法传入Cursor对象。

接下来使用getLoaderManager取得了当前Activity的LoaderManager对象,调用该对象的initLoader方法,初始化一个Loader对象。

intiLoader方法有三个参数:第一个参数为int id,是一个标识符,用于LoaderManager实例区分不同的Loader;第二个参数为Bundle args,用于向Loader中传入数据,第三个参数是LoaderCallBack<T>接口的实现类。由参数类型LoaderCallBack<T>可以看出这是一个回调类,主要用于LoaderManager管理该Loader对象时三个方法,在上文中已经介绍过。同时该接口为一个泛型接口,可以用于管理不同类型的Loader。本例中用于管理Cursor对象,故传入值为LoaderCallBack<Cursor>。

在LoaderCallBack<Cursor>的onCreateLoader中创建了一个CursorLoader类的实例返回,CursorLoader类是Loader<Cursor>的实现。在onLoadFinished方法中使用如下代码将CursorLoader加载完成后获得的Cursor对象置入SimpleCursorAdapter对象。

 

在onLoaderReset方法中使用如下代码,将未加载完成的Cursor对象从SimpleCursorAdapter对象中卸载。

 

为了配合上述代码,将原先在populateContactList方法中声明的SimpleCursorAdapter对象声明放入类中。由于CursorLoader是在Android 3.0中加入的新类型,故需要将程序的最低SDK需求设为3.0,即将minSdkVersion的值改为11

 最后将onCreate中的两处对populateContactList方法的调用改为populateContactList2方法调用。

至此,ContactManager类的改造完成,使用了新的SDK的实现方式,并修复了无帐户时添加联系人的错误。

改造后的项目源码下载地址:https://files.cnblogs.com/PandaHi/ContactManager.rar

posted @ 2013-10-11 12:51  [武汉]胖大海  阅读(1443)  评论(1编辑  收藏  举报