Android 使用VCard数据类型 异步进行联系人备份与恢复操作

生活中常有人因为更换手机而丢失联系人,所以联系人备份与恢复操作就显得有一些必要与价值。

所以学习了相关内容,并进行了适当整合,在这里整理出来。

本篇博客两个重点

  1. 使用VCard库进行联系人备份与恢复
  2. 异步进行备份与恢复操作

为什么要用VCard

VCard是用于联系人数据存储的标准数据格式,且有一套已经成熟的库可以使用,通用于手机,也通用于邮件等,所以使用VCard进行联系人的数据存储与备份格式是非常好的选择。当然是用XML格式去存储也是可以的。

下载android-vcard.jar请猛戳这里

为什么要异步进行操作

联系人备份与恢复都涉及到了数据库读写,而数据库的操作一向都是比较费时的,更何况是大量数据的情况下,所以将这些操作进行异步处理是一个非常有必要的事情,否则容易造成UI主线程卡顿。异步的另一个好处是同时能够在UI界面上给用户一些过程进度上的反馈,这个在重视产品交互体验的今天是非常重要的。

异步操作UI效果库,源自GitHub请猛戳这里

VCard本身库比较复杂,不再多说,有兴趣的可以研究源码。这里贴上一个已经做过一层封装的联系人处理类,直接调用即可。

封装好的联系人处理类

package com.pku.codingma.backupandrecovercontactdemo;

import android.app.Activity;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.ContactsContract;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import a_vcard.android.provider.Contacts;
import a_vcard.android.syncml.pim.VDataBuilder;
import a_vcard.android.syncml.pim.VNode;
import a_vcard.android.syncml.pim.vcard.ContactStruct;
import a_vcard.android.syncml.pim.vcard.VCardComposer;
import a_vcard.android.syncml.pim.vcard.VCardException;
import a_vcard.android.syncml.pim.vcard.VCardParser;

/**
 * Created by ma on 2016/4/1.
 */
public class ContactInfo {

    /**
     * MUST exist
     */
    private String name; // 姓名

    /**
     * 联系人电话信息
     */
    public static class PhoneInfo {
        /**
         * 联系电话类型
         */
        public int type;
        /**
         * 联系电话
         */
        public String number;
    }

    /**
     * 联系人邮箱信息
     */
    public static class EmailInfo {
        /**
         * 邮箱类型
         */
        public int type;
        /**
         * 邮箱
         */
        public String email;
    }

    private List<PhoneInfo> phoneList = new ArrayList<PhoneInfo>(); // 联系号码
    private List<EmailInfo> email = new ArrayList<EmailInfo>(); // Email

    /**
     * 构造联系人信息
     *
     * @param name 联系人姓名
     */
    public ContactInfo(String name) {
        this.name = name;
    }

    /**
     * 姓名
     */
    public String getName() {
        return name;
    }

    /**
     * 姓名
     */
    public ContactInfo setName(String name) {
        this.name = name;
        return this;
    }

    /**
     * 联系电话信息
     */
    public List<PhoneInfo> getPhoneList() {
        return phoneList;
    }

    /**
     * 联系电话信息
     */
    public ContactInfo setPhoneList(List<PhoneInfo> phoneList) {
        this.phoneList = phoneList;
        return this;
    }

    /**
     * 邮箱信息
     */
    public List<EmailInfo> getEmail() {
        return email;
    }

    /**
     * 邮箱信息
     */
    public ContactInfo setEmail(List<EmailInfo> email) {
        this.email = email;
        return this;
    }

    @Override
    public String toString() {
        return "{name: " + name + ", number: " + phoneList + ", email: " + email + "}";
    }

    /**
     * 联系人
     * 备份/还原操作
     *
     * @author LW
     */
    public static class ContactHandler {

        private static ContactHandler instance_ = new ContactHandler();

        /**
         * 获取实例
         */
        public static ContactHandler getInstance() {
            return instance_;
        }

        /**
         * 获取联系人指定信息
         *
         * @param projection 指定要获取的列数组, 获取全部列则设置为null
         * @return
         * @throws Exception
         */
        public Cursor queryContact(Activity context, String[] projection) {
            // 获取联系人的所需信息
            Cursor cur = context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, projection, null, null, null);
            return cur;
        }

