Android Dialer模块联系人搜索
发布日期:2021-06-30 21:23:30 浏览次数:2 分类:技术文章

本文共 21303 字,大约阅读时间需要 71 分钟。

MTK的Dialer模块联系人搜索     

      拨号搜索机制分为两个部分:引导搜索和搜索。其中引导搜索是指,从用户输入到开始搜索之间的流程,而搜索部分是指,从数据库搜索字符串的过程。

一、引导搜索部分

     默认的拨号界面的布局从上到下主要分为3个部分:显示列表、数字编辑框、拨号键盘。他们的作用是:用户直接在拨号键盘上输入数字,然后数字编辑框显示所输入的数字,同时在显示列表中体现此时的搜索结果。如图所示:

拨号界面布局

   从流程上来讲,需要拨号键盘将用户点击转换为按键事件并传递给编辑框,然后由编辑框传递给搜索框,再由搜索框传递给列表Fragment,然后在列表所加载的Adapter中体现当前的搜索结果。

搜索流程框图

1.1、从拨号键盘到编辑框

       用户在拨号键盘上的点击的数字按钮,都会在编辑框中体现出来,我们先来追踪这一过程。每个拨号键盘按钮都是DialpadKeyButton类型的View,他们继承自FrameLayout,当遇到点击事件时,就会触发configureKeypadListeners()方法,在DialpadFragment.java中

 

DialpadFragment.java

 

