Phonebook 导入SD上的.vcf联系人
2014-01-11 17:29:22
1. 当用户选择Phonebook中从SD卡导入联系人的操作后,程序回调转到ImportVCardActivity,然后用户选择好要导入的.vcf文件,并点击“确定”button,调用ImportVCardActivity中的importMultipleVCardFromExternalStorage()方法:
1 private void importMultipleVCardFromExternalStorage(2 final List<VCardFile> selectedVCardFileList) {3 mHandler.post(new Runnable() {4 public void run() {5 mVCardReadThread = new VCardReadThread(selectedVCardFileList);6 checkFinishingAndShowDialog(R.id.dialog_reading_vcard);7 }8 });9 }
先new一个VCardReadThread对象,然后在onCreateDialog()-->getReadingVCardDialog()中start这个Thread,如下:
1 private Dialog getReadingVCardDialog() { 2 if (mProgressDialogForReadVCard == null) { 3 String title = getString(R.string.spb_strings_import_all_txt); 4 String message = getString(R.string.reading_vcard_message); 5 mProgressDialogForReadVCard = new ProgressDialog(this); 6 mProgressDialogForReadVCard.setTitle(title); 7 mProgressDialogForReadVCard.setMessage(message); 8 mProgressDialogForReadVCard.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 9 mProgressDialogForReadVCard.setOnCancelListener(mVCardReadThread);10 mProgressDialogForReadVCard.setCanceledOnTouchOutside(false);11 mProgressDialogForReadVCard.setOnShowListener(new OnShowListener() {12 public void onShow(DialogInterface dialog) {13 if (mVCardReadThread != null) {14 mVCardReadThread.start();15 } else {16 SpbLog.e(LOG_TAG, "mVCardReadThread is null");17 dialog.dismiss();18 mProgressDialogForReadVCard = null;19 finish();20 }21 }22 });23 }24 return mProgressDialogForReadVCard;25 }
我们具体看VCardReadThread类的run()方法,以导入多个.vcf文件为例:
1 // Read multiple files. 2 List<VCardFile> verifiedVCardFileList = new ArrayList<VCardFile>(); 3 List<VCardSourceDetector> verifiedVCardFileDetector = new ArrayList<VCardSourceDetector>(); 4 int count = 0; 5 for (VCardFile vcardFile : mSelectedVCardFileList) { 6 if (mCanceled) { 7 return; 8 } 9 VCardEntryCounter counter = new VCardEntryCounter();10 VCardSourceDetector detector = new VCardSourceDetector();11 VCardBuilderCollection builderCollection = new VCardBuilderCollection(12 Arrays.asList(counter, detector));13 Uri uri = vcardFile.getUri();14 boolean result = false;15 try {16 result = readOneVCardFile(uri, VCardConfig.DEFAULT_CHARSET,17 builderCollection, null, true, null, null);18 }19 if (result) {20 count += counter.getCount();21 verifiedVCardFileList.add(vcardFile);22 verifiedVCardFileDetector.add(detector);23 }24 }25 mProgressDialogForReadVCard.setProgressNumberFormat(26 getString(R.string.spb_reading_contacts_txt));27 mProgressDialogForReadVCard.setMax(count);28 mProgressDialogForReadVCard.setProgress(0);29 mProgressDialogForReadVCard.setIndeterminate(false);30 31 int i = 0;32 for (VCardFile vcardFile : verifiedVCardFileList) {33 if (mCanceled) {34 return;35 }36 Uri uri = vcardFile.getUri();37 doActuallyReadOneVCard(uri, mAccount, true, verifiedVCardFileDetector.get(i),38 mErrorFileNameList);39 i++;40 }
有两个for循环,第一个是循环中获得了即将导入的联系人的总个数,因为这个信息要显示在ProgressBar进度条上,同时将vcardFile添加到verifiedVCardFileList中,这个List对象会在第二个循环中使用。再次我们先简单的看一下VCardFile是做什么的,代码如下:
1 class VCardFile { 2 private String mName; 3 private Uri mUri; 4 private long mLastModified; 5 6 public VCardFile(String name, Uri uri, long lastModified) { 7 mName = name; 8 mUri = uri; 9 mLastModified = lastModified;10 }11 12 public String getName() {13 return mName;14 }15 16 public Uri getUri() {17 return mUri;18 }19 20 public long getLastModified() {21 return mLastModified;22 }23 }
可以看到这个类的功能就是简单的封装.vcf File,包括对应的文件名称,uri以及最后一次修改时间。继续看第二个循环,其中调用了如下语句:
1 doActuallyReadOneVCard(uri, mAccount, true, verifiedVCardFileDetector.get(i),2 mErrorFileNameList);
传入了包含所有vcardFile的mErrorFileNameList, 帐号信息等,进入doActuallyReadOneVCard()方法:
1 private boolean doActuallyReadOneVCard(Uri uri, Account account, 2 boolean showEntryParseProgress, 3 VCardSourceDetector detector, List<String> errorFileNameList) { 4 final Context context = ImportVCardActivity.this; 5 mProgressShower = null; 6 if (showEntryParseProgress) { 7 mProgressShower = new ProgressShower(mProgressDialogForReadVCard, 8 context.getString(R.string.reading_vcard_message), 9 ImportVCardActivity.this, mHandler);10 mProgressShower.setListener(ImportVCardActivity.this);11 }12 mCommitter = new EntryCommitter(mResolver);13 String estimatedCharset = detector.getEstimatedCharset();14 VCardDataBuilder builder;15 16 // Use iso-8859-1 CHARSET to read VCard files, then use the charset17 // inside the VCard files to decode special strings.18 String targetCharset = estimatedCharset != null ? estimatedCharset : "utf-8";19 String sourceCharset = "iso-8859-1";20 if (try21) {21 // the targetCharset parameter (2nd one) will have no effect if22 // a CHARSET parameter is already provided in vcard. This is23 // the default value if nothing better can be found.24 builder = new VCardDataBuilder(sourceCharset, targetCharset, false, vcardType,25 mAccount, mAccountType);26 builder.addEntryHandler(mCommitter);27 if (mProgressShower != null) {28 builder.addEntryHandler(mProgressShower);29 }30 try {31 VCardParser parser = new VCardParser_V21(detector);32 parser.setBaseCharset(targetCharset);33 if (readOneVCardFile(uri, sourceCharset, builder, detector, false,34 parser, mErrorFileNameList)) {35 getTestData().fileImportedOK(uri);36 return true;37 } else {38 return false;39 }40 41 }42 }43 44 // NOT REACHED45 return false;46 }
我们的.vcf文件version是“VERSION:2.1”,targetCharset和sourceCharset使用来对.vcf文件进行编码的格式,这个方法中最重要的是调用了readOneVCardFile()方法,同时传入了VCardDataBuilder builder和VCardParser_V21 parser,这两个对象很重要,以后会重点用到。进入readOneVCardFile()方法,在这个方法中调用了callParser(parser, uri, charset, builder),如下:
1 private void callParser(VCardParser parser, Uri uri, String charset, 2 VCardBuilder builder) throws IOException, VCardException { 3 mVCardParser = parser; 4 5 if (mBrand == null) { 6 final String brand = Configuration.getInstance(ImportVCardActivity.this).getBrandName(); 7 mBrand = brand != null ? brand : ""; 8 } 9 mVCardParser.setBrand(mBrand);10 InputStream is = null;11 try {12 is = mResolver.openInputStream(uri);13 mVCardParser.parse(is, charset, builder, mCanceled);14 } finally {15 try {16 if (is != null) {17 is.close();18 }19 } catch (IOException e) {20 // Failed to close input stream - do nothing21 }22 }23 }
我们看到在这里将用流的方式访问.vcf文件,同时讲得到的流对象当作参数传给parse()方法,前面说过,mVCardParser是一个VCardParser_V21,所以进入VCardParser_V21看他的parse()方法,当然,他又调用了重载的parse(is, charset, builder),调用流程如下:
parse(is, charset, builder)-->parseVCardFile()-->parseOneVCard(firstReading)-->parseItems()-->parseItem(),如下:
1 protected boolean parseItem() throws IOException, VCardException { 2 mEncoding = sDefaultEncoding; 3 4 String line = getNonEmptyLine(); (1) 5 long start = System.currentTimeMillis(); 6 7 mParamCharsetTmp = null; 8 String[] propertyNameAndValue = separateLineAndHandleGroup(line); 9 if (propertyNameAndValue == null) {10 return true;11 }12 if (propertyNameAndValue.length != 2) {13 throw new VCardInvalidLineException("Invalid line /"" + line + "/"");14 }15 String propertyName = propertyNameAndValue[0].toUpperCase(); (2)16 String propertyValue = propertyNameAndValue[1];17 18 mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;19 20 if (propertyName.equals("ADR") || propertyName.equals("ORG") ||21 propertyName.equals("N")22 // "SOUND" is not multi-part on vCard specification but mere assumption.23 // Some Japanese phones use this as multi (DoCoMo spec).24 || propertyName.equals("SOUND")25 ) {26 start = System.currentTimeMillis();27 Log.d("D4", "propertyName = " + propertyName);28 Log.d("D4", "propertyValue = " + propertyValue);29 handleMultiplePropertyValue(propertyName, propertyValue); (3)30 mTimeParseAdrOrgN += System.currentTimeMillis() - start;31 return false;32 } else if (propertyName.equals("AGENT")) {33 handleAgent(propertyValue);34 return false;35 } else if (isValidPropertyName(propertyName)) {36 if (propertyName.equals("BEGIN")) {37 if (propertyValue.equals("VCARD")) {38 throw new VCardNestedException("This vCard has nested vCard data in it.");39 } else {40 throw new VCardException("Unknown BEGIN type: " + propertyValue);41 }42 } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {43 throw new VCardVersionException("Incompatible version: " +44 propertyValue + " != " + getVersion());45 }46 start = System.currentTimeMillis();47 handlePropertyValue(propertyName, propertyValue); (3)48 mTimeParsePropertyValues += System.currentTimeMillis() - start;49 return false;50 }51 52 throw new VCardNotSupportedException("Unknown property name: /"" +53 propertyName + "/"");54 }
首先看(1)的代码,从流文件中读取一行,所以可以发现其实就是一行一行读取并解析.vcf文件的。看(2)的代码,取出来propertyName,然后根据propertyName来获得propertyValue,下面是一段log,
1 D/D44444444444444444444444( 9909): propertyName = VERSION 2 D/D44444444444444444444444( 9909): propertyValue = 2.1 3 D/D42 ( 9909): propertyName = VERSION 4 D/D42 ( 9909): propertyValue = 2.1 5 D/D44444444444444444444444( 9909): propertyName = N 6 D/D44444444444444444444444( 9909): propertyValue = =E9=AD=8F=E4=BC=9F;;;; 7 D/D4 ( 9909): propertyName = N 8 D/D4 ( 9909): propertyValue = =E9=AD=8F=E4=BC=9F;;;; 9 D/D4 ( 9909): builder.toString().trim() = 10 D/D44444444444444444444444( 9909): propertyName = FN11 D/D44444444444444444444444( 9909): propertyValue = =E9=AD=8F=E4=BC=9F12 D/D42 ( 9909): propertyName = FN13 D/D42 ( 9909): propertyValue = =E9=AD=8F=E4=BC=9F14 D/D44444444444444444444444( 9909): propertyName = TEL15 D/D44444444444444444444444( 9909): propertyValue = 1861197664216 D/D42 ( 9909): propertyName = TEL17 D/D42 ( 9909): propertyValue = 18611976642
而一个典型的.vcf文件,这个文件中只包含一个联系人,名字是**,号码是18611976642,如下:
1 BEGIN:VCARD2 VERSION:2.13 N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E9=AD=8F=E4=BC=9F;;;;4 FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E9=AD=8F=E4=BC=9F5 TEL;HOME;VOICE:186119766426 END:VCARD
这样就取到了联系人包含的所有信息。
在每一个判断语句块里都有一个方法,看(3)的代码,这两个方法最终都会调用mBuilder.propertyValues(v)将propertyValue值所在的list存起来,因此最终的解析工作也是在propertyValues()方法里完成的。下面再看一个更复杂的.vcf文件:
BEGIN:VCARDVERSION:2.1N;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E6=9D=A8=E9=A3=9E=E5=B9=B4;;;;FN;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E6=9D=A8=E9=A3=9E=E5=B9=B4TEL;HOME;VOICE:18896784536TEL;CELL:9999999999EMAIL;HOME:yangling@gmail.comPHOTO;ENCODING=BASE64;TYPE=JPEG:/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCA gMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGB IUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQ UFBQUFBQUFBQUFBQUFBQUFBT/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxF DKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWm NkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMX Gx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAA AAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIF EKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZG VmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcb HyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD4oB44IakBwCBw MUoAHbr2xSbSST2PtQrGauSwEkjsBxzXuH7J+jHV/jBojADy7MS3TcZwVRtpP/AyteHwKd+Tz +HSvqr9hzQnn8QeJNW8pnNpaR268f8APRy//tGmtNxPyPtvWdcTVoYooyVjg4ZiOoKjkfnXE6 LeJqGpOS2wiNAEb+NnzKy89cBh+XtWreSXU9hLIICrkGMDgZBJUH8gprD0y3ksbm6M4QHdIyN 5gHfanGeygVsrWMWdt5q6V4e8qJyZ5XUgZ5+dy5z+Ax+FZkLtAV+YZBzkewNYQv1lupnmuYEM TIAPNGGCocHr/tGlvtfsYkk/4mVsr7QP9Z9T2qlYV2UbgpqOrvIrAr978gQv67qZqE5kW3izn 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 U8rBp0QR7xyWAHGfw4rVU3shcyP/Z
这个联系人信息如下:
可以发现,中文姓名和头像是进行了编码的,其他比如phone number,email等直接文本保存,那么我们进入propertyValues()方法看一下姓名和头像是怎么解析的,前面说过VCardDataBuilder mBuilder很重要,那么当然propertyValues()方法也是在VCardDataBuilder里面喽,代码如下:
1 public void propertyValues(List<String> values) { 2 if (values == null || values.size() == 0) { 3 return; 4 } 5 6 final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET"); 7 String charset = 8 ((charsetCollection != null) ? charsetCollection.iterator().next() : null); 9 String targetCharset = CharsetUtils.nameForDefaultVendor(charset);10 11 final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");12 String encoding =13 ((encodingCollection != null) ? encodingCollection.iterator().next() : null);14 15 if (targetCharset == null || targetCharset.length() == 0) {16 targetCharset = mTargetCharset;17 }18 19 if (!Charset.isSupported(targetCharset)20 && (("shift_jis").equalsIgnoreCase(charset)21 || "shift-jis".equalsIgnoreCase(charset)22 || "sjis".equalsIgnoreCase(charset))) {23 Log.w(LOG_TAG, targetCharset + " is not supported. Use shif_jis encoding.");24 targetCharset = "shift_jis";25 }26 27 for (String value : values) {28 Log.d("D2D", "value = " + value);29 Log.d("D2D", "handleOneValue(value, targetCharset, encoding) = " + handleOneValue(value, targetCharset, encoding));30 mCurrentProperty.addToPropertyValueList(31 handleOneValue(value, targetCharset, encoding));32 }33 }
以杨飞年为例,导入只包含他的信息的.vcf文件,看log如下:
D/D2D (11785): value = 2.1D/D2D (11785): handleOneValue(value, targetCharset, encoding) = 2.1D/D2D (11785): value = =E6=9D=A8=E9=A3=9E=E5=B9=B4D/D2D (11785): handleOneValue(value, targetCharset, encoding) = 杨飞年D/D2D (11785): value = D/D2D (11785): handleOneValue(value, targetCharset, encoding) = D/D2D (11785): value = D/D2D (11785): handleOneValue(value, targetCharset, encoding) = D/D2D (11785): value = D/D2D (11785): handleOneValue(value, targetCharset, encoding) = D/D2D (11785): value = D/D2D (11785): handleOneValue(value, targetCharset, encoding) = D/D2D (11785): value = =E6=9D=A8=E9=A3=9E=E5=B9=B4D/D2D (11785): handleOneValue(value, targetCharset, encoding) = 杨飞年D/D2D (11785): value = 18896784536D/D2D (11785): handleOneValue(value, targetCharset, encoding) = 18896784536D/D2D (11785): value = 9999999999D/D2D (11785): handleOneValue(value, targetCharset, encoding) = 9999999999D/D2D (11785): value = yangling@gmail.comD/D2D (11785): handleOneValue(value, targetCharset, encoding) = yangling@gmail.comD/D2D (11785): value = /9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAIBAQEBA QIBAQECAgICAgQDAgICAgUEBAMEBgUGBgYFBgYGBwkIBgcJBwYGCAsICQoKCgoKBggLDAsKDA kKCgr/2wBDAQICAgICAgUDAwUKBwYHCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo KCgoKCgoKCgoKCgoKCgoKCgr/wAARCABgAGADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxF DKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWm NkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMX Gx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAA AAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIF EKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZG VmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcb HyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8u45RswhV6bHL hWjjGF2etLHDGBkL97+HZSLBIWZweG/2KmHszCPP0J7LcSMHaqfL81fU/wDwSb8GP4v/AGyPC 88aL5OjpdajL8m7a0cMnls3/bZo6+WLGN1lLOuf+A/dr7//AOCGfgS6v/iF42+If9myStpGiW 9jF8v/AD8TNN/7aVcPd+ImWvwn6keM/HNr4tsoNPsXZIbD5ZZGX7ytGvzL/wB9V5f4G1m18Q+ JZWkm8pltYVSGT/ltJNuupI/m+9tWRf8Avn/Zrf1mfXr3w/cXselMkjK0CL8q7lZmjVv++Vja uW8K6beaFqV/LqyQg+bcPBJ9qVf4vLh+Xd/DGq10R5eU5JW1PT/tMPhT4dHT7C5Zru6uI2Rd3 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 H40+IN5q0dnPpOhyfatJZPlv
将“=E6=9D=A8=E9=A3=9E=E5=B9=B4”解析成了“杨飞年”,至于头像,好像还是一堆编码,继续看。
调用了mCurrentProperty.addToPropertyValueList()方法,代码在ContactStruct.java,如下:
1 public void addToPropertyValueList(String propertyValue) {2 // Trim trailing nul-chars, could be present if the value3 // originally originated from a SIM.4 propertyValue = StringUtil.trimTrailingNul(propertyValue);5 6 if (propertyValue != null) {7 mPropertyValueList.add(propertyValue.replace("/r/n", "/n"));8 }9 }
很简单,只是将传进来的propertyValue保存到List<String> mPropertyValueList对象中,到此,我们发现所有联系人相关的信息其实最后都会保存在mPropertyValueList中,也就是说,将以个联系人信息从.vcf文件中读入、解析之后保存到这里,那么最后一个问题就是什么时候将解析后的联系人信息保存到数据库的呢?
还记得我们前面有提到这样的调用流程:
parse(is, charset, builder)-->parseVCardFile()-->parseOneVCard(firstReading)-->parseItems()-->parseItem()
其中调用了parseOneVCard(firstReading)方法,代码如下:
1 private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException { 2 boolean allowGarbage = false; 3 if (firstReading) { 4 if (mNestCount > 0) { 5 for (int i = 0; i < mNestCount; i++) { 6 if (!readBeginVCard(allowGarbage)) { 7 return false; 8 } 9 allowGarbage = true;10 }11 }12 }13 14 // Allow garbage lines before "BEGIN:VCARD" at any time.15 // Some of the vCard from KDDI Phone contains garbage lines16 // between 'END:VCARD' and next 'BEGIN:VCARD'.17 if (!readBeginVCard(true)) {18 mBuilder.close();19 return false;20 }21 long start;22 if (mBuilder != null) {23 start = System.currentTimeMillis();24 mBuilder.startRecord("VCARD");25 mTimeReadStartRecord += System.currentTimeMillis() - start;26 }27 start = System.currentTimeMillis();28 parseItems();29 mTimeParseItems += System.currentTimeMillis() - start;30 readEndVCard(true, false);31 if (mBuilder != null) {32 start = System.currentTimeMillis();33 mBuilder.endRecord();34 mTimeReadEndRecord += System.currentTimeMillis() - start;35 }36 return true;37 }
其中不光调用了parseItems(),还调用了mBuilder.endRecord(),也就是VCardDataBuilder.endRecord(),代码如下:
1 public void endRecord() {2 mCurrentContactStruct.consolidateFields();3 4 for (EntryHandler entryHandler : mEntryHandlers) {5 entryHandler.onEntryCreated(mCurrentContactStruct);6 }7 mCurrentContactStruct.clear();8 }
调用了entryHandler.onEntryCreated(mCurrentContactStruct),代码在EntryCommitter.java,如下:
1 public Uri onEntryCreated(final ContactStruct contactStruct) {2 long start = System.currentTimeMillis();3 if (contactStruct != null) {4 mRawContactUri = contactStruct.pushIntoContentResolver(mContentResolver);5 }6 mTimeToCommit += System.currentTimeMillis() - start;7 return mRawContactUri;8 }
EntryCommitter类我们在前面doActuallyReadOneVCard()方法中见过。其又调用了ontactStruct.pushIntoContentResolver(mContentResolver),这一下子又跑到ContactStruct.pushIntoContentResolver()方法,代码部分如下:
1 public Uri pushIntoContentResolver(ContentResolver resolver) { 2 if (mBuilder == null) { 3 mBuilder = new ContactOperationBuilder(resolver); 4 } 5 mBuilder.openSession(); 6 mBuilder.newInsert(RawContacts.CONTENT_URI); 7 8 String myGroupsId = null; 9 String myGroupsRowId = null;10 boolean hasValue = false;11 12 if (mAccount != null) {13 mBuilder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);14 mBuilder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);15 16 // Assume that caller side creates this group if it does not exist.17 // TODO: refactor this code along with the change in GoogleSource.java18 if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {19 Cursor cursor = null;20 try {21 cursor = resolver.query(Groups.CONTENT_URI,22 new String[] {Groups.SOURCE_ID, Groups._ID },23 Groups.TITLE + "=?"24 + " and " + Groups.ACCOUNT_TYPE + "=?"25 + " and " + Groups.ACCOUNT_NAME + "=?",26 new String[] {GoogleAccountType.GOOGLE_MY_CONTACTS_GROUP,27 mAccount.type, mAccount.name}, null);28 if (cursor != null && cursor.moveToFirst()) {29 myGroupsId = cursor.getString(0);30 myGroupsRowId = cursor.getString(1);31 }32 } finally {33 if (cursor != null) {34 cursor.close();35 }36 }37 }38 } else {39 String account = null;40 mBuilder.withValue(RawContacts.ACCOUNT_NAME, account);41 mBuilder.withValue(RawContacts.ACCOUNT_TYPE, account);42 43 }44 mBuilder.build(null, true);45 46 // In case that one contact could be committed in different batch47 // If last commit fail, mDiscardRest set true to get rid of rest of48 // the contact info.49 50 if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName)51 && TextUtils.isEmpty(mPhoneticFamilyName) && TextUtils.isEmpty(mPhoneticGivenName) && TextUtils52 .isEmpty(mFullName))) {53 mBuilder.newInsert(Data.CONTENT_URI);54 mBuilder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);55 56 mBuilder.withValue(StructuredName.GIVEN_NAME, mGivenName);57 mBuilder.withValue(StructuredName.FAMILY_NAME, mFamilyName);58 mBuilder.withValue(StructuredName.MIDDLE_NAME, mMiddleName);59 mBuilder.withValue(StructuredName.PREFIX, mPrefix);60 mBuilder.withValue(StructuredName.SUFFIX, mSuffix);61 62 mBuilder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);63 mBuilder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);64 mBuilder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);65 66 mBuilder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());67 mBuilder.build(StructuredName.RAW_CONTACT_ID, false);68 hasValue = true;69 }70 71 if (mNickNameList != null && mNickNameList.size() > 0) {72 boolean first = true;73 for (String nickName : mNickNameList) {74 mBuilder.newInsert(Data.CONTENT_URI);75 mBuilder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);76 77 mBuilder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);78 mBuilder.withValue(Nickname.NAME, nickName);79 if (first) {80 mBuilder.withValue(Data.IS_PRIMARY, 1);81 first = false;82 }83 mBuilder.build(Nickname.RAW_CONTACT_ID, false);84 hasValue = true;85 }86 }87 ......
看到了吧,在组装ContactOperationBuilder,这是要保存联系人到数据库的节奏啊,果然,继续看mBuilder.build(***)方法,如下:
1 public void build(String key, boolean openAccount) { 2 if (mDiscardRest) { 3 return; 4 } 5 if (mWrapBuilder == null) { 6 return; 7 } 8 9 // if current operation oversize abandon it10 if (mCurrentOperationSizeInByte >= MAX_OPERATION_SIZE) {11 mDiscardRest = true;12 mCurrentOperationSizeInByte = 0;13 return;14 }15 16 avoidOperationOverFlow();17 18 if (key != null) {19 withValueBackReference(key);20 }21 22 ContentProviderOperation operation = mWrapBuilder.build();23 if (!openAccount) {24 mValidDataCommit = true;25 mOperationList.add(operation);26 }27 if (mOperationList.size() >= COMMIT_GATE) {28 Uri uri = apply(); (1)29 30 if (uri != null) {31 mRawContactId = ContentUris.parseId(uri);32 }33 34 }35 if (openAccount) {36 mOperationList.add(operation);37 }38 39 mOperationSizeInByte += mCurrentOperationSizeInByte;40 mCurrentOperationSizeInByte = 0;41 }42 43 public Uri apply() {44 ContentProviderResult[] cpr = null;45 try {46 if (mOperationList != null && mOperationList.size() > 0) {47 cpr = mResolver.applyBatch(ContactsContract.AUTHORITY, mOperationList); (2)48 for (ContentProviderOperation cpo : mOperationList) {49 Log.d("D3", "cpo.toString = " + cpo.toString());50 }51 }52 53 }54 55 if (mOperationList != null) {56 mOperationList.clear();57 }58 mOperationListSizeAtOpenSession = 0;59 60 Uri uri = null;61 // if cpr.length > mCurrentContactRecord means last62 // applyBatch commit whole contacts and parts.63 if (cpr != null) {64 if (cpr.length > mCurrentContactRecord) {65 uri = cpr[mCurrentContactRecord].uri;66 mContactAppendum = true;67 } else if (cpr.length == mCurrentContactRecord) {68 // return first contact uri in last apply69 uri = cpr[0].uri;70 }71 }72 mCurrentContactRecord = 0;73 74 return uri;75 }
在build()方法(1)代码处,满足条件后会调用apply()方法,看apply()中(2)的方法,保存了吧。
呵呵,Phonebook导入.vcf的过程分析至此结束,至于是如何解码中文姓名和头像的,会专门写文章研究这个问题的,因为要解码肯定得先编码,是不?怎还得先搞清楚到底是如何编码的,才能弄明白要如何解码。