        /**
         * 获取联系人信息
         *
         * @param context
         * @return
         */
        public List<ContactInfo> getContactInfo(Activity context) {
            List<ContactInfo> infoList = new ArrayList<ContactInfo>();

            Cursor cur = queryContact(context, null);

            if (cur.moveToFirst()) {
                do {

                    // 获取联系人id号
                    String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
                    // 获取联系人姓名
                    String displayName = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
                    ContactInfo info = new ContactInfo(displayName);// 初始化联系人信息

                    // 查看联系人有多少电话号码, 如果没有返回0
                    int phoneCount = cur.getInt(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));

                    if (phoneCount > 0) {

                        Cursor phonesCursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + id, null, null);

                        if (phonesCursor.moveToFirst()) {
                            List<PhoneInfo> phoneNumberList = new ArrayList<PhoneInfo>();
                            do {
                                // 遍历所有电话号码
                                String phoneNumber = phonesCursor.getString(phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                                // 对应的联系人类型
                                int type = phonesCursor.getInt(phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE));

                                // 初始化联系人电话信息
                                ContactInfo.PhoneInfo phoneInfo = new ContactInfo.PhoneInfo();
                                phoneInfo.type = type;
                                phoneInfo.number = phoneNumber;

                                phoneNumberList.add(phoneInfo);
                            } while (phonesCursor.moveToNext());
                            // 设置联系人电话信息
                            info.setPhoneList(phoneNumberList);
                        }
                    }

                    // 获得联系人的EMAIL
                    Cursor emailCur = context.getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, ContactsContract.CommonDataKinds.Email.CONTACT_ID + "=" + id, null, null);

                    if (emailCur.moveToFirst()) {
                        List<EmailInfo> emailList = new ArrayList<EmailInfo>();
                        do {
                            // 遍历所有的email
                            String email = emailCur.getString(emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA1));
                            int type = emailCur.getInt(emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.TYPE));

                            // 初始化联系人邮箱信息
                            ContactInfo.EmailInfo emailInfo = new ContactInfo.EmailInfo();
                            emailInfo.type = type;    // 设置邮箱类型
                            emailInfo.email = email;    // 设置邮箱地址

