Android Phonebook编写联系人UI加载及联系人保存流程(六)
2014-01-07 11:18:08 将百度空间里的东西移过来。
1. Save contact
我们前面已经写了四篇文章,做了大量的铺垫,总算到了这一步,见证奇迹的时刻终于到了。

用户添加了所有需要添加的信息后,点击“Done”来保存新建好的联系人,我们就从用户点击“Done”Button开始分析。
前面提到过,“Done”的处理事件是在ContactEditorActivity里面设置的,如下:
View saveMenuItem = customActionBarView.findViewById(R.id.save_menu_item);
saveMenuItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mFragment.doSaveAction();
}
});
我们进入ContactEditorFragment,看他的调用逻辑,doSaveAction()-->save(SaveMode.CLOSE),重点看save()方法:
public boolean save(int saveMode) {
...
// Save contact
Intent intent = ContactSaveService.createSaveContactIntent(
mContext, mState,
SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
getActivity().getClass(),
ContactEditorActivity.ACTION_SAVE_COMPLETED,
mUpdatedPhotos, mPbrIndex);
mContext.startService(intent);
}
可以看到,我们千里追踪的mState被当作参数,和其他对象一样,进入了ContactSaveService,这个ContactSaveService继承自IntentService,进入ContactSaveService之后调用了一个回调方法,如下:
@Override
protected void onHandleIntent(Intent intent) {
String action = intent.getAction();
if (ACTION_SAVE_CONTACT.equals(action)) {
saveContact(intent);
CallerInfoCacheUtils.sendUpdateCallerInfoCacheIntent(this);
}
看saveContact(intent),这个方法就是保存联系人的终极方法:
private void saveContact(Intent intent) {
RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
final boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false);
Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS);
final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
String sourceId = null;
boolean isNewAdnContact = false;
RawContactDelta entityDelta = state.get(0);
final AccountType type = entityDelta.getAccountType(accountTypes);
RawContactModifier.trimEmpty(state, accountTypes, isProfile);
Uri lookupUri = null;
final ContentResolver resolver = getContentResolver();
boolean succeeded = false;
long insertedRawContactId = -1;
int tries = 0;
while (tries++ < PERSIST_TRIES) {
final ArrayList<ContentProviderOperation> diff = state.buildDiff();
ContentProviderResult[] results = null;
if (!diff.isEmpty()) {
results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
}
final long rawContactId = getRawContactId(state, diff, results);
if (rawContactId == -1) {
}
insertedRawContactId = getInsertedRawContactId(diff, results);
final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
lookupUri = RawContacts.getContactLookupUri(resolver, rawContactUri);
succeeded = true;
if (isAdnContact && isNewAdnContact && lookupUri != null) {
long contactId = ContentUris.parseId(lookupUri);
long newRawContactId = getRawContactId(state, diff, results);
AdnContactsCollector.updateAdnContactsNow(
contactId, newRawContactId,
entityDelta.getAccountType(),
entityDelta.getAccountName(), sourceId);
}
break;
}
reportSaveStatus(intent, lookupUri, succeeded);
}
上面的方法并非是完整的方法,我只是截取了重点代码,看while循环,说明会try保存联系人三次,而不是一次。我们前面一致关注的state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);同时还从intent中取出了账户信息等,而最关键的代码实在while循环里面,首先看final ArrayList<ContentProviderOperation> diff = state.buildDiff();我们知道,保存联系人时一般会把所有的信息封装到ContentProviderOperation里面(可以参考http://www.cnblogs.com/wlrhnh/p/3477216.html 和 http://www.cnblogs.com/wlrhnh/p/3477252.html),然后执行resolver.applyBatch,那么现在的问题就是如何封装ContentProviderOperation了,我们知道state是RawContactDeltaList,而且根据前面的分析,它里面添加了一个RawContactDelta对象,下面我们进入RawContactDeltaList的state.buildDiff()方法:
public ArrayList<ContentProviderOperation> buildDiff() {
final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
for (RawContactDelta delta : this) {
final int firstBatch = diff.size();
final boolean isInsert = delta.isContactInsert();
backRefs[rawContactIndex++] = isInsert ? firstBatch : -1;
delta.buildDiff(diff);
if (mIsUnlinkingRawContact) continue;
if (rawContactId != -1) {
final Builder builder = beginKeepTogether();
builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
diff.add(builder.build());
} else if (firstInsertRow == -1) {
firstInsertRow = firstBatch;
} else {
final Builder builder = beginKeepTogether();
builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, firstInsertRow);
builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch);
diff.add(builder.build());
}
}
return diff;
}
首先for循环中取出了RawContactDelta对象,一般情况下只有一个,然后调用delta.buildDiff(diff),看来还得进入RawContactDelta的buildDiff()方法,此处传入的diff是一个ArrayList<ContentProviderOperation>对象:
for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
for (ValuesDelta child : mimeEntries) {
if (mContactsQueryUri.equals(Profile.CONTENT_RAW_CONTACTS_URI)) {
builder = child.buildDiff(Uri.withAppendedPath(Profile.CONTENT_URI, RawContacts.Data.CONTENT_DIRECTORY));
} else {
builder = child.buildDiff(Data.CONTENT_URI);
}
}
}
看外层的for循环,mEntries是HashMap<String, ArrayList<ValuesDelta>>对象,应该还记得前文中分析的saveValue(String column, String value)方法吧,如下:
protected void saveValue(String column, String value) {
Log.d("David", "column = " + column);
Log.d("David", "value = " + value);
mEntry.put(column, value);
Log.d("David", "mState = " + mState);
}
其中mEntry是ValuesDelta对象,而且有ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)在KindSectionView.java。
继续分析内层循环,取出每一个ValuesDelta对象,然后调用child.buildDiff(Data.CONTENT_URI),进入ValuesDelta:
public ContentProviderOperation.Builder buildDiff(Uri targetUri) {
Builder builder = null;
if (isInsert()) {
mAfter.remove(mIdColumn);
builder = ContentProviderOperation.newInsert(targetUri);
builder.withValues(mAfter);
}
return builder;
}
好了,我们看这几个循环,我在每一循环下面都打了log:
RawContactDeltaList: for (RawContactDelta delta : this)
Log.d("D1", "delta = " + delta); RawContactDelta: for (ArrayList<ValuesDelta> mimeEntries : mEntries.values())
Log.d("D1", "===================================");
Log.d("D1", "mimeEntries = " + mimeEntries); RawContactDelta: for (ValuesDelta child : mimeEntries)
Log.d("D1", "child = " + child); ValuesDelta: buildDiff(Uri targetUri)
Log.d("D1", "mAfter = " + mAfter);
当我输入Name=Lucky, PhoneNumber=18611112222,然后点击保存联系人时,log结果如下:

