最近工作中要整理最新的Android L的联系人源码分析,在网上各种搜索都不怎么满意,要么版本太老,要么分析的太浅层次了,要么只有简单的使用。无奈就自己整理,发出来共享一下。
联系人分为了Contacts和ContactsCommon,与sim卡联系人相关的是在Telephony中,数据库是在ContactsProvider,apk要push到/system/priv-app/Contacts下
主要介绍一下和联系人相关的几个主要的功能,其他简单的就略过了,具体问题还是需要在code中看。
系统联系人数据库存放在如下位置data\data\com.android.providers.contacts\data bases\,正常的联系人都保存在contacts2.db中,个人信息保存在profile.db,但是两个数据库的基本结构都是一样的
表结构
其中有比较重要的三个表:account、mimetype、data、raw_contacts、contacts,其他的表结构用的较少,遇到问题再去ContactsProvider中查看code。
几张表直接的主要关联关系
Account是和账号有关
data表中存放的是联系人具体信息,每行存储一位联系人的某一类信息(如电话,姓名,邮箱、社交账号、地址等)
其中数据存放在dataX(x为数字)中,根据这行的mimetype_id来对应是什么类的信息数据,mimetype表中存放的是几种数据类型和_id,如图所示
对应到code中,可以通过下面的一些类去源码中具体看mimetype和dataX是对应关系
例如:
其他的类可以参考下面这段定义
String MIMETYPE_EMAIL_V2 = Email.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/email_v2";
String MIMETYPE_IM = Im.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/im";
String MIMETYPE_NICKNAME = Nickname.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/nickname";
String MIMETYPE_ORGANIZATION = Organization.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/organization";
String MIMETYPE_PHONE_V2 = Phone.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/phone_v2";
String MIMETYPE_SIP_ADDRESS = SipAddress.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/sip_address";
String MIMETYPE_NAME = StructuredName.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/name";
String MIMETYPE_POSTAL_ADDRESS_V2 = StructuredPostal.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/postal-address_v2";
String MIMETYPE_IDENTITY = Identity.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/identity";
String MIMETYPE_PHOTO = Photo.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/photo";
String MIMETYPE_GROUP_MEMBERSHIP = GroupMembership.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/group_membership";
String MIMETYPE_NOTE = Note.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/note";
String MIMETYPE_WEBSITE = Website.CONTENT_ITEM_TYPE;//"vnd.android.cursor.item/website";
下面是数据库中的视图(一些联合查询的封装),其中重要的几个已经标出。
以上都是一些常用到的数据表和视图,详细的数据表结构以及各个字段的数据类型和意义还需要在code中详细查看。
ContactsProvider中最主要的两个类:
com.android.providers.contacts.ContactsDatabaseHelper
com.android.providers.contacts.ContactsProvider2
ContactsDatabaseHelper继承自SqliteOpenHelper,是对数据库的创建和更新的操作,该类中有详细的每个表的字段和数据类型的定义,以及数据库根据version更新和对联系人部分数据CURD操作封装的声明,提供给ContactsProvider2调用
ContactsProvider2继承自AbstractContactsProvider,是apps和数据库之间数据传递的协议,通过Uri来进行访问,定义了apps需要使用到的CURD方法
以主界面的DefaultContactBrowseListFragment为例,继承关系如图
如下图所示:继承自ContactEntryListFragment的List界面的数据加载流程如下:
在Fragment中通过LoaderManager来对CursorLoader的创建和管理, Fragment中持有一个ContactEntryListAdapter对象,CursorLoader的参数配置等都放在ContactEntryListAdapter中处理, CursorLoader数据加载完成之后回调到Fragment中来,然后通过ContactEntryListAdapter来控制数据的刷新,以及UI的控制
DefaultContactBrowseListFragment对应的adapter为DefaultContactListAdapter,对应的cursorloader为ProfileAndContactsLoader。
对于联系人的创建、更新、删除一般情况下都封装在ContactSaveService类中,如图
ContactSaveService类继承自IntentService,是一个用于异步操作联系人数据的service,在完成数据操作之后,通过请求操作的时候传递来intent(intent中包含需要回调的Activity和action)回调到Activity中来通知UIThread进行UI update
以create contact为例:
ContactEditorFragment.save()方法中ContactSaveService中createSaveContactIntent(),传递了回调需要的参数
ContactSaveService中saveContact()中
能够回调到Activity是因为,Contact中有关联系人操作的Activity的基类ContactsActivity 实现了ContactSaveService.Listener,然后在onCreate中ContactSaveService.registerListener(this),然后通过上面的代码逻辑最终会调用到
主界面为PeopleActivity界面,分为两个tab,分别为DefaultContactBrowseListFragment和ContactTileListFragment,通过viewpager进行切换
其中DefaultContactBrowseListFragment为联系人列表页面,主要用来处理联系人列表的加载展示和联系人搜索,数据加载流程上面已经说明了,列表itemview为ContactListItemView
ContactTileListFragment为收藏联系人及常用联系人,该类直接继承自Fragment,但是和之前说的流程基本一致,方式略微不同,数据分为收藏联系人和最近常联系的联系人,通过ContactTileLoaderFactory来提供加载不同数据的cursorloader
在UI显示上,通过adapter来控制显示stared和frequent联系人,stared为3个一行,frequent一行一个
主界面最上层右下角有一个create联系人的按钮,点击直接进入create contact界面
最上面是toolbar,有search按钮,和menu键
主界面还监听了provider数据变化
从联系人列表进入的时候是全屏状态,从其他途径(目前已知的有从短信、桌面快捷方式)进入的时候是非全屏状态,是通过MultiShrinkScroller控制(runEntranceAnimation)
数据加载和上面的加载流程类似。
编辑界面在ContactEditorActivity->ContactEditorFragment中,界面中数据加载流程都和上面类似,比较复杂的是编辑界面的UI,是一个自定义的RawContactEditorView,下图是数据传递和绑定的流程图
Fragment中持有自定义view对象RawContactEditorView和数据对象RawContactDeltaList,从数据库中查询出数据Contact之后,将数据封装到RawContactDeltaList中,然后通过bindeditors、editor.setState方法将RawContactDeltaList数据传递给自定义view,然后RawContactEditorView就会将数据拆分,然后传递各个子view,同时也将RawContactDeltaList传递过去,然后数据更新就会在子view中完成,当Fragment中需要进行保存联系人的时候,就可以直接使用RawContactDeltaList;保存联系人的操作和上面的数据交互流程类似。
Pick界面为ContactSelectionActivity,会根据不同的action加载不同的Fragment,包括ContactPickerFragment
PhoneNumberPickerFragment
EmailAddressPickerFragment
PostalAddressPickerFragment
JoinContactListFragment
比较常用的是前3个Fragment
ContactSelectionActivity一般都是通过startActivityForResult的方式启动的,所以在它finish之前需要返回一个结果回去。
数据加载流程略过。
这个功能是从主界面上的menu键触发
会调用到Telephony的com.android.phone.SimContacts extends ADNList,ADNList中负责数据加载流程,SimContacts负责UI逻辑处理,都很简单。
导入vcard文件,是在ImportVCardActivity中,首先会通过VCardScanThread获取到存储空间中的.vcf文件,然后提示用户来选择要导入的vcf文件,通过VCardCacheThread进行缓存数据,封装导入数据需要的数据类型,具体的导入过程是通过bind VCardService进行,service中通过ExecutorService (初始化的为一个单线程的线程池)来执行ImportProcessor(implements Runable)线程,ImportProcessor中执行具体解析导入,ImportProcessor在初始化的时候会传一个NotificationImportExportListener来进行导入完成之后的接口回调。
导出联系人到vcf文件,存储到存储空间中,导出过程和导入过程类似,也是使用VCardService进行。
Android L上没有专门针对SIM联系人的编辑,显示等处理,只有上面的导入操作,据说5.1支持双卡,还不清楚对于sim联系人有什么影响
1、拷贝到剪贴板
/**
* Copy a text to clipboard.拷贝到剪贴板
*
* @param context Context
* @param label Label to show to the user describing this clip.
* @param text Text to copy.
* @param showToast If {@code true}, a toast is shown to the user.
*/
public static void copyText(Context context, CharSequence label, CharSequence text,
boolean showToast) {
if (TextUtils.isEmpty(text)) return;
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(
Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(label == null ? "" : label, text);
clipboardManager.setPrimaryClip(clipData);
if (showToast) {
String toastText = context.getString(R.string.toast_text_copied);
Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show();
}
}
2、Android L上用到的水波纹效果的类:ViewAnimationUtils
3、修改statusbar颜色(Android L上的,其他version未验证)
private void updateStatusBarColor() {
if (mScroller == null) {
return;
}
final int desiredStatusBarColor;
// Only use a custom status bar color if QuickContacts touches the top of the viewport.
if (mScroller.getScrollNeededToBeFullScreen() <= 0) {
desiredStatusBarColor = mStatusBarColor;
} else {
desiredStatusBarColor = Color.TRANSPARENT;
}
// Animate to the new color.
final ObjectAnimator animation = ObjectAnimator.ofInt(getWindow(), "statusBarColor",
getWindow().getStatusBarColor(), desiredStatusBarColor);
animation.setDuration(ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION);
animation.setEvaluator(new ArgbEvaluator());
animation.start();
}
总体来看,和我们之前在mtk4.4.4上做的区别很大,移植工作量比较大。