                            emailList.add(emailInfo);
                        } while (emailCur.moveToNext());

                        info.setEmail(emailList);
                    }

                    //Cursor postalCursor = getContentResolver().query(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI, null, ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID + "=" + id, null, null);
                    infoList.add(info);
                } while (cur.moveToNext());
            }
            cur.close();
            return infoList;
        }

        /**
         * 备份联系人
         */
        public void backupContacts(Activity context, List<ContactInfo> infos) {

            try {
                String path = Environment.getExternalStorageDirectory() + "/contacts.vcf";

                OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(path), "UTF-8");

                VCardComposer composer = new VCardComposer();

                for (ContactInfo info : infos) {
                    ContactStruct contact = new ContactStruct();
                    contact.name = info.getName();
                    // 获取联系人电话信息, 添加至 ContactStruct
                    List<PhoneInfo> numberList = info
                            .getPhoneList();
                    for (ContactInfo.PhoneInfo phoneInfo : numberList) {
                        contact.addPhone(phoneInfo.type, phoneInfo.number,
                                null, true);
                    }
                    // 获取联系人Email信息, 添加至 ContactStruct
                    List<EmailInfo> emailList = info.getEmail();
                    for (ContactInfo.EmailInfo emailInfo : emailList) {
                        contact.addContactmethod(Contacts.KIND_EMAIL,
                                emailInfo.type, emailInfo.email, null, true);
                    }
                    String vcardString = composer.createVCard(contact,
                            VCardComposer.VERSION_VCARD30_INT);
                    writer.write(vcardString);
                    writer.write("\n");

                    writer.flush();
                }
                writer.close();

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (VCardException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 获取vCard文件中的联系人信息
         *
         * @return
         */
        public List<ContactInfo> restoreContacts() throws Exception {
            List<ContactInfo> contactInfoList = new ArrayList<ContactInfo>();

            VCardParser parse = new VCardParser();
            VDataBuilder builder = new VDataBuilder();
            String file = Environment.getExternalStorageDirectory() + "/contacts2.vcf";

            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

            String vcardString = "";
            String line;
            while ((line = reader.readLine()) != null) {
                vcardString += line + "\n";
            }
            reader.close();

            boolean parsed = parse.parse(vcardString, "UTF-8", builder);

            if (!parsed) {
                throw new VCardException("Could not parse vCard file: " + file);
            }

            List<VNode> pimContacts = builder.vNodeList;

            for (VNode contact : pimContacts) {

                ContactStruct contactStruct = ContactStruct.constructContactFromVNode(contact, 1);
                // 获取备份文件中的联系人电话信息
                List<ContactStruct.PhoneData> phoneDataList = contactStruct.phoneList;
                List<PhoneInfo> phoneInfoList = new ArrayList<PhoneInfo>();
                for (ContactStruct.PhoneData phoneData : phoneDataList) {
                    ContactInfo.PhoneInfo phoneInfo = new ContactInfo.PhoneInfo();
                    phoneInfo.number = phoneData.data;
                    phoneInfo.type = phoneData.type;
                    phoneInfoList.add(phoneInfo);
                }

                // 获取备份文件中的联系人邮箱信息
                List<ContactStruct.ContactMethod> emailList = contactStruct.contactmethodList;
                List<EmailInfo> emailInfoList = new ArrayList<EmailInfo>();
                // 存在 Email 信息
                if (null != emailList) {
                    for (ContactStruct.ContactMethod contactMethod : emailList) {
                        if (Contacts.KIND_EMAIL == contactMethod.kind) {
                            ContactInfo.EmailInfo emailInfo = new ContactInfo.EmailInfo();
                            emailInfo.email = contactMethod.data;
                            emailInfo.type = contactMethod.type;
                            emailInfoList.add(emailInfo);
                        }
                    }
                }
                ContactInfo info = new ContactInfo(contactStruct.name).setPhoneList(phoneInfoList).setEmail(emailInfoList);
                contactInfoList.add(info);
            }

            return contactInfoList;
        }

        /**
         * 向手机中录入联系人信息
         *
         * @param info 要录入的联系人信息
         */
        public void addContacts(Activity context, ContactInfo info) {
            ContentValues values = new ContentValues();
            //首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId
            Uri rawContactUri = context.getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values);
            long rawContactId = ContentUris.parseId(rawContactUri);

            //往data表入姓名数据
            values.clear();
            values.put(ContactsContract.RawContacts.Data.RAW_CONTACT_ID, rawContactId);
            values.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
            values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, info.getName());
            context.getContentResolver().insert(
                    ContactsContract.Data.CONTENT_URI, values);

            // 获取联系人电话信息
            List<PhoneInfo> phoneList = info.getPhoneList();
            /** 录入联系电话 */
            for (ContactInfo.PhoneInfo phoneInfo : phoneList) {
                values.clear();
                values.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId);
                values.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
                // 设置录入联系人电话信息
                values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneInfo.number);
                values.put(ContactsContract.CommonDataKinds.Phone.TYPE, phoneInfo.type);
                // 往data表入电话数据
                context.getContentResolver().insert(
                        ContactsContract.Data.CONTENT_URI, values);
            }

            // 获取联系人邮箱信息
            List<EmailInfo> emailList = info.getEmail();

            /** 录入联系人邮箱信息 */
            for (ContactInfo.EmailInfo email : emailList) {
                values.clear();
                values.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId);
                values.put(ContactsContract.RawContacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
                // 设置录入的邮箱信息
                values.put(ContactsContract.CommonDataKinds.Email.DATA, email.email);
                values.put(ContactsContract.CommonDataKinds.Email.TYPE, email.type);
                // 往data表入Email数据
                context.getContentResolver().insert(
                        ContactsContract.Data.CONTENT_URI, values);
            }

        }

    }
}

接下来上异步操作与UI更新相关代码

