序言: 又到了晚上加班的时间,打算开始写下关于Dialer部分的博客,有关拨号流程和来电流程的文章很多,我就不跟着写了,我准备写些我在Dialer开发中学到的东西。
Androidb版本: 8.1
Dialer的拨号主界面对应的是Dialtactsactivity
Dialtactsactivity
@Override
protected void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState == null) {
getFragmentManager()
.beginTransaction()
.add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
.commit();
}
...
}
在Oncreate里,savedInstanceState为空时,显示的主Fragment是ListsFragment;
在ListsFragment里就是用ViewPager+ViewPagerTabs+PagerAdapter+Fragments的方式实现拨号界面。
其中这些fragment分别有
- SpeedDialFragment
- CallLogFragment
- AllContactsFragment
- VisualVoicemailCallLogFragment
其中我平时开发看的比较多的是CallLogFragment和AllContactsFragment;
CallLogFragment
CallLogFragment的核心是数据更新,操作者是CallLogQueryHandler。
在CallLogFragment的onCreate里初始化了
mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit);
当onActivityCreated调用时,去开始初始化数据。
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setupData();
updateSelectAllState(savedInstanceState);
mAdapter.onRestoreInstanceState(savedInstanceState);
}
onActivityCreated里setupData()
protected void setupData() {
mAdapter =
Bindings.getLegacy(getActivity())
.newCallLogAdapter(
getActivity(),
mRecyclerView,
this,
this,
activityType == CallLogAdapter.ACTIVITY_TYPE_DIALTACTS
? (CallLogAdapter.OnActionModeStateChangedListener) getActivity()
: null,
new CallLogCache(getActivity()),
mContactInfoCache,
getVoicemailPlaybackPresenter(),
new FilteredNumberAsyncQueryHandler(getActivity()),
activityType);
mRecyclerView.setAdapter(mAdapter);
fetchCalls();
}
构建了CallLogAdapter用来适配RecyclerView
@Override
public void fetchCalls() {
...
mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
...
}
通过CallLogQueryHandler去查询calllog数据库;
这里有两部分,一是fetchCalls的过程,二是CallLogAdapter的适配
首先看fetchCalls
private void fetchCalls(int token, int callType, boolean newOnly, long newerThan) {
StringBuilder where = new StringBuilder();
List<String> selectionArgs = new ArrayList<>();
// 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));
}
if (callType == Calls.VOICEMAIL_TYPE) {
VoicemailComponent.get(mContext)
.getVoicemailClient()
.appendOmtpVoicemailSelectionClause(mContext, where, selectionArgs);
} else {
// Filter out all Duo entries other than video calls
where
.append(" AND (")
.append(Calls.PHONE_ACCOUNT_COMPONENT_NAME)
.append(" IS NULL OR ")
.append(Calls.PHONE_ACCOUNT_COMPONENT_NAME)
.append(" NOT LIKE 'com.google.android.apps.tachyon%' OR ")
.append(Calls.FEATURES)
.append(" & ")
.append(Calls.FEATURES_VIDEO)
.append(" == ")
.append(Calls.FEATURES_VIDEO)
.append(")");
}
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();
/// M: [VoLTE ConfCallLog] For Volte Conference callLog @{
String orderby = Calls.DEFAULT_SORT_ORDER;
if (DialerFeatureOptions.isVolteConfCallLogSupport()) {
orderby = CallsCompat.SORT_DATE + " DESC";
}
startQuery(
token,
null,
uri,
CallLogQuery.getProjection(),
selection,
selectionArgs.toArray(new String[selectionArgs.size()]),
orderby);
/// @}
}
前部分就是构建数据库查询语句,后面还有封装
startQuery是属于AsyncQueryHandler的方法
public void startQuery(int token, Object cookie, Uri uri,
String[] projection, String selection, String[] selectionArgs,
String orderBy) {
// Use the token as what so cancelOperations works properly
Message msg = mWorkerThreadHandler.obtainMessage(token);
msg.arg1 = EVENT_ARG_QUERY;
WorkerArgs args = new WorkerArgs();
args.handler = this;
args.uri = uri;
args.projection = projection;
args.selection = selection;
args.selectionArgs = selectionArgs;
args.orderBy = orderBy;
args.cookie = cookie;
msg.obj = args;
mWorkerThreadHandler.sendMessage(msg);
}
最终WorkerHandler的handleMessage
case EVENT_ARG_QUERY:
Cursor cursor;
try {
cursor = resolver.query(args.uri, args.projection,
args.selection, args.selectionArgs,
args.orderBy);
// Calling getCount() causes the cursor window to be filled,
// which will make the first access on the main thread a lot faster.
if (cursor != null) {
cursor.getCount();
}
} catch (Exception e) {
Log.w(TAG, "Exception thrown during handling EVENT_ARG_QUERY", e);
cursor = null;
}
args.result = cursor;
break;
其实就是拿ContentResolver去查询数据库
后面当数据库查询查询完了 又发送message给自己
Message reply = args.handler.obtainMessage(token);
reply.obj = args;
reply.arg1 = msg.arg1;
reply.sendToTarget();
这里要看清
AsyncQueryHandler extends Handler
protected class WorkerHandler extends Handler
WorkerHandler 是AsyncQueryHandler的内部类handler
所以AsyncQueryHandler 是用WorkerHandler去查询,查完了再发信息给自己
最终处理onQueryComplete
case EVENT_ARG_QUERY:
onQueryComplete(token, args.cookie, (Cursor) args.result);
break;
onQueryComplete是抽象方法,实际是子类NoNullCursorAsyncQueryHandler操作
@Override
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);
}
处理了空cursor问题,又继续是抽象方法,调用子类CallLogQueryHandler的onNotNullableQueryComplete
回到CallLogQueryHandler,
@Override
protected synchronized void onNotNullableQueryComplete(int token, Object cookie, Cursor cursor) {
....
if (token == QUERY_CALLLOG_TOKEN || token == QUERY_SEARCH_TOKEN) {
/// @}
if (updateAdapterData(cursor)) {
cursor = null;
}
}
....
}
private boolean updateAdapterData(Cursor cursor) {
final Listener listener = mListener.get();
if (listener != null) {
return listener.onCallsFetched(cursor);
}
return false;
}
然后这里的listener,就是CallLogFragment
回到CallLogFragment的onCallsFetched
拿到数据之后开始更新CalllogAdapter的数据
@Override
public boolean onCallsFetched(Cursor cursor) {
if (getActivity() == null || getActivity().isFinishing()) {
// Return false; we did not take ownership of the cursor
return false;
}
mAdapter.invalidatePositions();
mAdapter.setLoading(false);
mAdapter.changeCursor(cursor);
。。。
return true;
}
其中最终调用的是changeCursor;
我们去看CalllogAdapter的changeCursor
changeCursor是属于GroupingListAdapter的方法
public void changeCursor(Cursor cursor) {
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) {
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();
}
}
最终notifyDataSetChanged,更新数据重新开始加载。
拿到数据后,就是CalllogAdapter的故事了
由于CalllogAdapter是集成RecyclerView.Adapter,所以数据的加载流程就应该和RecyclerView.Adapter一样了。
- onCreateViewHolder
- onBindViewHolder
- getItemCount
- getItemViewType
- getItemId
由于Calllog的item数据与界面很复杂,造成这里的onCreateViewHolder与onBindViewHolder非常冗长。
onBindViewHolder里显示调用了bindCallLogListViewHolder后来衍生出loadAndRender,loadAndRender又展开为loadData和render,然后还有异步AsyncTask,
详细内容动辄如此一排排
details.contactUri = info.lookupUri;
details.namePrimary = info.name;
details.nameAlternative = info.nameAlternative;
details.nameDisplayOrder = mContactsPreferences.getDisplayOrder();
details.numberType = info.type;
details.numberLabel = info.label;
details.photoUri = info.photoUri;
details.sourceType = info.sourceType;
details.objectId = info.objectId;
details.contactUserType = info.userType;
CalllogAdapter最主要就是数据加载流程,把握了这些,自定制就没问题了。
然后CallLogFragment就正常显示了。
AllContactsFragment的难点也是Adapter的适配过程,
AllContactsFragment的Adapter是多层继承
DefaultContactListAdapter extends ContactListAdapter extends ContactEntryListAdapter extends IndexerListAdapter IndexerListAdapter extends PinnedHeaderListAdapter PinnedHeaderListAdapter extends CompositeCursorAdapter extends BaseAdapter
经过这么多的继承,使DefaultContactListAdapter看着很简单又很复杂,简单是因为结构内部只有少数几个方法,复杂是因为大部分内容都是在父类里实现,同样,这里的数据加载过程也是很复杂,代码长。代码不再看了。
总结:
经过从DialtactsActivity显示ListsFragment,到CalllogFragment的数据获取到Adapter加载数据流程,可以看到这一路下来,不停的分化,每个Fragment的内容都很多,数据查询的封装也多,最后导致整个项目看起来篇幅很大,但是核心内容就是Activity显示不同的Fragment,Fragment加载不同的数据显示列表。