通话记录分析

来源:互联网 发布:mac配置qq企业邮箱 编辑:程序博客网 时间:2024/05/23 23:22

1,通话记录

1.1 初始化

在Dialer中,通话记录信息都是通过CallLogActivity 显示,实际上,真正完成的是CallLogFragment 。CallLogActivity的内部类

ViewPagerAdapter的getItem方法如下,

public Fragment getItem(int position) {     switch (getRtlPosition(position)) {     case TAB_INDEX_ALL:         return new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL);    case TAB_INDEX_MISSED:          return new CallLogFragment(Calls.MISSED_TYPE);     }     throw new IllegalStateException("No fragment at position " + position);}

查询的数据库:contacts2.calls

通话记录没有搜索,在onCreateView函数里根据不同的参数直接查询。

查询的时间顺序是有近到远。

CallLogFragment的构造方法如下,

public CallLogFragment(int filterType, int logLimit, long dateLimit) {     mCallTypeFilter = filterType;//查询通话记录的类型     mLogLimit = logLimit;     mDateLimit = dateLimit;}

通话记录主要包括以下类型:

所有通话,未接来电,所有外拨电话,所有来电,黑名单来电。

CallLogQueryHandler对应的定义如下,

private static final int INCOMING_IMS_TYPE = 5;private static final int OUTGOING_IMS_TYPE = 6;private static final int MISSED_IMS_TYPE = 7;•••

CallLogFragment的onCreate方法主要逻辑如下,

final Activity activity = getActivity();//获取所在的Activity对象//获取进程的ContentResolver对象final ContentResolver resolver = activity.getContentResolver();String currentCountryIso = GeoUtil.getCurrentCountryIso(activity);//构造CallLogQueryHandler对象mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit);//锁屏管理mKeyguardManager =  (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);//注册通话记录数据库监听resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver);//注册联系人数据库监听resolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true,                mContactsObserver);resolver.registerContentObserver(Status.CONTENT_URI, true, mVoicemailStatusObserver);setHasOptionsMenu(true);//设置菜单

CallLogFragment的onCreateView方法主要逻辑如下,

1,获取RecyclerView布局,

mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);mRecyclerView.setHasFixedSize(true);mLayoutManager = new LinearLayoutManager(getActivity());mRecyclerView.setLayoutManager(mLayoutManager);

2,构造RecyclerView的Adapter

mAdapter = ObjectFactory.newCallLogAdapter(getActivity(),this,   new ContactInfoHelper(getActivity(), currentCountryIso), mVoicemailPlaybackPresenter,   isShowingRecentsTab);mRecyclerView.setAdapter(mAdapter);

3,调用fetchCalls方法开始查询通话记录

fetchCalls();

1.2 查询通话记录

CallLogFragment的fetchCalls调用流程图如下,



fetchCalls方法如下,

public void fetchCalls() {   mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);}

CallLogQueryHandler有不同参数的fetchCalls方法,最后的fetchCalls方法主要逻辑如下,

1,构造查询语句,

StringBuilder where = new StringBuilder();List<String> selectionArgs = Lists.newArrayList();// Ignore voicemails marked as deletedwhere.append(Voicemails.DELETED);where.append(" = 0");if (newOnly) {   where.append(" AND ");   where.append(Calls.NEW);   where.append(" = 1");}•••

2,根据通话记录查询类型构造查询参数,

if (callType > CALL_TYPE_ALL) {     if (where.length() > 0) {         where.append(" AND ");     }     if ((callType == Calls.INCOMING_TYPE) || (callType == Calls.OUTGOING_TYPE)                    || (callType == Calls.MISSED_TYPE)) {        where.append(String.format("(%s = ? OR %s = ?)",                        Calls.TYPE, Calls.TYPE));     } else {        // Add a clause to fetch only items of type voicemail.        where.append(String.format("(%s = ?)", Calls.TYPE));    }        // Add a clause to fetch only items newer than the requested date        selectionArgs.add(Integer.toString(callType));    if (callType == Calls.INCOMING_TYPE) {        selectionArgs.add(Integer.toString(INCOMING_IMS_TYPE));    } else if (callType == Calls.OUTGOING_TYPE) {         selectionArgs.add(Integer.toString(OUTGOING_IMS_TYPE));    } else if (callType == Calls.MISSED_TYPE) {         selectionArgs.add(Integer.toString(MISSED_IMS_TYPE));   }•••

3,获取URI

final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit;final String selection = where.length() > 0 ? where.toString() : null;Uri uri = TelecomUtil.getCallLogUri(mContext).buildUpon()  .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit)).build();

