Android 7.1.1 Dialer中通话记录显示详解

这两天在看通话记录相关问题,顺便跟踪了Dialer中的通话记录是怎么显示出来的,在这和大家分享下。当有来电或去电时,calllog会被插入到calllog.db的数据中去,具体可以参考《Android 7.1.1 通话记录数据库详解》。在Dialer中通话记录对应的为CallLogFragment这个界面。下面我们先来具体分析这个Fragment的布局,再来看看每个控件的数据是如何获取的。

通话记录对应的界面为CallLogFragment,在onCreateView中加载了call_log_fragment.xml,这个布局中具体有两个控件RecyclerView和EmptyContentView(继承自LinearLayout的自定义控件),代码如下:

 call_log_fragment.xml

 //packages/apps/Dialer/res/layout/call_log_fragment.xml
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background_dialer_call_log">

    <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/background_dialer_call_log"
        android:clipToPadding="false"
        android:paddingStart="@dimen/call_log_horizontal_margin"
        android:paddingEnd="@dimen/call_log_horizontal_margin"
        android:paddingBottom="@dimen/floating_action_button_list_bottom_padding" />

    <com.android.dialer.widget.EmptyContentView
        android:id="@+id/empty_list_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone" />

</FrameLayout>

//packages/apps/Dialer/src/com/android/dialer/calllog/CallLogFragment.java
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
        View view = inflater.inflate(R.layout.call_log_fragment, container, false);
        setupView(view, null);
        return view;
    }

    protected void setupView(
            View view, @Nullable VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
        mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
        mRecyclerView.setHasFixedSize(true);
        mLayoutManager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(mLayoutManager);
        mEmptyListView = (EmptyContentView) view.findViewById(R.id.empty_list_view);
        mEmptyListView.setImage(R.drawable.empty_call_log);
        mEmptyListView.setActionClickedListener(this);

        int activityType = mIsCallLogActivity ? CallLogAdapter.ACTIVITY_TYPE_CALL_LOG :
                CallLogAdapter.ACTIVITY_TYPE_DIALTACTS;
        String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
        mAdapter = ObjectFactory.newCallLogAdapter(
                        getActivity(),
                        this,
                        new ContactInfoHelper(getActivity(), currentCountryIso),
                        voicemailPlaybackPresenter,
                        activityType);
        mRecyclerView.setAdapter(mAdapter);
        fetchCalls();
    }
其中EmptyContentView是当数据库中没有calllog时显示,具体显示mRecyclerView或mEmptyListView是根据CallLogFragment.onCallsFetched中cursor的size决定的。在setupView中主要设置了RecyclerView的adapter和调用fetchCalls查询数据库。我们先来说说mRecyclerView设置的mAdapter,通过ObjectFactory.newCallLogAdapter返回CallLogAdapter的实例,在adapter中根据viewType,创建createVoicemailPromoCardViewHolder或者createCallLogEntryViewHolder,下面具体以创建calllog的entry来讲讲。

在createCallLogEntryViewHolder中加载了call_log_list_item.xml,然后创建了CallLogListItemViewHolder的实例,布局文件咱就不贴代码了,结合着界面来看看具体显示效果


布局文件就不在继续分析了,具体看下CallLogListItemViewHolder中每个item的值都是怎么来的,刚上面讲到在CallLogFragment的setupView中调用了fetchCalls,这个方法就是查询数据库的,具体流程如下


下面主要看看CallLogQueryHandler中fetchCalls查询数据库时的代码,调用startQuery后,数据查询完成后会回调onNotNullableQueryComplete,在这个里面update cursor

//packages/apps/Dialer/src/com/android/dialer/calllog/CallLogQueryHandler.java
    private void fetchCalls(int token, int callType, boolean newOnly, long newerThan) {
        StringBuilder where = new StringBuilder();
        List<String> selectionArgs = Lists.newArrayList();

        // Always hide blocked calls.
        where.append("(").append(Calls.TYPE).append(" != ?)");
        selectionArgs.add(Integer.toString(AppCompatConstants.CALLS_BLOCKED_TYPE));

        // Ignore voicemails marked as deleted
        if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
                >= Build.VERSION_CODES.M) {
            where.append(" AND (").append(Voicemails.DELETED).append(" = 0)");
        }

        if (newOnly) {
            where.append(" AND (").append(Calls.NEW).append(" = 1)");
        }

        if (callType > CALL_TYPE_ALL) {
            where.append(" AND (").append(Calls.TYPE).append(" = ?)");
            selectionArgs.add(Integer.toString(callType));
        } else {
            where.append(" AND NOT ");
            where.append("(" + Calls.TYPE + " = " + AppCompatConstants.CALLS_VOICEMAIL_TYPE + ")");
        }

        if (newerThan > 0) {
            where.append(" AND (").append(Calls.DATE).append(" > ?)");
            selectionArgs.add(Long.toString(newerThan));
        }

        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();
        startQuery(token, null, uri, CallLogQuery._PROJECTION, selection, selectionArgs.toArray(
                new String[selectionArgs.size()]), Calls.DEFAULT_SORT_ORDER);
    }
