背景介绍
什么是vCard联系人
vCard的定义我们可以参考维基百科的介绍,大概的意思是说它是一种以 .vcf 结尾的结构化文件,里面保存了诸如姓名、电话、地址、邮箱等个人信息,便于在网上或者设备之间进行数据交换,vCard联系人就是以这种格式存储的联系人信息。
vCard有哪些版本
通过查看源码 frameworks/opt/vcard 目录下面的 VCardConfig.java 文件,我们可以发现代码里面有三个vCard的版本,每个版本之间不能相互传输数据,因为它们支持的内容有细微的不同,比如3.0就比2.1多几个新的field,定义如下:
/**
* <p>
* Generic vCard format with the vCard 2.1. When composing a vCard entry,
* the US convension will be used toward formatting some values.
* </p>
*/
public static final int VCARD_TYPE_V21_GENERIC =
(VERSION_21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/**
* <p>
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
* </p>
* <p>
* Not fully ready yet. Use with caution when you use this.
* </p>
*/
public static final int VCARD_TYPE_V30_GENERIC =
(VERSION_30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/**
* General vCard format with the version 4.0.
* @hide vCard 4.0 is not published yet.
*/
public static final int VCARD_TYPE_V40_GENERIC =
(VERSION_40 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
vCard联系人导出操作过程
vCard文本数据格式
BEGIN:VCARD
VERSION:2.1
N:Haaha;;;;
FN:Haaha
TEL;CELL:999-9999
EMAIL;HOME:qq@qq.com
ADR;HOME:;;Chengdu;;;;
URL:www.baidu.com
PHOTO;ENCODING=BASE64;JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwM
NOTE:We friend
X-AIM:IMqq
X-SIP:sipqqq
END:VCARD
代码流程分析
整体概述
- 通过界面跳转到 MultiPickContactsActivity 并选择几个需要导出的联系人 通过 CheckListListener.putValue 收集被选中联系人位置信息,并通过intent返回给 PeopleActivity。
- PeopleActivity 通过 ExportVCardActivity 绑定 VCardService 并打开 DocumentsActivity 界面进行异步的创建 .vcf 文件和路径。
- ExportVCardActivity 通过 VCardService 调用 ExportProcessor,使用线程方式读取联系人信息,并按照一定格式写入第2步中创建的vcf文件中。
整体流程图
部分重点方法介绍
ExportVCardActivity
private Intent getCreateDocIntent() {
final Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);//ACTION_CREATE_DOCUMENT =android.intent.action.CREATE_DOCUMENT启动DocumentsActivity的action
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(VCardService.X_VCARD_MIME_TYPE);
intent.putExtra(Intent.EXTRA_TITLE, mBidiFormatter.unicodeWrap(
getString(R.string.exporting_vcard_filename), TextDirectionHeuristics.LTR));//exporting_vcard_filename = contacts.vcf 默认导出的文件名
return intent;
}
ExportProcessor
private void runInternal() {
if (DEBUG) Log.d(LOG_TAG, String.format("vCard export (id: %d) has started.", mJobId));
final ExportRequest request = mExportRequest;
VCardComposer composer = null; //The class for composing vCard from Contacts information.
Writer writer = null;
boolean successful = false;
try {
if (isCancelled()) {
Log.i(LOG_TAG, "Export request is cancelled before handling the request");
return;
}
final Uri uri = request.destUri;//得到contacts的uri
final OutputStream outputStream;
try {
outputStream = mResolver.openOutputStream(uri);//构建输出流
}
........省略部分代码
final String exportType = request.exportType;
final int vcardType;
if (TextUtils.isEmpty(exportType)) { //传入的类型为null
vcardType = VCardConfig.getVCardTypeFromString(
mService.getString(R.string.config_export_vcard_type));//获取默认的vCard 版本为 2.1
} else {
vcardType = VCardConfig.getVCardTypeFromString(exportType);
}
composer = new VCardComposer(mService, vcardType, true); //构建VCardComposer默认的编码格式为 DEFAULT_EXPORT_CHARSET = "UTF-8";
// Tony add
// composer = new VCardComposer(ExportVCardActivity.this, vcardType,"UTF-16", true); 可以直接构建某种编码格式的VCardComposer
.......省略部分代码
writer = new BufferedWriter(new OutputStreamWriter(outputStream));
final Uri contentUriForRawContactsEntity = RawContactsEntity.CONTENT_URI;
// TODO: should provide better selection.
if (!composer.init(Contacts.CONTENT_URI, new String[] {Contacts._ID},
selExport, null,
null, contentUriForRawContactsEntity)) {//获取联系人信息
final String errorReason = composer.getErrorReason();
.........
}
final int total = composer.getCount();
if (total == 0) {
final String title =
mService.getString(R.string.fail_reason_no_exportable_contact);
doFinishNotification(title, null);
return;
}
int current = 1; // 1-origin
while (!composer.isAfterLast()) {
if (isCancelled()) {
Log.i(LOG_TAG, "Export request is cancelled during composing vCard");
return;
}
try {
writer.write(composer.createOneEntry());//创建每个联系人的vCard信息,然后通过write写入文件
}
........省略部分代码
}
VCardComposer
public String createOneEntry(Method getEntityIteratorMethod) {
if (mIsDoCoMo && !mFirstVCardEmittedInDoCoMoCase) {
mFirstVCardEmittedInDoCoMoCase = true;
// Previously we needed to emit empty data for this specific case, but actually
// this doesn't work now, as resolver doesn't return any data with "-1" contactId.
// TODO: re-introduce or remove this logic. Needs to modify unit test when we
// re-introduce the logic.
// return createOneEntryInternal("-1", getEntityIteratorMethod);
}
final String vcard = (mEntityIterator == null || getEntityIteratorMethod != null)
?createOneEntryInternal(mCursor.getLong(mIdColumn),getEntityIteratorMethod)
:createOneEntryInternalByIterator(); // 获取vCard文本字符串
if (!mCursor.moveToNext()) {
Log.e(LOG_TAG, "Cursor#moveToNext() returned false");
}
return vcard;
}
public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
if (contentValuesListMap == null) {
Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
return "";
} else {
final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);//创建 builder 并设置vCard中默认的几个字段,包括BEGIN:VCARD 和 VERSION:2.1
builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
.appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
.appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE),
mPhoneTranslationCallback)
.appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
.appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
.appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
.appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));//加入contacts中的信息
if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
}
builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
.appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
.appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
.appendSipAddresses(contentValuesListMap.get(SipAddress.CONTENT_ITEM_TYPE))
.appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
return builder.toString();
}
}