序言: 又到了晚上加班的时间,打算开始写下关于Dialer部分的博客,有关拨号流程和来电流程的文章很多,我就不跟着写了,我准备写些我在Dialer开发中学到的东西。


Androidb版本: 8.1


Dialer的拨号主界面对应的是Dialtactsactivity

Dialtactsactivity

  @Overrideprotected 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分别有

  1. SpeedDialFragment
  2. CallLogFragment
  3. AllContactsFragment
  4. VisualVoicemailCallLogFragment

其中我平时开发看的比较多的是CallLogFragment和AllContactsFragment;


CallLogFragment
CallLogFragment的核心是数据更新,操作者是CallLogQueryHandler。

在CallLogFragment的onCreate里初始化了

mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit);

当onActivityCreated调用时,去开始初始化数据。

  @Overridepublic 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

  @Overridepublic 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 deletedif (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 callswhere.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 properlyMessage 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操作

  @Overrideprotected 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,

  @Overrideprotected 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的数据


@Overridepublic boolean onCallsFetched(Cursor cursor) {if (getActivity() == null || getActivity().isFinishing()) {// Return false; we did not take ownership of the cursorreturn 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一样了。

  1. onCreateViewHolder
  2. onBindViewHolder
  3. getItemCount
  4. getItemViewType
  5. 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加载不同的数据显示列表。

Android Dialer源码分析之拨号主界面ListsFragment相关推荐

  1. 【Android SDM660源码分析】- 03 - UEFI XBL GraphicsOutput BMP图片显示流程

    [Android SDM660源码分析]- 03 - UEFI XBL GraphicsOutput BMP图片显示流程 1. GraphicsOutput.h 2. 显示驱动初化 DisplayDx ...

  2. Android 音频源码分析——AndroidRecord录音(一)

    Android 音频源码分析--AndroidRecord录音(一) Android 音频源码分析--AndroidRecord录音(二) Android 音频源码分析--AndroidRecord音 ...

  3. Android框架源码分析——从设计模式角度看 Retrofit 核心源码

    Android框架源码分析--从设计模式角度看 Retrofit 核心源码 Retrofit中用到了许多常见的设计模式:代理模式.外观模式.构建者模式等.我们将从这三种设计模式入手,分析 Retrof ...

  4. Android HandlerThread 源码分析

    HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我们又有一 个耗时任务需要执行,我们不得不重新创建 ...

  5. Android ADB 源码分析(三)

    前言 之前分析的两篇文章 Android Adb 源码分析(一) 嵌入式Linux:Android root破解原理(二) 写完之后,都没有写到相关的实现代码,这篇文章写下ADB的通信流程的一些细节 ...

  6. 【Android SDM660源码分析】- 02 - UEFI XBL QcomChargerApp充电流程代码分析

    [Android SDM660源码分析]- 02 - UEFI XBL QcomChargerApp充电流程代码分析 一.加载 UEFI 默认应用程序 1.1 LaunchDefaultBDSApps ...

  7. 【Android SDM660源码分析】- 01 - 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序

    [Android SDM660源码分析]- 01 - 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序 一.创建DXE_DRIVER ...

  8. 【Android SDM660源码分析】- 04 - UEFI ABL LinuxLoader 代码分析

    [Android SDM660源码分析]- 04 - UEFI ABL LinuxLoader 代码分析 1. LinuxLoader.c 系列文章: <[Android SDM660开机流程] ...

  9. 人人网官方Android客户端源码分析(1)

    ContentProvider是不同应用程序之间进行数据交换的标准API,ContentProvider以某种Uri的形式对外提供数据,允许其他应用访问或修改数据;其他应用程序使用ContentRes ...

最新文章

  1. 多核学习在图像分类中的应用
  2. [云炬python3玩转机器学习] 6-4 在线性回归模型中使用梯度下降法
  3. 7z(p7zip)压缩软件在Linux下的安装和使用
  4. 密码学系列之:memory-hard函数
  5. [ASP.NET MVC2 系列] ASP.NET MVC 之如何创建自定义路由约束
  6. 耗费 7.5 亿做的“垃圾”,被 3 个程序员爸爸重写并开源了
  7. mysql brighthouse_mysql的三方Infobright数据仓库BRIGHTHOUSE引擎
  8. 纯CSS3实现lightbox+图像轮播
  9. 【数据结构和算法笔记】队列(Queue)详解:c实现
  10. 显示器尺寸对照表_电脑显示器尺寸对照表(电脑显示器尺寸一览表)
  11. 网络安全实验5 证书应用和证书管理
  12. 衡水的高考2021年成绩查询,2021年衡水高考最高分多少分,历年衡水高考状元
  13. 4个C++算法数学模板(备战蓝桥杯)
  14. 行人重识别论文阅读5-基于换衣服的行人重识别
  15. Unity发布WebGL遇到的问题
  16. 七大人脉宝典造亿万富翁
  17. 搜索引擎开源代码(转)
  18. H5满屏彩色泡泡小特效(适合表白哦~做完发给让你每天想念的人吧~)
  19. 2020年书法落款_散文书法落款-2020年书法落款怎么写?
  20. 大数据与人工智能实验室【2022-05-08】

热门文章

  1. 如何找到自己喜欢的电子书|电子书搜索引擎介绍
  2. 乔治亚理工学院计算机专业,佐治亚理工学院电子与计算机专业
  3. PS学习记录6--html5 canvas+js实现ps钢笔抠图
  4. 【59MB】金融求职宝典 玩转金融业的正确姿势 增订版.PDF
  5. 一本书读懂大数据(每个人都看得懂的大数据入门书) - 电子书下载(高清版PDF格式+EPUB格式)...
  6. 离散数学 学习笔记-Day4
  7. HBase、Kudu 和 ClickHouse 全视角对比
  8. OpenResty 连接Redis
  9. JavaScript设计模式读书笔记(一)= 创建型设计模式
  10. openssl 自制国密证书