private void configureKeypadListeners(View fragmentView) {        final int[] buttonIds = new int[] {R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,                R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.zero, R.id.pound};         View dialpadKey;         for (int i = 0; i < buttonIds.length; i++) {            dialpadKey =  fragmentView.findViewById(buttonIds[i]);            dialpadKey.setOnClickListener(this);        }         // Long-pressing one button will initiate Voicemail.        final View one = fragmentView.findViewById(R.id.one);        one.setOnLongClickListener(this);         // Long-pressing zero button will enter '+' instead.        final View zero = fragmentView.findViewById(R.id.zero);        zero.setOnLongClickListener(this);   // Long-pressing one button will initiate Voicemail.        final View start = fragmentView.findViewById(R.id.star);        start.setOnLongClickListener(this);         // Long-pressing zero button will enter '+' instead.        final View pound = fragmentView.findViewById(R.id.pound);        pound.setOnLongClickListener(this);}

configureKeypadListeners()方法中,设置了dialpadKey点击事件的监听:dialpadKey.setOnClickListener(this);然后在DialpadFragment的onClick()方法中,将当前的点击事件转换为标准的按键输入:

@Override  DialpadFragment.java    public void onClick(View view) {        /** M: Prevent the event if dialpad is not shown. @{ */        if (getActivity() != null                && !((DialtactsActivity)getActivity()).isDialpadShown()) {            Log.d(TAG, "onClick but dialpad is not shown, skip !!!");            return;        }        /** @} */        switch (view.getId()) {            case R.id.dialpad_floating_action_button:                mHaptic.vibrate();                handleDialButtonPressed();                break;            case R.id.deleteButton: {                keyPressed(KeyEvent.KEYCODE_DEL);                break;            }            case R.id.digits: {                if (!isDigitsEmpty()) {                    mDigits.setCursorVisible(true);                }                break;            }            case R.id.dialpad_overflow: {                /// M: for plug-in @{                ExtensionManager.getInstance().getDialPadExtension().constructPopupMenu(                         mOverflowPopupMenu, mOverflowMenuButton, mOverflowPopupMenu.getMenu());                /// @}                mOverflowPopupMenu.show();                break;            }		//Added by duyuanfeng for Lenovo dialpad                case R.id.one: {                    keyPressed(KeyEvent.KEYCODE_1);                    break;                }                case R.id.two: {                    keyPressed(KeyEvent.KEYCODE_2);                    break;                }           // ......                case R.id.star: {                    keyPressed(KeyEvent.KEYCODE_STAR);                    break;                }		//End addition            default: {                Log.wtf(TAG, "Unexpected onClick() event from: " + view);                return;            }        }}

  这里看到,当我们在拨号键盘上点击某个View时,将会通过onClick()转换为标准的键盘消息,比如,在R.id.one控件上的点击,将会转换为KeyEvent.KEYCODE_1消息。然后在keyPressed()中将会把当前输入传递给编辑框:

 

 

DialpadFragment.java

 

private void keyPressed(int keyCode) {        if (getView() == null || getView().getTranslationY() != 0) {            return;        }        mHaptic.vibrate();        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);        mDigits.onKeyDown(keyCode, event);         // If the cursor is at the end of the text we hide it.        final int length = mDigits.length();        if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {            mDigits.setCursorVisible(false);        }		if(length >=128)//songhu add for cu320			clearDialpad();}

 mDigits.onKeyDown(keyCode, event)将内容传递给编辑框控件,mDigits就是编辑框控件。

1.2、从编辑框到搜索框

        搜索框的作用主要是,当拨号键盘隐藏时,显示当前的输入内容。而编辑框需要将当前的输入传递给搜索框。当编辑框检测到KeyDown事件后,就会将当前键盘的输入放入编辑框中,并触发TextWatcher的相关方法:

DialpadFragment.java

 

public void afterTextChanged(Editable input) {        .        if (!mDigitsFilledByIntent &&                SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {                        mDigits.getText().clear();        }         if (isDigitsEmpty()) {            mDigitsFilledByIntent = false;            mDigits.setCursorVisible(false);        }         if (mDialpadQueryListener != null) {//传递给mDialpadQueryListener            mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());        }         updateDeleteButtonEnabledState();    }

 在这里,又将当前已经输入的文本传递给mDialpadQueryListener,它是在DialtactsActivity.java中实现的

DialtactsActivity.java

public void onDialpadQueryChanged(String query) {        if (mSmartDialSearchFragment != null) {            mSmartDialSearchFragment.setAddToContactNumber(query);        }        final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,                /* M: [MTK Dialer Search] use mtk enhance dialpad map */                DialerFeatureOptions.isDialerSearchEnabled() ?                        SmartDialNameMatcher.SMART_DIALPAD_MAP                        : SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);         if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {            if (DEBUG) {                Log.d(TAG, "onDialpadQueryChanged - new query: " + query);            }            if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {                                if (!TextUtils.isEmpty(normalizedQuery)) {                    mPendingSearchViewQuery = normalizedQuery;                }                return;            }            //传递给搜索框            mSearchView.setText(normalizedQuery);        }}

 在onDialpadQueryChanged()中将当前编辑框的内容通过setText()方法传递给了mSearchView,也就是最上方的搜索框。

1.3、从搜索框到搜索结果列表Fragment

        搜索框下面的列表用于在搜索时显示搜索结果,他所处的位置是复用的,可以选择性的加载三种Fragment,当处于非搜索状态时,加载PhoneFavoriteFragment,这是进入拨号界面的默认加载项,将会显示瓦片式收藏界面,当在搜索模式时,将会加载SmartDialSearchFragment(拨号搜索,在拨号盘里输入号码呈现结果集的fragment)或者RegularSearchFragment(全局搜索,在actionbar的edittext里输入号码呈现结果集的fragment)用于显示当时的搜索结果。对于最常用的用户在拨号键盘输入内容触发的搜索,将会加载SmartDialSearchFragment。此时搜索框需要将要搜索的文本传递给SmartDialSearchFragment。
        在搜索时,由于搜索框注册了文本监听器,所以将会触发TextWatcher,此时需要暂存当前要搜索的文本,并进入搜索模式,然后再将搜索内容交给SmartDialSearchFragment。

 

DialtactsActivity.java

/*** Listener used to send search queries to the phone search fragment.*/private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {        @Override        public void beforeTextChanged(CharSequence s, int start, int count, int after) {        }          @Override        public void onTextChanged(CharSequence s, int start, int before, int count) {            final String newText = s.toString();            if (newText.equals(mSearchQuery)) {                // If the query hasn't changed (perhaps due to activity being destroyed                // and restored, or user launching the same DIAL intent twice), then there is                // no need to do anything here.                return;            }            if (DEBUG) {                Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText);                Log.d(TAG, "Previous Query: " + mSearchQuery);            }            mSearchQuery = newText;              // 当搜索的字符串为变成不为空的时候显示搜索界面            if (!TextUtils.isEmpty(newText)) {                // Call enterSearchUi only if we are switching search modes, or showing a search                // fragment for the first time.                final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||                        (!mIsDialpadShown && mInRegularSearch);                if (!sameSearchMode) {                    enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);                }            } //选择不同的搜索模式            if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {                mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);            } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {                mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);            }        }        @Override        public void afterTextChanged(Editable s) {        }};

在这里从搜索框进入到不同的SearchFragment,并将文本传递给SearchFragment;

1.4、从搜索列表的Fragment到Adapter

先来看一下SmartDialSearchFragment的继承关系:
    SmartDialSearchFragment
            ----SearchFragment
                ----PhoneNumberPickerFragment
                    ----ContactEntryListFragment<ContactEntryListAdapter>
                        ----Fragment
       SmartDialSearchFragment拿到搜索的文本后,需要传递给自己的Adapter才能完成搜索任务,我们现在来分析这个交接的过程。从上面1.3节中我们看到,SmartDialSearchFragment通过setQueryString()拿到了要搜索的字串,我们来查看这个方法,他是在SmartDialSearchFragment的父类ContactEntryListFragment中被实现的:

 

      ContactEntryListFragment.java

 

public void setQueryString(String queryString, boolean delaySelection) {        if (!TextUtils.equals(mQueryString, queryString)) {            if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {                if (TextUtils.isEmpty(mQueryString)) {                    // Restore the adapter if the query used to be empty.                    mListView.setAdapter(mAdapter);                } else if (TextUtils.isEmpty(queryString)) {                    // Instantly clear the list view if the new query is empty.                    mListView.setAdapter(null);                }            }             mQueryString = queryString;            setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);             if (mAdapter != null) {               //传递给Adapter                mAdapter.setQueryString(queryString);               //触发Adapter重新搜索                          reloadData();            }        }}

      在这里,Fragment将要搜索的文本通过setQueryString()的方法传递给当前的Adapter,然后通过reloadData()方法触发Adapter的搜索机制。那么这里的Adapter具体是指哪个呢?我们在SmartDialSearchFragment中找到了该Adapter的创建之处,他就是SmartDialNumberListAdapter:

 

SmartDialSearchFragment.java

 

@Override    protected ContactEntryListAdapter createListAdapter() {        SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());        adapter.setUseCallableUri(super.usesCallableUri());        adapter.setQuickContactEnabled(true);        // Set adapter's query string to restore previous instance state.        adapter.setQueryString(getQueryString());        return adapter;}

 

该Adapter的继承关系如下:

        SmartDialNumberListAdapter
            ----DialerPhoneNumberListAdapter
                ----PhoneNumberListAdapter
                    ----ContactEntryListAdapter
                        ----IndexerListAdapter
                            ----PinnedHeaderListAdapter
                                ----CompositeCursorAdapter
 接下来我们分析如何通过Fragment的reloadData()触发Adapter的搜索。
1.5、Adapter触发搜索机制
       刚才介绍到,SmartDialSearchFragment在setQueryString()时,通过reloadData()触发了Adapter的搜索,我们来看一下这个流程:
ContactEntryListFragment.java

protected void reloadData() {        removePendingDirectorySearchRequests();        mAdapter.onDataReload();        mLoadPriorityDirectoriesOnly = true;        mForceLoad = true;	    //触发新的Adapter        startLoading();    }    protected void startLoading() {        Log.d(TAG, "startLoading");        if (mAdapter == null) {            // The method was called before the fragment was started            Log.d(TAG, "[statLoading] mAdapter is null");            return;        }	    //配置Adapter要搜索的文本        configureAdapter();        int partitionCount = mAdapter.getPartitionCount();        for (int i = 0; i < partitionCount; i++) {            Partition partition = mAdapter.getPartition(i);            if (partition instanceof DirectoryPartition) {                DirectoryPartition directoryPartition = (DirectoryPartition)partition;                if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {                    if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {                        startLoadingDirectoryPartition(i);                    }                }            } else {                //通过LoaderManager进行异步查询                getLoaderManager().initLoader(i, null, this);            }        }        // Next time this method is called, we should start loading non-priority           directories        mLoadPriorityDirectoriesOnly = false;}

   在startLoading()时,通过configureAdapter()对当前的Adapter配置了要搜索的文本、排序方法以及显示主题等信息,由于

 

*partition instanceof DirectoryPartition = true

 

因此就会执行startLoadingDirectoryPartition()方法

 

ContactEntryListFragment.javaprivate void startLoadingDirectoryPartition(int partitionIndex) {        DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);        partition.setStatus(DirectoryPartition.STATUS_LOADING);        long directoryId = partition.getDirectoryId();        if (mForceLoad) {            if (directoryId == Directory.DEFAULT) {                loadDirectoryPartition(partitionIndex, partition);            } else {                loadDirectoryPartitionDelayed(partitionIndex, partition);            }        } else {            Bundle args = new Bundle();            args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);            getLoaderManager().initLoader(partitionIndex, args, this);        }}

 

然后就通过LoaderManager进行异步查询。我们来看Loader的流程: 经过initLoader()的操作之后,就会触发SmartDialSearchFragment中的onCreateLoader()方法:

SmartDialSearchFragments.java

public Loader
onCreateLoader(int id, Bundle args) { // Smart dialing does not support Directory Load, falls back to normal search instead. if (id == getDirectoryLoaderId()) { return super.onCreateLoader(id, args); } else { final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter(); /// M: [MTK Dialer Search] @{ if (DialerFeatureOptions.isDialerSearchEnabled()) { DialerSearchCursorLoader loader = new DialerSearchCursorLoader(super.getContext(), usesCallableUri()); adapter.configureLoader(loader); return loader; /// @} } else { //创建当前的CursorLoader,也就是SmartDialCursorLoader SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext()); adapter.configureLoader(loader); return loader; } }}

         由于DialerFeatureOptions.isDialerSearchEnabled()为true,因此这里创建了DialerSearchCursorLoader作为当前的CursorLoader。然后通过adapter的configureLoader()方法将该Loader传递给SmartDialNumberListAdapter,接下来就会在DialerSearchCursorLoader中完成异步查询,现在我们看一下在DialerSearchCursorLoader中的查询流程:

 

DialerSearchCursouLoader.java     /**     * Configures the query string to be used to find SmartDial matches.     * @param query The query string user typed.     */    public void configureQuery(String query, boolean isSmartQuery) {          Log.d(TAG, "MTK-DialerSearch, Configure new query to be " + query);          mQuery = query;	//搜索模式  isSmartQuery        if (!isSmartQuery) {            mQuery = DialerSearchUtils.stripTeleSeparators(query);        }	//判断字符串是否合法        if (!DialerSearchUtils.isValidDialerSearchString(mQuery)) {            mEnableDefaultSearch = true;        }    }      /**     * Queries the Contacts database and loads results in background.     * @return Cursor of contacts that matches the SmartDial query.     */    @Override    public Cursor loadInBackground() {         Log.d(TAG, "MTK-DialerSearch, Load in background. mQuery: " + mQuery);         final DialerSearchHelper dialerSearchHelper = DialerSearchHelper.getInstance(mContext);        Cursor cursor = null;        if (mEnableDefaultSearch) {            cursor = dialerSearchHelper.getRegularDialerSearchResults(mQuery, mUseCallableUri);        } else {            cursor = dialerSearchHelper.getSmartDialerSearchResults(mQuery);        }        if (cursor != null) {            Log.d(TAG, "MTK-DialerSearch, loadInBackground, result.getCount: "                    + cursor.getCount());             return cursor;        } else {            Log.w(TAG, "MTK-DialerSearch, ----cursor is null----");            return null;        }}

这段代码主要是查询联系人数据库,并在后台加载结果,是利用dialerSearchHelper.getSmartDialerSearchResults()得到cursor的。

@DialerSearchHelper.java/*** Query dialerSearch results from contactsProvider, use MTK algorithm.* @param query* @return DialerSearch result.*/public Cursor getSmartDialerSearchResults(String query) {        Log.d(TAG, "MTK-DialerSearch, getSmartDialerSearchResults, queryFilter: " + query);         if (TextUtils.isEmpty(query) || query.length() >= 128) {//songhu add for search anr            return null;        }         final ContentResolver resolver = mContext.getContentResolver();        Cursor cursor = null;        try {            int displayOrder = sContactsPrefs.getDisplayOrder();            int sortOrder = sContactsPrefs.getSortOrder();	    //设置Uri的路径            Uri baseUri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "dialer_search");	    //设置Uri的搜索文本            Uri dialerSearchUri = baseUri.buildUpon().appendPath(query).build();            Log.d(TAG, "MTK-DialerSearch, displayOrder: " + displayOrder + " ,sortOrder: "                    + sortOrder);	    //在Uri的path中加入两个键值对,并根据参数查询字符串            Uri dialerSearchParamUri = dialerSearchUri.buildUpon().appendQueryParameter(                    ContactsContract.Preferences.DISPLAY_ORDER, String.valueOf(displayOrder))                    .appendQueryParameter(ContactsContract.Preferences.SORT_ORDER,                            String.valueOf(sortOrder)).build();             cursor = resolver.query(dialerSearchParamUri, null, null, null, null);             Log.d(TAG, "liuhuan DISPLAY_ORDER= " + String.valueOf(displayOrder)+"SORT_ORDER ="+String.valueOf(sortOrder));  	    Log.d(TAG, "liuhuan DISPLAY_ORDER= " + ContactsContract.Preferences.DISPLAY_ORDER+"SORT_ORDER ="+ContactsContract.Preferences.SORT_ORDER);             Log.d(TAG, "MTK-DialerSearch, cursor.getCount: " + cursor.getCount());             return cursor;        } catch (Exception e) {            Log.w(TAG, "Exception thrown in MTK-DialerSearch, getSmartDialerSearchResults", e);             if (cursor != null) {                cursor.close();                cursor = null;            }            return null;        }}

LOG:dialerSearchParamUri= content://com.android.contacts/dialer_search/5?android.contacts.DISPLAY_ORDER=1&android.contacts.SORT_ORDER=1

appendQueryParameter(String ,String);这个方法的官方解释为:Encodes the key and value and then appends the parameter to the querystring.官方解释链接:点击打开链接,我的理解就是在Uri中将加入一个键值对如(name,faker);就查询name是faker的数据,最后查询的时候是调用resolver.query()。resolver 是通过getContentResolver得来的,ContentResolver是直译为内容解析器,在android中程序间的数据共享是通过Provider/Reslover,提供数据(内容)的就是Provider,Reslover就提供接口对这个数据进行解读,根据Android官方文档,query方法的解释为:

public final Cursor query (Uri uri, String[] projection,String selection,String[] selectionArgs, StringsortOrder){}

第一个参数为 Uri,android中有很多reslover,为了区分这些reslover,就需要每个reslover都有一个独有的标识,而这个Uri就是这个标识;

第二个参数为 projection,就是要获取到reslover中的数据的哪些内容,必须联系人有name和id,如果只想得到联系人的name,那么就可以设置这个参数:当设置为null的时候就是获取reslover中的所有内容;

第三个参数为 selection :设置条件,比如,我只想得到reslover中,联系人为Faker的相关信息;

第四个参数为 selectionArgs:这个是配合第三个参数使用的,如果第三个参数中有?,那么第四个参数就会替换第三个参数;

第五个参数为 sortOrder:这个是设置reslover中的数据按照什么排序;

参考文档:点击打开链接
 

public void onLoadFinished(Loader
loader, Cursor data) { Log.d(TAG, "[onLoadFinished] loader:" + loader + ",data:" + data); /// M: check whether the fragment still in Activity @{ if (!isAdded()) { Log.d(TAG, "onLoadFinished(),This Fragment is not add to the Activity now.data:" + data); return; } /// @} if (!mEnabled) { Log.d(TAG, "return in onLoad finish,mEnabled:" + mEnabled); return; } int loaderId = loader.getId(); if (loaderId == DIRECTORY_LOADER_ID) { mDirectoryListStatus = STATUS_LOADED; mAdapter.changeDirectories(data); Log.d(TAG, "onLoadFinished startloading,loaderId:" + loaderId); startLoading(); } else { onPartitionLoaded(loaderId, data); if (isSearchMode()) { int directorySearchMode = getDirectorySearchMode(); if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) { if (mDirectoryListStatus == STATUS_NOT_LOADED) { mDirectoryListStatus = STATUS_LOADING; getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this); } else { startLoading(); } } } else { mDirectoryListStatus = STATUS_NOT_LOADED; getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID); }

最后将查询结果传递给ContactEntryListFragment的onLoadFinished()方法:在onLoadFinished()中,通过onPartitionLoaded()对当前的Adapter所使用的Cursor进行更新,从而刷新列表。

下面总结一下搜索的流程说明:

转载地址:https://liwangjiang.blog.csdn.net/article/details/90047200 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Android ContentProvider(Android内容提供者,跨进程通讯)
下一篇:Android Dialer--通讯整体过程分析

发表评论

最新留言

不错!
[***.144.177.141]2024年04月23日 07时48分37秒