1. 程式人生 > >Android 使用VCard資料型別 非同步進行聯絡人備份與恢復操作

Android 使用VCard資料型別 非同步進行聯絡人備份與恢復操作

生活中常有人因為更換手機而丟失聯絡人資訊,又需要重新一個個去找親戚朋友獲取。
這樣的情況下,聯絡人備份與恢復功能就顯得非常實用。所以我學習了相關內容,並進行了適當整合,在這裡整理出來。

本篇部落格兩個重點

  1. 使用VCard庫進行聯絡人備份與恢復
  2. 非同步進行備份與恢復操作

為什麼要用VCard?

VCard是用於聯絡人資料儲存的標準資料格式,且有一套已經成熟的庫可以使用,通用於手機,也通用於郵件等,所以使用VCard進行聯絡人的資料儲存與備份格式是非常好的選擇。當然是用XML格式去儲存也是可以的。

下載android-vcard.jar請猛戳這裡

為什麼要非同步進行操作?

聯絡人備份與恢復都涉及到了資料庫讀寫,而資料庫的操作一向都是比較費時的,更何況是大量資料的情況下,所以將這些操作進行非同步處理是一個非常有必要的事情,否則容易造成UI主執行緒卡頓。非同步的另一個好處是同時能夠在UI介面上給使用者一些過程進度上的反饋,這個在重視產品互動體驗的今天是非常重要的。

一個非同步操作UI效果庫,源自GitHub請猛戳這裡

VCard本身庫比較複雜,不再多說,有興趣的可以研究原始碼。這裡貼上一個已經做過一層封裝的聯絡人處理類,直接整合呼叫即可。

封裝好的聯絡人處理類model

model的屬性包括基本的聯絡人姓名、電話、郵箱。其他屬性如果有需求可以自己新增。

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 + "}"; } }

封裝在ContactInfo內部的操作handler類

handler內部二次封裝了通過VCard庫對聯絡人操作的函式

/**
     * 聯絡人
     * 備份/還原操作
     *
     */
    public static class ContactHandler {

        private static ContactHandler instance_ = new ContactHandler();

        /**
         * 獲取例項
         */
        public static ContactHandler getInstance() {
            return instance_;
        }
     }

通過資料庫獲取手機中的聯絡人

第一步通過ContentResolver對所有聯絡人進行查詢,獲得Cursor
第二步將Cursor中的資料依次取出,構造成ContactInfo陣列,為第三步做準備

        /**
         * 獲取聯絡人指定資訊
         *
         * @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;
        }

將獲取到的聯絡人資料通過VCard資料格式備份

第三步在獲取到ContactInfo陣列後,將其寫入VCard檔案中

/**
         * 備份聯絡人
         */
        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檔案中的聯絡人資訊

反之如果要從VCard中恢復聯絡人資料的話,也先是將其轉化為ContactInfo陣列

/**
         * 獲取vCard檔案中的聯絡人資訊
         *
         * @return List<ContactInfo>
         */
        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;
        }

將從VCard中獲取的聯絡人資料插入到手機中

獲取到ContactInfo陣列後,再通過ContentResolver類將ContactInfo陣列依次插入到系統聯絡人資料庫中

/**
         * 向手機中錄入聯絡人資訊
         *
         * @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);
            }

        }

    }

非同步處理與功能使用DEMO

使用了基本的執行緒去實現非同步,通過Handler傳遞結果訊息,並更新按鈕狀態。

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完整程式碼方法

  1. 訪問我的GitHub進行下載,如果覺得有幫助,請點個贊,謝謝請猛戳這裡
  2. 通過CSDN下載站進行積分下載請猛戳這裡