Android4.4 Telephony流程分析——拨号应用(Dialer)的通话记录加载过程

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉。

Android系统通话记录存储在联系人数据库contacts2.db中的calls表中,通话记录(calllog)存储到数据库的时机可查看我之前的一篇博客Android4.4
Telephony流程分析——电话挂断
step39,系统提供了CallLogProvider这个ContentProvider来供外界访问。我们来看本文将会使用到的CallLogProvider的代码片段:

/**
 * Call log content provider.
 */
public class CallLogProvider extends ContentProvider {

......
    private static final int CALLS_JION_DATA_VIEW = 5;
    private static final int CALLS_JION_DATA_VIEW_ID = 6;
......
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        sURIMatcher.addURI(CallLog.AUTHORITY, "calls", CALLS);
        sURIMatcher.addURI(CallLog.AUTHORITY, "calls/#", CALLS_ID);
        sURIMatcher.addURI(CallLog.AUTHORITY, "calls/filter/*", CALLS_FILTER);
        sURIMatcher.addURI(CallLog.AUTHORITY, "calls/search_filter/*", CALLS_SEARCH_FILTER);
        sURIMatcher.addURI(CallLog.AUTHORITY, "callsjoindataview", CALLS_JION_DATA_VIEW);
        sURIMatcher.addURI(CallLog.AUTHORITY, "callsjoindataview/#", CALLS_JION_DATA_VIEW_ID);
        sURIMatcher.addURI(CallLog.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGESTIONS);
        sURIMatcher.addURI(CallLog.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGESTIONS);
        sURIMatcher.addURI(CallLog.AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", SEARCH_SHORTCUT);
    }
    private static final HashMap<String, String> sCallsProjectionMap;
......
    private static final String mstableCallsJoinData = Tables.CALLS + " LEFT JOIN "
    + " (SELECT * FROM " +  Views.DATA + " WHERE " + Data._ID + " IN "
    + "(SELECT " +  Calls.DATA_ID + " FROM " + Tables.CALLS + ")) AS " + Views.DATA
            + " ON(" + Tables.CALLS + "." + Calls.DATA_ID + " = " + Views.DATA + "." + Data._ID + ")";
......
    private static final HashMap<String, String> sCallsJoinDataViewProjectionMap;
......

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
     .......
        switch (match) {
......
            case CALLS_JION_DATA_VIEW: {
                qb.setTables(mstableCallsJoinData);
                qb.setProjectionMap(sCallsJoinDataViewProjectionMap);
                qb.setStrict(true);
                break;
            }

            case CALLS_JION_DATA_VIEW_ID: {
                qb.setTables(mstableCallsJoinData);//将查询这个数据集合,<span style="line-height: 23.9999980926514px; font-family: Arial;">mstableCallsJoinData</span>前面已定义
                qb.setProjectionMap(sCallsJoinDataViewProjectionMap);
                qb.setStrict(true);
                selectionBuilder.addClause(getEqualityClause(Tables.CALLS + "." + Calls._ID,
                        parseCallIdFromUri(uri)));
                break;
            }
......
         }
      ......
    }
......
}

calls表的主要字段及其数据类型可查看下表:

下面是Dialer中通话记录的加载时序图,此图只关注calllog数据的处理:

Dialer模块是Android4.4之后才独立处理的,整个模块大部分的UI显示都是使用Framgment实现。触发通话记录刷新加载的的操作比较多,如Fragment onResume()时、数据库更新时、选择了通话记录过滤等,这些操作都会使用step2的refreshData()方法来查询数据库。

step3~step4,刷新通话记录联系人图片缓存,联系人图片缓存使用的是LruCache技术,异步加载,后面再发博文分析。

step5,读取sim卡过滤设置、通话类型设置,开始查询,

    public void startCallsQuery() {
        mAdapter.setLoading(true);//step6,正在加载联系人,此时联系人列表不显示为 空

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
        int simFilter = prefs.getInt(Constants.SIM_FILTER_PREF, Constants.FILTER_SIM_DEFAULT);//要查看calllog的SIM卡
        int typeFilter = prefs.getInt(Constants.TYPE_FILTER_PREF, Constants.FILTER_TYPE_DEFAULT);//通话类型:来电?去电?未接?全部?
        mCallLogQueryHandler.fetchCallsJionDataView(simFilter, typeFilter);
        /* add wait cursor */
        int count = this.getListView().getCount();
        Log.i(TAG, "***********************count : " + count);
        mIsFinished = false;

        if (0 == count) {//现在列表中记录为空,显示等待加载控件
            Log.i(TAG, "call sendmessage");
            mHandler.sendMessageDelayed(mHandler.obtainMessage(WAIT_CURSOR_START),
                    WAIT_CURSOR_DELAY_TIME);
        }
    }

