Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android4.4 Telephony流程分析——撥號應用(Dialer)的通話記錄加載過程

Android4.4 Telephony流程分析——撥號應用(Dialer)的通話記錄加載過程

編輯:關於Android編程

本文代碼以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 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 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);//將查詢這個數據集合,mstableCallsJoinData前面已定義
                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\cZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcmVcamF2YVxhbmRyb2lkXGNvbnRlbnSjrHN0ZXAxML2rsunRr8frx/O9u7j4y/yjrDxicj4KPC9wPgo8cD48L3A+CjxwcmUgY2xhc3M9"brush:java;"> 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即AsyncQueryHandler,用於工作線程返回查詢結果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,來看一下它的繼承結構:

浏覽器中打開即可查看大圖。

未完待續,有不對的地方,請指正。



  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved