Android Dialer源码分析之拨号主界面ListsFragment

序言: 又到了晚上加班的时间,打算开始写下关于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分别有

  1. SpeedDialFragment
  2. CallLogFragment
  3. AllContactsFragment
  4. 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一样了。

  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加载不同的数据显示列表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值