4,调用startQuery方法进行查询,

startQuery(token, null, uri, CallLogQuery._PROJECTION, selection, selectionArgs.toArray(EMPTY_STRING_ARRAY), Calls.DEFAULT_SORT_ORDER);

父类NoNullCursorAsyncQueryHandler的startQuery方法如下,

public void startQuery(int token, Object cookie, Uri uri, String[] projection, String selection,     String[] selectionArgs, String orderBy) {final CookieWithProjection projectionCookie = new CookieWithProjection(cookie, projection);  super.startQuery(token, projectionCookie, uri, projection, selection, selectionArgs, orderBy);}

直接调用父类AsyncQueryHandler的startQuery方法进行异步查询, AsyncQueryHandler的原理在此不论述了。

只需要知道的是AsyncQueryHandler查询完成之后会回调onQueryComplete方法。

NoNullCursorAsyncQueryHandler的onQueryComplete方法如下,

protected final void onQueryComplete(int token, Object cookie, Cursor cursor) {    CookieWithProjection projectionCookie = (CookieWithProjection) cookie;    super.onQueryComplete(token, projectionCookie.originalCookie, cursor);    if (cursor == null) {        cursor = new EmptyCursor(projectionCookie.projection);    }    onNotNullableQueryComplete(token, projectionCookie.originalCookie, cursor);}

onNotNullableQueryComplete方法是一个abstract方法,子类CallLogQueryHandler的实现如下,