查询完成之后调用onNotNullableQueryComplete,因为CallLogQueryHandler extends NoNullCursorAsyncQueryHandler ,而NoNullCursorAsyncQueryHandler extends AsyncQueryHandler,当调用AsyncQueryHandler的startQuery时会回调AsyncQueryHandler的onQueryComplete(Android原生的,具体实现方式可以自己查看,这里就不啰嗦了)
而在NoNullCursorAsyncQueryHandler调用了onQueryComplete,接着调用onNotNullableQueryComplete

//packages/apps/Dialer/src/com/android/dialer/calllog/CallLogQueryHandler.java
    protected synchronized void onNotNullableQueryComplete(int token, Object cookie,
            Cursor cursor) {
        if (cursor == null) {
            return;
        }
        try {
            if (token == QUERY_CALLLOG_TOKEN || token == QUERY_VOICEMAIL_ARCHIVE) {
                if (updateAdapterData(cursor)) {
                    cursor = null;
                }
            } else if (token == QUERY_VOICEMAIL_STATUS_TOKEN) {
                updateVoicemailStatus(cursor);
            } else if (token == QUERY_VOICEMAIL_UNREAD_COUNT_TOKEN) {
                updateVoicemailUnreadCount(cursor);
            } else if (token == QUERY_MISSED_CALLS_UNREAD_COUNT_TOKEN) {
                updateMissedCallsUnreadCount(cursor);
            } else {
                Log.w(TAG, "Unknown query completed: ignoring: " + token);
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }
 在updateAdapterData会调用所有实现了CallLogQueryHandler.Listener接口的onCallsFetched方法

//packages/apps/Dialer/src/com/android/dialer/calllog/CallLogQueryHandler.java
    private boolean updateAdapterData(Cursor cursor) {
        final Listener listener = mListener.get();
        if (listener != null) {
            return listener.onCallsFetched(cursor);
        }
        return false;

    } 

在CallLogFragment中的onCallsFetched会获取到查询后的cursor,在这调用mAdapter.changeCursor(cursor)后,并根据cursor.getCount决定显示那个view

//packages/apps/Dialer/src/com/android/dialer/calllog/CallLogFragment.java
  public boolean onCallsFetched(Cursor cursor) {
        ...
        mAdapter.changeCursor(cursor);
        // This will update the state of the "Clear call log" menu item.
        getActivity().invalidateOptionsMenu();

        boolean showListView = cursor != null && cursor.getCount() > 0;
        mRecyclerView.setVisibility(showListView ? View.VISIBLE : View.GONE);
        mEmptyListView.setVisibility(!showListView ? View.VISIBLE : View.GONE);
       ...
    }
接下来就要重点说下RecyclerView的adapter了,因为CallLogAdapter继承GroupingListAdapter,当调用mAdapter.changeCursor时,实际调用父类GroupingListAdapter里changeCursor
//packages/apps/Dialer/src/com/android/dialer/calllog/GroupingListAdapter.java
    public void changeCursor(Cursor cursor, boolean voicemail) {
        if (cursor == mCursor) {
            return;
        }

        if (mCursor != null) {
            mCursor.unregisterContentObserver(mChangeObserver);
            mCursor.unregisterDataSetObserver(mDataSetObserver);
            mCursor.close();
        }

        // Reset whenever the cursor is changed.
        reset();
        mCursor = cursor;

        if (cursor != null) {
            if (voicemail) {
                addVoicemailGroups(mCursor);
            } else {
                addGroups(mCursor);
            }

            // Calculate the item count by subtracting group child counts from the cursor count.
            mItemCount = mGroupMetadata.size();

            cursor.registerContentObserver(mChangeObserver);
            cursor.registerDataSetObserver(mDataSetObserver);
            notifyDataSetChanged();
        }
    }
在这方法里registerContentObserver和registerDataSetObserver,并调用notifyDataSetChanged,当cursor的content发生改变之后,又会调用onContentChanged最后还是会调到CallLogFragment.fetchCalls查询数据库,在这就不围绕这个再说了
    protected ContentObserver mChangeObserver = new ContentObserver(new Handler()) {
        @Override
        public boolean deliverSelfNotifications() {
            return true;
        }

        @Override
        public void onChange(boolean selfChange) {
            onContentChanged();
        }
    };

    protected DataSetObserver mDataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            notifyDataSetChanged();
        }
    };
当调用notifyDataSetChanged后,CallLogAdapter中就会根据查询后的cursor重新rebind and relayout所有可见的view,具体对每个view设置Text,是在CallLogListItemHelper.setPhoneCallDetails里,根据传进去的值,对每个view赋值,最终实现是在PhoneCallDetailsHelper.setPhoneCallDetails这个方法里对各个view设置值,代码太多就不在贴代码了,下面简单总结下整个流程

1、在CallLogFragment的onCreateView中设置RecyclerView的adapter,并调用fetchCalls查询数据。

2、当查询完成后会回调到CallLogFragment的onCallsFetched,并调用changeCursor,registerContentObserver和registerDataSetObserver,数据发生改变的时候,调用RecyclerView.Adapter的notifyDataSetChanged就会根据新的cursor rebind and relayout所有可见的view。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值