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.javapublic 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 dialpadcase 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 additiondefault: {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 cu320clearDialpad();
}

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) {
//传递给mDialpadQueryListenermDialpadQueryListener.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() {@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic 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 */);}}@Overridepublic 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) {//传递给AdaptermAdapter.setQueryString(queryString);//触发Adapter重新搜索           reloadData();}}
}

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

SmartDialSearchFragment.java

@Overrideprotected 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;//触发新的AdapterstartLoading();}protected void startLoading() {Log.d(TAG, "startLoading");if (mAdapter == null) {// The method was called before the fragment was startedLog.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 directoriesmLoadPriorityDirectoriesOnly = false;
}

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

*partition instanceof DirectoryPartition = true

因此就会执行startLoadingDirectoryPartition()方法

ContactEntryListFragment.java
private 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<Cursor> 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,也就是SmartDialCursorLoaderSmartDialCursorLoader 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;//搜索模式  isSmartQueryif (!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.*/@Overridepublic 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 anrreturn 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<Cursor> 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进行更新,从而刷新列表。

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

Android Dialer模块联系人搜索相关推荐

  1. Android:MTK的Dialer模块联系人搜索

    MTK的Dialer模块联系人搜索 拨号搜索机制分为两个部分:引导搜索和搜索.其中引导搜索是指,从用户输入到开始搜索之间的流程,而搜索部分是指,从数据库搜索字符串的过程. 一.引导搜索部分 默认的拨号 ...

  2. Android通讯录模糊查询搜索(号码,姓名,首字母简拼,全拼),批量选取联系人

    Android通讯录模糊查询搜索(号码,姓名,首字母简拼,全拼),批量选取联系人 公司最近的项目中遇到一个需求需要读取通讯录联系人,并需要支持对联系人的模糊查询及批量选取,网上找了好几个例子,模糊查询 ...

  3. Android Dialer,Mms,Contacts源码修改笔记,移动端混合开发经验

    ②在AndroidManifest.xml中修改相应Activity的theme <activity android:name=".HomeActivity" android ...

  4. Android Dialer,Mms(1),高并发系统基础篇

    33.使用ComponentName启动另一个应用的Activity时出现java.lang.SecurityException: Permission Denial的解决方案: 原因分析: 在SDK ...

  5. Android Dialer,Mms,Android开发指南

    long contactId = Long.parseLong(str); //Toast.makeText(this, "contactId:"+contactId, 2).sh ...

  6. android 功能模块之通讯模块四

     Android通讯录开发之通讯录联系人搜索功能最新实现 2014年1月13日 之前的有两篇博客介绍了如何解决通讯录搜索功能的问题,那些方法都是从网上搜集,然后经过自己整理试验之后的,但在项目测试 ...

  7. android 详细构建过程,Android构建模块详细步骤概述

    大家可能还记得,我们51CTO在一篇文章中介绍过有关Android模块的一些基本概念,相信大家应该可以对这些模块有一个深刻的认识了.在这里,我们会分步骤详细为大家讲解一下Android构建模块的相关知 ...

  8. android恢复联系人,如何从Android手机恢复联系人[最佳方式]

    有没有简单的方法 从Android设备恢复已删除/丢失的联系人? 各种原因将导致Android手机上的数据丢失. 例如,意外删除可能会导致电话号码丢失,而一些错误的操作也可能导致联系人丢失. 此外,技 ...

  9. Android通讯录模糊匹配搜索实现 号码 首字母 简拼 全拼

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! Andr ...

最新文章

  1. (004) java后台开发之Eclipse(Neon) 版本安装Java EE插件
  2. 【Groovy】Groovy 代码创建 ( 使用 Java 语法实现 Groovy 类和主函数并运行 | 按照 Groovy 语法改造上述 Java 语法规则代码 )
  3. 【转】【C++】__stdcall、__cdcel和__fastcall三者的区别
  4. 【python之路24】装饰器
  5. halcon算子盘点:Chapter 12:OCR(光字符识别)
  6. python自定义函数详解_Python自定义函数
  7. java model 中文乱码,java传值乱码解决方法
  8. 图像处理——在Python中使用OpenCV显示图像
  9. L1-051 打折 (5 分)—团体程序设计天梯赛
  10. pt-table-checksum
  11. 全国计算机软考机试试题,软考机试试题.doc
  12. Java架构师发展路线
  13. 华硕BIOS TPM设置
  14. T.38 和传真的未来
  15. 计算机网络的利与弊,网络的利与弊辩论会总结.doc
  16. 常见电脑硬件故障有哪些?如何解决?~~~主板故障
  17. 【医学图像分割】CT医学图像的预处理(重采样)
  18. 64B/66B编码技术
  19. 包嗅探和包回放简介-tcpdump,tcpreplay
  20. php 手机号码三要素实名查询

热门文章

  1. EXFO 光时反射仪MAX-730C-SM1基本规格
  2. iOS CGContext实现绘制任意圆角
  3. Vmware安装debian9虚拟机
  4. 计算机excel中累计如何计算,『EXCEL表中,累计计算公式如何设置』excel分段累计求和...
  5. python----列表 例题 创建一个列表,命名为foods, 在列表中保存5个食物士豆,西红柿面条,苹果,披萨并打印出每一个食物的名字
  6. Exception in thread “main“ java.lang.ClassCastException: test.SuperClass cannot be cast to test.SubC
  7. 高音质游戏无线蓝牙耳机推荐,低延迟吃鸡王者兼容无压力
  8. 传奇3服务器配置文件,服务器技术交流_GowLom2战神引擎GameServer配置文件说明_-921根据地_只做有质量的游戏 - Powered by Discuz!...
  9. 《乔布斯传》阅读感悟
  10. iOS开发——网络请求案例汇总(AFNetworking)