异步处理与功能使用DEMO

package com.pku.codingma.backupandrecovercontactdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.dd.CircularProgressButton;

import java.util.List;

/**
 * A placeholder fragment containing a simple view.
 */
public class BackupAndRecoverContactActivityFragment extends Fragment implements View.OnClickListener{
    CircularProgressButton mBackupContactButton;
    CircularProgressButton mRecoverContactButton;

    //标记消息的来源
    public final int BACKUP_WHAT = 0;
    public final int RECOVER_WHAT = 1;

    //标记成功还是失败
    public final int SUCCESS_FLAG = 1;
    public final int FAIL_FLAG = 0;

    //用于进行备份和还原操作的ContactHandler内部类
    ContactInfo.ContactHandler handler = ContactInfo.ContactHandler.getInstance();

    public BackupAndRecoverContactActivityFragment() {

    }

    Handler mBackupAndRecoverProcessHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == BACKUP_WHAT){
                //add your action
                if (msg.arg1 == SUCCESS_FLAG){
                    mBackupContactButton.setProgress(100);
                }else {
                    mBackupContactButton.setProgress(-1);
                }
            }else if (msg.what == RECOVER_WHAT){
                //add your action
                if (msg.arg1 == SUCCESS_FLAG){
                    mRecoverContactButton.setProgress(100);
                }else {
                    mRecoverContactButton.setProgress(-1);
                }
            }
            ShowTipTool.showTip(getActivity(), msg.obj.toString());
        }
    };

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view =  inflater.inflate(R.layout.fragment_backup_and_recover_contact, container, false);
        mBackupContactButton = (CircularProgressButton) view.findViewById(R.id.backup_contact_button);
        mRecoverContactButton = (CircularProgressButton) view.findViewById(R.id.recover_contact_button);
        initEvent();
        return view;
    }

    private void initEvent() {
        mRecoverContactButton.setOnClickListener(this);
        mBackupContactButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.backup_contact_button:
                backup_contact();
                break;
            case R.id.recover_contact_button:
                recover_contact();
                break;
            default:
                break;
        }
    }

    public void backup_contact(){
        //让按钮进入工作状态
        mBackupContactButton.setIndeterminateProgressMode(true);
        mBackupContactButton.setProgress(50);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //新建一条Handler处理的消息
                Message message = new Message();
                try{
                    // 进行备份联系人信息动作
                    handler.backupContacts(getActivity(), handler.getContactInfo(getActivity()));
                    //如果顺利,则将消息的参数设置为成功
                    message.obj = "backup success";
                    message.arg1 = SUCCESS_FLAG;
                }catch (Exception e){
                    //如果出现异常,则将消息的参数设置为失败
                    message.obj = "backup fail";
                    message.arg1 = FAIL_FLAG;
                    e.printStackTrace();
                }finally {
                    //最后设置消息来源并发送
                    message.what = BACKUP_WHAT;
                    mBackupAndRecoverProcessHandler.sendMessage(message);
                }
            }
        }).start();
    }

    //与backup基本相同,不再注释
    public void recover_contact(){
        mRecoverContactButton.setIndeterminateProgressMode(true);
        mRecoverContactButton.setProgress(50);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = new Message();
                try {
                    // 获取要恢复的联系人信息
                    List<ContactInfo> infoList = handler.restoreContacts();
                    for (ContactInfo contactInfo : infoList) {
                        // 恢复联系人
                        handler.addContacts(getActivity(), contactInfo);
                    }
                    message.obj = "recover success";
                    message.arg1 = SUCCESS_FLAG;
                } catch (Exception e) {
                    message.obj = "recover fail";
                    message.arg1 = FAIL_FLAG;
                    e.printStackTrace();
                }finally {
                    message.what = RECOVER_WHAT;
                    mBackupAndRecoverProcessHandler.sendMessage(message);
                }
            }
        }).start();
    }
}

读写权限

<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

效果图若干张

初始状态

处理状态

完成状态

异常出错状态

Demo完整代码请猛戳这里

时间: 2024-10-22 04:46:39

Android 使用VCard数据类型 异步进行联系人备份与恢复操作的相关文章