step7~step11,一步一步将添加查询条件,将查询请求提交给ContentProvider。

step9,设置查询Uri,

        if (QUERY_ALL_CALLS_JOIN_DATA_VIEW_TOKEN == token) {
            queryUri = Uri.parse("content://call_log/callsjoindataview");
            queryProjection = CallLogQueryEx.PROJECTION_CALLS_JOIN_DATAVIEW;
        }

CallLogQueryHandlerEx、NoNullCursorAsyncQueryHandler抽象类、AsyncQueryHandler抽象类是继承关系,继承自Handler,

AsyncQueryHandler是Framework中提供的异步查询类,定义在\frameworks\base\core\java\android\content,step10将查询请求交给它,

    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);//mWorkerThreadHandler是WorkerHandler的对象,也是一个Handler,与工作线程通信
        msg.arg1 = EVENT_ARG_QUERY;

        WorkerArgs args = new WorkerArgs();
        args.handler = this;//this即<span style="font-family: Arial; line-height: 23.9999980926514px;">AsyncQueryHandler,用于</span>工作线程返回查询结果Cursor
        args.uri = uri;
        args.projection = projection;
        args.selection = selection;
        args.selectionArgs = selectionArgs;
        args.orderBy = orderBy;
        args.cookie = cookie;
        msg.obj = args;

        mWorkerThreadHandler.sendMessage(msg);//查询将在工作线程中进行
    }

step12~step15,工作线程将查询结果返回给AsyncQueryHandler的handleMessage()处理。

    protected class WorkerHandler extends Handler {
        public WorkerHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            final ContentResolver resolver = mResolver.get();
            if (resolver == null) return;

            WorkerArgs args = (WorkerArgs) msg.obj;

            int token = msg.what;
            int event = msg.arg1;

            switch (event) {
                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;//查询结果cursor
                    break;

                    ......
            }

            // passing the original token value back to the caller
            // on top of the event values in arg1.
            Message reply = args.handler.obtainMessage(token); //args.handler就是上文提到的this
            reply.obj = args;
            reply.arg1 = msg.arg1; //EVENT_ARG_QUERY

            reply.sendToTarget();
        }
    }

step16,查询完成,返回cursor,判断cursor是否为空。

    @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返回
            cursor = new EmptyCursor(projectionCookie.projection);
        }
        onNotNullableQueryComplete(token, projectionCookie.originalCookie, cursor);//step17
    }

step18~step19,将结果cursor返回给CallLogFragmentEx。

    @Override
    public void onCallsFetched(Cursor cursor) {
        .......
        mAdapter.setLoading(false);//与step6对应
        mAdapter.changeCursor(cursor);//更改CallLogListAdapter的cursor,刷新ListView
        // when dialpadfrangment is in forgoround, not update dial pad menu item.
        Activity activity = getActivity();
        /// M: for refresh option menu;
        activity.invalidateOptionsMenu();
        if (mScrollToTop) {
            //Modified by Lee 2014-06-30 for flip sms and call start
            final HYListView listView = (HYListView)getListView();
            //Modified by Lee 2014-06-30 for flip sms and call end

            if (listView.getFirstVisiblePosition() > 5) {
                listView.setSelection(5);
            }

            listView.setSelection(0);
            mScrollToTop = false;
        }
        mCallLogFetched = true;

        /** M: add :Bug Fix for ALPS00115673 @ { */
        Log.i(TAG, "onCallsFetched is call");
        mIsFinished = true;
        mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(getActivity(),
                android.R.anim.fade_out));
        mLoadingContainer.setVisibility(View.GONE);
        mLoadingContact.setVisibility(View.GONE);
        mProgress.setVisibility(View.GONE);
        // hide calldetail view,let no call log warning show on all screen
        if (mCallDetail != null) {
            if (cursor == null || cursor.getCount() == 0) {
                mCallDetail.setVisibility(View.GONE);
            } else {
                mCallDetail.setVisibility(View.VISIBLE);
            }
        }

        mEmptyTitle.setText(R.string.recentCalls_empty);
        /** @ }*/

        destroyEmptyLoaderIfAllDataFetched();
        // send message,the message will execute after the listview inflate
        handle.sendEmptyMessage(SETFIRSTTAG); //设置ListView第一条可显示的数据
    }