if (token == QUERY_CALLLOG_TOKEN) {    if (updateAdapterData(cursor)) {          cursor = null;     }•••

如果是普通的通话记录,就调用updateAdapterData方法更新数据。

updateAdapterData方法如下,

private boolean updateAdapterData(Cursor cursor) {    final Listener listener = mListener.get();     if (listener != null) {          return listener.onCallsFetched(cursor);     }     return false;}

回调监听器的onCallsFetched方法,当然是在CallLogFragment实现。

监听器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的onCallsFetched方法会完成通话记录的更新显示。

1.3 更新显示

CallLogFragment的onCallsFetched方法主要逻辑如下,

mAdapter.changeCursor(cursor);

mAdapter 是CallLogAdapter对象, 并且继承于GroupingListAdapter, GroupingListAdapter定义如下,

abstract class GroupingListAdapter extends RecyclerView.Adapter {

其中, GroupingListAdapter实现了RecyclerView.Adapter 的getItemCount方法,

CallLogAdapter实现了RecyclerView.Adapter的onCreateViewHolder/ onBindViewHolder方法。

GroupingListAdapter的changeCursor方法主要逻辑如下,

1,为mCursor变量赋值,

mCursor = cursor;

2,调用findGroups方法对查询到的通话记录分组,

findGroups();

3,更新界面。

notifyDataSetChanged();

notifyDataSetChanged原理在此就不论述了,总之会调用getItemCount/onCreateViewHolder/ onBindViewHolder方法更新界面。

1.3.1 分组

看通话记录界面,可以看到:

1,通话记录分为三类:今天,昨天,更早。如何分类的?

2,相邻的同一号码为一组显示。如何做到的?

查询完之后,会做两件事情,分组(分为今天,昨天以及更早),相邻的相同号码的通话记录分为一组。两件事情在两个不同的类中进行,但是同时进行。

首先在CallLogGroupBuilder类中的addGroups进行分组,分为今天,昨天以及更早。分组的依据是将通话时的时间和当前的时间进行对比。

int currentGroupDayGroup = getDayGroup(firstDate, currentTime);mGroupCreator.setDayGroup(firstRowId, currentGroupDayGroup);

然后调用CallLogAdapter 的setDayGroup 函数将相关信息存储在

mDayGroups(HashMap)中。在bindCallLogListViewHolder函数显示时,在该HashMap中查询当前和前一个Cursor的分组信息,

如果不相同,就显示一个分隔组(昨天或者更早)

int currentGroup = getDayGroupForCall(views.rowId);int previousGroup = getPreviousDayGroup(c);if (currentGroup != previousGroup) {   views.dayGroupHeader.setVisibility(View.VISIBLE);   views.dayGroupHeader.setText(getGroupDescription(currentGroup));} else {  views.dayGroupHeader.setVisibility(View.GONE);  }

不仅如此,在addGroups函数中,会比较相邻号码等相关信息是否相同,如果相同就是一个组,如果不同就新建一个组。

然后调用GroupingListAdapter的addGroup函数将这些分组信息保存在64位的mGroupMetadata数组中。其中,高位表示分组的大小,

低位表示分组的起始位置,这两个信息很重要,是显示的基础。

long metadata = ((long)size << 32) | cursorPosition;mGroupMetadata[mGroupCount++] = metadata;

比如:通话记录(3,1,2,2)共8条通话记录,分为4个组,保存的信息为(0,3),(3,1),(4,2),

(6,2)。GroupingListAdapter中的getItemCount()函数根据相关信息,返回的结果为通话记录的组数,而不是单条的通话记录,

这和listview的不一样。这样就回答了上面的两个疑惑。

1.3.2 onCreateViewHolder

CallLogAdapter的onCreateViewHolder方法如下,

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {    if (viewType == VIEW_TYPE_SHOW_CALL_HISTORY_LIST_ITEM) {       return ShowCallHistoryViewHolder.create(mContext, parent);    } else if (viewType == VIEW_TYPE_VOICEMAIL_PROMO_CARD) {      return createVoicemailPromoCardViewHolder(parent);    }    return createCallLogEntryViewHolder(parent);}

createCallLogEntryViewHolder的方法如下,

private ViewHolder createCallLogEntryViewHolder(ViewGroup parent) {  LayoutInflater inflater = LayoutInflater.from(mContext);  View view = inflater.inflate(R.layout.call_log_list_item, parent, false);  CallLogListItemViewHolder viewHolder = CallLogListItemViewHolder.create(      view, mContext, mExpandCollapseListener, mTelecomCallLogCache,                mCallLogListItemHelper, mVoicemailPlaybackPresenter);  viewHolder.callLogEntryView.setTag(viewHolder);  viewHolder.callLogEntryView.setAccessibilityDelegate(mAccessibilityDelegate);  viewHolder.primaryActionView.setOnCreateContextMenuListener(mOnCreateContextMenuListener);viewHolder.primaryActionView.setTag(viewHolder);   return viewHolder;}

由此,一条通话记录就对应一个CallLogListItemViewHolder对象。

1.3.3 onBindViewHolder

CallLogAdapter的onBindViewHolder方法会调用bindCallLogListViewHolder方法加载每条通话记录的信息, 

bindCallLogListViewHolder方法的主要逻辑如下,

1,获取通话记录组中的第一个Cursor以及该组中的通话记录条数。

Cursor c = (Cursor) getItem(position);if (c == null) {   return;}int count = getGroupSize(position);

2,依次将号码等信息封装在ContactInfo, PhoneCallDetails以及CallLogListItemViewHolder类中。

3,控制是否显示分组,调用CallLogListItemViewHolder的showActions()函数是否显示新建联系人等信息(根据ContactInfo来决定)。

4,调用CallLogListItemViewHolder的setPhoto函数显示图标以及姓名等信息。

5,PhoneCallDetailsViews详细的显示PhoneCallDetails中的通话记录信息(通话时间以及归属地等等),并且PhoneCallDetailsViews

是包含于CallLogListItemViewHolder中的。