【Android Developers Training】 99. 获取联系人详细信息

注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer.android.com/training/contacts-provider/retrieve-details.html 这节课将会展示如何获取一个联系人的详细数据,比如电子邮件地址,电话号码,等等.当用户获得一个联系人后,他会想要查看他的详细信息.你可以展示给他们所有的信息,或者只展示某一特定类

Android:用Handler实现异步处理功能

Android:用Handler实现异步处理功能 - 51CTO.COM 一.一个问题 有这样一个问题值得我们思考,若把一些类似于下载的功能(既耗时且不一定有结果)写在Activity(主线程)里,会导致Activity阻塞,长时间无响应,直至页面假死(如果5秒钟还没有完成的话,会收到Android系统的一个错误提示 "强制关闭").因此,我们需要把这些耗时的操作放在单独的子线程中操作.这就是Handler的使命.Handler提供异步处理的功能,发送和接收不是同时的(Activity

android 4.4.3上面,联系人的头像默认显示首字母,但是不支持中文字符,修改支持中文

在android 4.4.3上面,联系人的头像默认显示首字母,但是不支持中文字符,如下图: 如果联系人名字的第一位是英文字符(a-z || A-Z),则默认头像将显示该首字母. 如果支持中文时显示第一个汉字,那就happy了. 那就看看如何通过修改源代码来实现这一小功能吧- 我们还是先了解下联系人头像加载的流程吧- 联系人头像加载这个问题还是很有意思的,在Contacts中使用ContactPhotoManager类(严格来讲是这个类的子类)来实现头像的异步加载. 这个类还使用了LruCache

向Android模拟器中批量导入通讯录联系人

使用adb命令向Android模拟器中批量导入通讯录联系人的方法: 使用adb提供的命令, 可以非常方便地从PC中将通讯录批量导入android模拟器中. 首先要先准备好固定格式的vcf文件, 该文件即android中的通讯录存储格式文件. 格式如下: BEGIN:VCARD VERSION:2.1 N:;Qiqi;;; FN:Qiqi TEL;HOME:7474 EMAIL;HOME:qiqi.com ADR;HOME:;;Qiqi;;;; END:VCARD BEGIN:VCARD VERS

android从网络上异步加载图像

研究了android从网络上异步加载图像: (1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法. 在主线程中new 一个Handler对象,加载图像方法如下所示 [java] view plaincopyprint? private void loadImage(final String url, final int id) { handler.post(new Runnable() { public void run()

★android开发--ListView+Json+异步网络图片加载+滚动翻页的例子(图片能缓存,图片不错乱)

例子中用于解析Json的Gson请自己Google下载 主Activity: package COM.Example.Main; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import COM.Example.Main.R; import COM.Example.Main.stringG

android学习笔记——使用QuickContactBadge关联联系人

本文大部分内容来自<疯狂android讲义>. QuickContactBadge继承了ImageView,因此它的本质也是图片,也可以通过android:src属性指定它显示的图片.QuickcontactBadge额外增加的功能是:该图片可以关联到手机中指定联系人,当用户单击该图片时,系统将会打开相应联系人的联系方式界面. 为了让QuickContactBadge与特定联系人关联,可以调用如下方法进行关联. assignContactFromEmail(String emailAddres

Android编程之图片(异步)加载类

应某人之请,写一篇关于图片加载类.其实,网上有很多这样的类,而且比较推崇的是来自google中开源中的一篇.他写的比较好了,而且注意了内存优化,下面贴出它的图片下载类: [java] view plaincopy /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not 

Java for Android 基础笔记-数据类型

Java的基本数据类型 布尔类型 boolean true | false java是一个强类型的语言,与JS中的布尔类型的自由转换相比,JAVA的Boolean类型只有两种true和false,JS中相应的只可以自行转换. 字符类型 char 可以存储一个汉字,和其他语言非常类似不再赘述. 整型 byte, 占用一个字节 -128`-127 short 2字节 -2^15~2^15-1 int 4字节 -2^31~2^31-1 long 8字节  -2^63~2^63-1 整型常量默认为int