step24~step32,主要是处理通话记录的分组显示。

step26中是具体的分组规则、分组过程:

    public void addGroups(Cursor cursor) {
        final int count = cursor.getCount();
        if (count == 0) {
            return;
        }

        int currentGroupSize = 1;
        cursor.moveToFirst();
        // The number of the first entry in the group.
        String firstNumber = cursor.getString(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_NUMBER);
        // This is the type of the first call in the group.
        int firstCallType = cursor.getInt(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_CALL_TYPE);

        //The following lines are provided and maintained by Mediatek Inc.
        int firstSimId = cursor.getInt(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_SIM_ID);
        int firstVtCall = cursor.getInt(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_VTCALL);
        long firstDate = cursor.getLong(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_DATE);
        if (0 != cursor.getCount()) {
            setGroupHeaderPosition(cursor.getPosition());
        }
        /// @}

        while (cursor.moveToNext()) {
            // The number of the current row in the cursor.
            final String currentNumber = cursor.getString(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_NUMBER);
            final int callType = cursor.getInt(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_CALL_TYPE);
            /// @}
            final boolean sameNumber = equalNumbers(firstNumber, currentNumber);
            final boolean shouldGroup;
            /// M: add @{
            final int simId = cursor.getInt(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_SIM_ID);
            final int vtCall = cursor.getInt(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_VTCALL);
            final long date = cursor.getLong(CallLogQueryEx.CALLS_JOIN_DATA_VIEW_DATE);
            final boolean isSameDay = CallLogDateFormatHelper.isSameDay(firstDate, date);
            /// @ }
            /// M: [VVM] voice mail should not be grouped.
            if (firstCallType == Calls.VOICEMAIL_TYPE || !sameNumber || firstCallType != callType
                    || firstSimId != simId || firstVtCall != vtCall || !isSameDay) { //看注释
                // Should only group with calls from the same number, the same
                // callType, the same simId and the same vtCall values.
                shouldGroup = false; //这个条件下,ListView需要显示一条记录
            } else {
                shouldGroup = true; //同一个group ListView只显示一条记录,加上通话记录数目
            }
            /// @}

            if (shouldGroup) {
                // Increment the size of the group to include the current call, but do not create
                // the group until we find a call that does not match.
                currentGroupSize++; //累加
            } else {
                // Create a group for the previous set of calls, excluding the current one, but do
                // not create a group for a single call.
                addGroup(cursor.getPosition() - currentGroupSize, currentGroupSize);
                if (!isSameDay) { //不是同一天的通话记录,需要显示Header(日期)
                    setGroupHeaderPosition(cursor.getPosition());
                }
                /// @}
                // Start a new group; it will include at least the current call.
                currentGroupSize = 1;
                // The current entry is now the first in the group.//上一条记录为参考值,比较
                firstNumber = currentNumber;
                firstCallType = callType;
                /// M: add @{
                firstCallType = callType;
                firstSimId = simId;
                firstVtCall = vtCall;
                firstDate = date;
                /// @}
            }
        }

        addGroup(count - currentGroupSize, currentGroupSize);
        /// @}
    }

step27~step29,记录需要设置Header的位置到mHeaderPositionList这个HashMap中,

    public void setGroupHeaderPosition(int cursorPosition) {
        mHeaderPositionList.put(Integer.valueOf(cursorPosition), Boolean.valueOf(true));
    }

step30~step32,记录一个Group(ListView的一个item)的开始位置和大小(包含的通话记录数目)于mGroupMetadata,

    protected void addGroup(int cursorPosition, int size, boolean expanded) {
        if (mGroupCount >= mGroupMetadata.length) {
            int newSize = idealLongArraySize(
                    mGroupMetadata.length + GROUP_METADATA_ARRAY_INCREMENT);
            long[] array = new long[newSize];
            System.arraycopy(mGroupMetadata, 0, array, 0, mGroupCount);
            mGroupMetadata = array;
        }

        long metadata = ((long)size << 32) | cursorPosition;
        if (expanded) {
            metadata |= EXPANDED_GROUP_MASK;
        }
        mGroupMetadata[mGroupCount++] = metadata;
    }

