android导出vCard联系人流程

本博客基于android7.1版本分析,仅用于沟通学习使用

背景介绍

什么是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

代码流程分析

整体概述

  1. 通过界面跳转到 MultiPickContactsActivity 并选择几个需要导出的联系人 通过 CheckListListener.putValue 收集被选中联系人位置信息,并通过intent返回给 PeopleActivity。
  2. PeopleActivity 通过 ExportVCardActivity 绑定 VCardService 并打开 DocumentsActivity 界面进行异步的创建 .vcf 文件和路径。
  3. 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();
        }
    }
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值