RawContactDelta只有一个,包含所有用户输入的信息,可见RawContactDelta是一个包含所有联系人信息的对象;
每一个RawContactDelta都包含好几个ArrayList<ValuesDelta>,其实每一个ArrayList只有一个对象ValuesDelta;那么一个ValuesDelta包含一个Item的信息,如Name, Phone, Email。然后每一个ValuesDelta做buildDiff()操作,builder.withValues(mAfter),mAfter是一个ContentValues对象,打印结果如上图mAfter所示。
好了,现在返回到最开始的位置,ContactSaveService.java,saveContact()方法,有
final ArrayList<ContentProviderOperation> diff = state.buildDiff(); ContentProviderResult[] results = null;
if (!diff.isEmpty()) {
results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
}
resolver.applyBatch操作,那我们看看最终返回的diff是什么?
for (ContentProviderOperation d : diff) {
Log.d("D2", "ContentProviderOperation = " + d);
}
results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
log截图如下:

可以看到每一个ContentProviderOperation中都附带相应的值,而这些值会被resolver.applyBatch()方法保存在数据库里。
至此,新建联系人UI和保存联系人分析结束。
Android Phonebook编写联系人UI加载及联系人保存流程(六)的更多相关文章
- Android Phonebook编写联系人UI加载及联系人保存流程(一)
2014-01-06 17:05:11 将百度空间里的东西移过来. 本文适合ROM定制做Phonebook的童鞋看,其他人飘过即可- Phonebook添加/编辑联系人UI加载及保存联系人流程,是一系 ...
- Android Phonebook编写联系人UI加载及联系人保存流程(三)
2014-01-07 09:54:13 将百度空间里的东西移过来. 本文从点击“添加联系人”Button开始,分析新建联系人页面UI是如何加载,以及新的联系人信息是如何保存的,借此,我们一探Phon ...
- Android Phonebook编写联系人UI加载及联系人保存流程(五)
2014-01-07 10:46:30 将百度空间里的东西移过来. 在前面的文章中我们分析了UI的加载,其中提到了一个重要的对象:RawContactDeltaList mState,我前面说过这个对 ...
- Android Phonebook编写联系人UI加载及联系人保存流程(二)
2014-01-06 17:18:29 1. Phonebook中新建/编辑联系人的UI不是用xml文件写的,它是随着帐号类型的改变来加载不同的UI,比如SIM联系人,只有Name.Phone Num ...
- Android Phonebook编写联系人UI加载及联系人保存流程(四)
2014-01-07 10:23:22 将百度空间里的东西移过来. 5. KindSectionView KindSectionView是何方神圣呢?它又是怎么怎么和一个DataKind,以及一个Ra ...
- Android ViewPager Fragment使用懒加载提升性能
Android ViewPager Fragment使用懒加载提升性能 Fragment在如今的Android开发中越来越普遍,但是当ViewPager结合Fragment时候,由于Androi ...
- Android三种基本的加载网络图片方式(转)
Android三种基本的加载网络图片方式,包括普通加载网络方式.用ImageLoader加载图片.用Volley加载图片. 1. [代码]普通加载网络方式 ? 1 2 3 4 5 6 7 8 9 10 ...
- Android开发中如何解决加载大图片时内存溢出的问题
Android开发中如何解决加载大图片时内存溢出的问题 在Android开发过程中,我们经常会遇到加载的图片过大导致内存溢出的问题,其实类似这样的问题已经屡见不鲜了,下面将一些好的解决方案分享给 ...
- Android引入高速缓存的异步加载全分辨率
Android引进高速缓存的异步加载全分辨率 为什么要缓存 通过图像缩放,我们这样做是对的异步加载优化的大图,但现在的App这不仅是一款高清大图.图.动不动就是图文混排.以图代文,假设这些图片都载入到 ...
随机推荐
- 第一天……
Hello,大家好! 不对,这个开头有点不对.我不应该这么说,这个博客我是为我自己而开通的.目的很简单,好记兴不如烂笔头,把每天所学所感写下来. 我想作一个有条理的人,一个有计划的人,一个有效率的人. ...
- C++—函数
一.函数的基本知识 要使用C++函数,必须完成一下工作: (1)提供函数定义: (2)提供函数原型: (3)调用函数. 1.定义函数 可以将函数分为两类,有返回值的函数和没有返回值的函数.没有返回值的 ...
- Java Date与SimpleDateFormat
最近在弄一些涉及到时间处理的项目.本来自己写了一个时间转换函数,虽然能用但是过于麻烦而且不够规范,于是学习了下java自带的时间处理的类. public class Timechg { public ...
- Subversion中文手册(svnbook) TortoiseSVN中文帮助手册
文档地址 http://svndoc.iusesvn.com/
- aliyun CentOS6.5 上 svn 安装笔记
为了方便管理自己的一些学习资料.总结等,在CentOS6.5上安装SVN,记录过程如下: 1.1 安装方式 独立服务器 fsfs SVN服务器有2种运行方式:独立安装.SVN+Apache.独立 ...
- 【转】Android应用开发之PNG、IconFont、SVG图标资源优化详解
1 背景 最近因为一些个人私事导致好久没写博客了,多事之年总算要过去了,突然没了动力,所以赶紧先拿个最近项目中重构的一个小知识点充下数,老题重谈. 在我们App开发中大家可能都会有过如下痛疾(程序员和 ...
- Unity5 新功能解析--GI(全局光)
http://blog.csdn.net/leonwei/article/details/48009059 Unity5带来的最大的改变就是全新的GI,在烘焙引擎上抛弃了4的beast,使用了虚幻采用 ...
- Mybatis学习(壹)
一.Mybatis的引言 1.Mybatis框架概念:是数据库持久层的框架,对数据库的访问和操作.Mybatis对JDBC的封装,Mybatis替换JDBC开发,解决DAO中的通用问题. 2.JDBC ...
- 关于WorkFlow的使用以及例子
近期做项目,项目需要用到工作流方面的技术,我在这里与大家分享一个workFlow学习的地址,共大家学习. http://www.cnblogs.com/foundation/ 各文档的说明: F资料├ ...
- HTML5自学笔记[ 23 ]canvas绘图基础7
变换矩阵: transform(a,b,c,d,e,f),多次使用该函数,效果是多次状态改变的累加: setTransform(a,b,c,d,e,f),会忽略之前的变换,从起始状态开始改变.