mGroupMetadata是long型数组,初始大小为GROUP_METADATA_ARRAY_INITIAL_SIZE,16,当空间不够时,每次以GROUP_METADATA_ARRAY_INCREMENT(128)增大。

通话记录ListView和Adapter的数据绑定是在GroupingListAdapter中的getView()方法中,此类继承自BaseAdapter,来看一下它的继承结构:

通话记录的数据加载先说到这里。

右键复制图片地址,在浏览器中打开即可查看大图。

未完待续,有不对的地方,请指正。

时间: 2024-10-20 22:04:48

Android4.4 Telephony流程分析——拨号应用(Dialer)的通话记录加载过程的相关文章

Android4.4 Telephony流程分析——SIM卡开机时的数据加载

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. 本文主要介绍sim卡数据的读取过程,当射频状态处于准备状态时,此时UiccCardApplication应处于AppState.APPSTATE_READY状态,我们沿着这个信号跟踪下去.阅读本文时可先阅读Android4.4 Telephony流程分析--SIM卡开机时的初始化一文,了解Radio和sim卡状态更新过程. 先来看一下数据加载的序列图: step1~step3,走的是更新过程

Android4.4 Telephony流程分析——GsmServiceStateTracker管理网络服务状态

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. 本文主要介绍GsmServiceStateTracker是怎么管理网络的?手机开机后,怎么去注册网络?网络状态是怎么变换传递的. Android在ServiceState.java中定义了四种ServiceState状态和16中无线通信网络类型: public class ServiceState implements Parcelable { /** * Normal operation

Android4.4 Telephony流程分析——联系人(Contact)列表缩略图的加载过程

本文代码以MTK平台Android 4.4.2为分析对象,与Google原生AOSP有些许差异,请读者知悉. Android联系人列表的缩略图加载主要用到ContactPhotoManager.java这个类,这是个抽象类,实现了ComponentCallbacks2接口,其内部有个它的具体实现类,叫ContactPhotoManagerImpl,ContactPhotoManagerImpl继承了ContactPhotoManager并实现了android.os.Handler.Callbac

Android4.4 Telephony流程分析——SIM卡开机时的初始化

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. 本文主要介绍MTK Android开机时,SIM卡的Framework部分初始化过程. 先看一段注释: /* Once created UiccController registers with RIL for "on" and "unsol_sim_status_changed" * notifications. When such notification

Android4.4 Telephony流程分析——彩信(MMS)发送过程

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. 彩信收发依靠WAP网络,在Android4.4中的实现基于Http协议的应用.下图为几个彩信传输过程中的关键类: SendTransaction:发送彩信 NotificationTransaction:彩信接收 ReadRecTransaction:彩信阅读报告 RetrieveTransaction:彩信下载 TransactionService是个service,负责调度处理各种彩信事

【Spring源码分析系列】启动component-scan类扫描加载过程

原文地址:http://blog.csdn.net/xieyuooo/article/details/9089441/ 在spring 3.0以上大家都一般会配置一个Servelet,如下所示: 1 <servlet> 2 <servlet-name>spring</servlet-name> 3 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-clas

Android4.4 Framework分析——Android默认Home应用Launcher3的加载过程分析

本文主要介绍Android4.4默认Home应用Launcher3的启动过程和Launcher3的数据加载过程.Launcher的启动是开机时,ActivityManagerService准备好后开始的,下图是它的启动序列图: step1,SystemServer中,ActivityManagerService准备好了. step3, boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord targe

SpringBoot启动流程分析(四):IoC容器的初始化过程

SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一):SpringApplication类初始化过程 SpringBoot启动流程分析(二):SpringApplication的run方法 SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法 SpringBoot启动流程分析(四

spring启动component-scan类扫描加载过程---源码分析

有朋友最近问到了 spring 加载类的过程,尤其是基于 annotation 注解的加载过程,有些时候如果由于某些系统部署的问题,加载不到,很是不解!就针对这个问题,我这篇博客说说spring启动过程,用源码来说明,这部分内容也会在书中出现,只是表达方式会稍微有些区别,我将使用spring 3.0的版本来说明(虽然版本有所区别,但是变化并不是特别大),另外,这里会从WEB中使用spring开始,中途会穿插自己通过newClassPathXmlApplicationContext 的区别和联系.