1.3 和CallLogAdapter交互
Fragment和Adapter交互的架构如下,
监听器Listener是CallLogQueryHandler的内部接口,仅有2个方法,
public interface Listener {
/** Called when {@link CallLogQueryHandler#fetchVoicemailStatus()} completes. */
void onVoicemailStatusFetched(Cursor statusCursor);
/**
* Called when {@link CallLogQueryHandler#fetchCalls(int)} complete.
* Returns true if takes ownership of cursor.
*/
boolean onCallsFetched(Cursor combinedCursor);
}
mListener是一个WeakReference组,
private final WeakReference<Listener> mListener;
在CallLogQueryHandler的构造方法中初始化,
public CallLogQueryHandler(Context context, ContentResolver contentResolver, Listener listener,
int limit) {
super(contentResolver);
mContext = context.getApplicationContext();
mListener = new WeakReference<Listener>(listener);
mLogLimit = limit;
}
CallLogFragment
CallLogFragment的onCallsFetched方法主要逻辑如下,
mAdapter.changeCursor(cursor);
mAdapter 是CallLogAdapter对象,。
CallLogSearchFragment
CallLogSearchFragment是直接调用父类CallLogFragment的onCallsFetched方法。
MSimCallLogFragment
MsimCallLogFragment的onCallsFetched方法和CallLogFragment的onCallsFetched方法几乎完全相同,主要逻辑如下,
mAdapter.changeCursor(cursor);
CallLogAdapter继承于GroupingListAdapter, GroupingListAdapter定义如下,
abstract class GroupingListAdapter extends RecyclerView.Adapter {
其中, GroupingListAdapter实现了RecyclerView.Adapter 的getItemCount方法,
CallLogAdapter实现了RecyclerView.Adapter的onCreateViewHolder/ onBindViewHolder方法。
GroupingListAdapter的changeCursor方法调用流程图如下,
GroupingListAdapter的changeCursor方法主要逻辑如下,
1,取消注册,此时查询完成,需要在更新完界面之后才能受数据库的影响,
mCursor.unregisterContentObserver(mChangeObserver);
mCursor.unregisterDataSetObserver(mDataSetObserver);
mCursor.close();
2,为mCursor变量赋值,
mCursor = cursor;
3,调用findGroups方法对查询到的通话记录分组,
findGroups();
4,更新界面,重新注册数据库,
cursor.registerContentObserver(mChangeObserver);
cursor.registerDataSetObserver(mDataSetObserver);
mRowIdColumnIndex = cursor.getColumnIndexOrThrow("_id");
notifyDataSetChanged();
notifyDataSetChanged原理在此就不论述了,总之会调用getItemCount/onCreateViewHolder/ onBindViewHolder方法更新界面。
看通话记录界面,可以看到:
1,通话记录分为三类:今天,昨天,更早。如何分类的?
2,相邻的同一号码为一组显示。如何做到的?
查询完之后,会做两件事情,分组(分为今天,昨天以及更早),相邻的相同号码的通话记录分为一组。两件事情在两个不同的类中进行,但是同时进行。
CallLogAdapter的addGroups方法如下,
mCallLogGroupBuilder.addGroups(cursor);
mCallLogGroupBuilder是CallLogGroupBuilder对象,在CallLogAdapter的构造方法中初始化,
mCallLogGroupBuilder = new CallLogGroupBuilder(this);
CallLogGroupBuilder的addGroups方法主要逻辑如下,
1,首先清除上次查询的分组,并获取系统的当前时间,
mGroupCreator.clearDayGroups();
long currentTime = System.currentTimeMillis();
2,获取查询的第一条通话记录信息,
cursor.moveToFirst();
String firstNumber = cursor.getString(CallLogQuery.NUMBER);//通话号码
int firstCallType = cursor.getInt(CallLogQuery.CALL_TYPE); //通话类型
•••
final long firstDate = cursor.getLong(CallLogQuery.DATE);//通话时间
final long firstRowId = cursor.getLong(CallLogQuery.ID);
3,调用getDayGroup方法获取分组,然后调用CallLogAdapter的setDayGroup方法设置分组,
int currentGroupDayGroup = getDayGroup(firstDate, currentTime);
mGroupCreator.setDayGroup(firstRowId, currentGroupDayGroup);
getDayGroup 方法如下,
int days = DateUtils.getDayDifference(TIME, date, now);
if (days == 0) {
return DAY_GROUP_TODAY;
} else if (days == 1) {
return DAY_GROUP_YESTERDAY;
} else {
return DAY_GROUP_OTHER;
}
将通话记录时间和当前的系统时间对比,按照时间信息分为3组,
时间小于一天的为一组,返回值为0;
时间超过一天但是未超过2天的为一组,返回值为1;
时间超过2天的为一组,返回值为2;
4,利用cursor信息进行逐个分组,
while (cursor.moveToNext()) {
分组的代码就不贴出来了,例如现在 有8条通话记录,按照情况分别如下,
A, 8条通话记录分别对应8个不同的号码,3条是今天的通话,2条是昨天的通话,3条是以前的通话。
首先利用通话信息调用getDayGroup方法获取时间分组,
currentGroupDayGroup = getDayGroup(date, currentTime);
然后调用CallLogAdapter的setDayGroup方法保存时间分组信息,
mGroupCreator.setDayGroup(currentCallId, currentGroupDayGroup);
CallLogAdapter的setDayGroup方法如下,
if (!mDayGroups.containsKey(rowId)) {
mDayGroups.put(rowId, dayGroup);
}
将每条通话记录的时间分组信息保存在mDayGroups变量中, mDayGroups定义如下,
private HashMap<Long,Integer> mDayGroups = new HashMap<Long, Integer>();
因此, mDayGroups对应保存的信息为(0,0), (1,0), (2,0), (3,1), (4,1), (5,2), (6,2), (7,2),
B, 8条通话记录分别对应8个完全相同的号码,3条是今天的通话,2条是昨天的通话,3条是以前的通话。
如果号码相同的话,就可以进行合并,
currentGroupSize++; //初始值为1
然后调用CallLogAdapter的setDayGroup方法保存时间分组信息,
mGroupCreator.setDayGroup(currentCallId, currentGroupDayGroup);
最后调用addGroup方法记录合并信息,
addGroup(count - currentGroupSize, currentGroupSize);//参数为(0,8)
CallLogGroupBuilder的addGroup方法调用流程图如下,
CallLogGroupBuilder的addGroup方法如下,
mGroupCreator.addGroup(cursorPosition, size, false);
调用CallLogAdapter的addGroup方法保存分组信息。CallLogAdapter有一个addGroup方法,也有一个addGroups方法,容易混淆。
CallLogAdapter对应的addGroup方法如下,
super.addGroup(cursorPosition, size, expanded);
直接调用父类GroupingListAdapter的addGroup方法,在分析之前,首先看一些变量,
mGroupCount 是int类型的,是通话记录分组的数目,
mGroupMetadata是long型的数组,长度为64位,
private long[] mGroupMetadata;
在findGroups方法中,也就是查询通话记录之后,初始化如下,
mGroupCount = 0; // mGroupCount 初始值为0
mGroupMetadata = new long[GROUP_METADATA_ARRAY_INITIAL_SIZE];
mGroupMetadata数组的初始大小为16
GroupingListAdapter的addGroup方法逻辑如下,
1,如果分组大小大于16,则将分组大小增加到128,
if (mGroupCount >= mGroupMetadata.length) {
int newSize = idealLongArraySize(
mGroupMetadata.length + GROUP_METADATA_ARRAY_INCREMENT);
long[] array = new long[newSize];
System.arraycopy(mGroupMetadata, 0, array, 0, mGroupCount);
mGroupMetadata = array;
}
2,保存分组信息,高32位保存一个分组的起始id,低32位保存了一个分组的通话记录数目,
long metadata = ((long)size << 32) | cursorPosition;
if (expanded) {
metadata |= EXPANDED_GROUP_MASK;
}
mGroupMetadata[mGroupCount++] = metadata;
如果8条通话记录完全相同,则高32位是0,低32位为8,记录为(0,8)
C, 8条通话记录,前3条号码相同,第5条和第6条相同,第7条和第8条相同。
执行逻辑如下,
由于前3条号码相同,因此在while循环中调用第4条通话记录信息时,会处理前面3条信息,
addGroup(cursor.getPosition() - currentGroupSize, currentGroupSize);
调用此方法之后, mGroupMetadata记录的值为(0,3)
当全部调用完成之后, mGroupMetadata记录的值为(0,3),(3,1),(4,2),(6,2)。
小结:
GroupingListAdapter的findGroups方法最后调用CallLogGroupBuilder的addGroups方法进行通话的时间,号码进行分组,并且分别保存在CallLogAdapter的mDayGroups变量和GroupingListAdapter的mGroupMetadata中。