SettingsIntelligence
Android Settings 系列文章:
- Android Settings解析
- SettingsIntelligence
- SettingsProvider
首语
Android Settings中搜索功能帮助我们可以快速访问设置项,进行自定义设置,以得到更佳的使用体验。Android Settings搜索的实现实际不在Settings模块里,而是存在一个单独的模块—SettingsIntelligence,它里面实现了Settings的核心搜索功能,因此,学习SettingsIntelligence搜索实现可以让我们更多了解Settings模块。
搜索实现流程
本文以Android 13 SettingsIntelligence模块源码进行分析。
首先搜索栏的跳转实现在SearchFeatureProvider的initSearchToolbar中,initSearchToolbar在Android Settings解析文章分析过,在SettingsHomepageActivity的initSearchBarView方法中调用。最终跳转到包名为com.android.settings.intelligence,action为android.settings.APP_SEARCH_SETTINGS的页面中。
public interface SearchFeatureProvider {default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) {...final Intent intent = buildSearchIntent(context, pageId).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);...toolbar.setOnClickListener(tb -> {FeatureFactory.getFactory(context).getSlicesFeatureProvider().indexSliceDataAsync(context);FeatureFactory.getFactory(context).getMetricsFeatureProvider().logSettingsTileClick(KEY_HOMEPAGE_SEARCH_BAR, pageId);final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle();activity.startActivity(intent, bundle);});}
}
它对应的模块为SettingsIntelligence,模块路径:packages/apps/SettingsIntelligence。从AndroidManifest.xml可以看到,Settings跳转搜索的页面为SearchActivity,SearchActivity添加SearchFragment,在SearchFragment中实现了搜索的核心逻辑。
查看onCreate方法,进行了一些变量的初始化,onCreateView方法中进行view初始化,设置布局为search_panel,我们只需要关注搜索框控件SearchView,设置查询字符串为mQuery,即输入搜索的内容。
设置查询监听,重写onQueryTextSubmit和onQueryTextChange方法。当搜索框文本改变时,通过restartLoaders方法调用LoadManager开启加载数据流程。当Loader创建成功时,回调onCreateLoader方法,调用getSearchResultLoader方法来SearchResultLoader实例。
public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener,LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {... mSearchView = toolbar.findViewById(R.id.search_view);mSearchView.setQuery(mQuery, false /* submitQuery */);mSearchView.setOnQueryTextListener(this);mSearchView.requestFocus();return view;} @Overridepublic boolean onQueryTextChange(String query) {...if (isEmptyQuery) {final LoaderManager loaderManager = getLoaderManager();loaderManager.destroyLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT);mShowingSavedQuery = true;mSavedQueryController.loadSavedQueries();mSearchFeatureProvider.hideFeedbackButton(getView());} else {mMetricsFeatureProvider.logEvent(SettingsIntelligenceEvent.PERFORM_SEARCH);restartLoaders();}return true;}@Overridepublic boolean onQueryTextSubmit(String query) {// Save submitted query.mSavedQueryController.saveQuery(mQuery);hideKeyboard();return true;}private void restartLoaders() {mShowingSavedQuery = false;final LoaderManager loaderManager = getLoaderManager();loaderManager.restartLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT,null /* args */, this /* callback */);}@Overridepublic Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {final Activity activity = getActivity();switch (id) {case SearchCommon.SearchLoaderId.SEARCH_RESULT:return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);default:return null;}}
}
在SearchFeatureProvider实现类SearchFeatureProviderImpl中创建了SearchResultLoader实例,SearchResultLoader在子线程进行数据查找。
public class SearchResultLoader extends AsyncLoader<List<? extends SearchResult>> {private final String mQuery;public SearchResultLoader(Context context, String query) {super(context);mQuery = query;}@Overridepublic List<? extends SearchResult> loadInBackground() {SearchResultAggregator aggregator = SearchResultAggregator.getInstance();return aggregator.fetchResults(getContext(), mQuery);}
}
fetchResults方法进行数据查找,并创建了一个tasks集合,然后变量tasks,保存到taskResults中。
public class SearchResultAggregator {@NonNullpublic synchronized List<? extends SearchResult> fetchResults(Context context, String query) {final SearchFeatureProvider mFeatureProvider = FeatureFactory.get(context).searchFeatureProvider();final ExecutorService executorService = mFeatureProvider.getExecutorService();final List<SearchQueryTask> tasks =mFeatureProvider.getSearchQueryTasks(context, query);// Start tasksfor (SearchQueryTask task : tasks) {executorService.execute(task);}// Collect resultsfinal Map<Integer, List<? extends SearchResult>> taskResults = new ArrayMap<>();final long allTasksStart = System.currentTimeMillis();for (SearchQueryTask task : tasks) {final int taskId = task.getTaskId();try {taskResults.put(taskId,task.get(SHORT_CHECK_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS));} catch (TimeoutException | InterruptedException | ExecutionException e) {Log.d(TAG, "Could not retrieve result in time: " + taskId, e);taskResults.put(taskId, Collections.EMPTY_LIST);}}// Merge resultsfinal List<? extends SearchResult> mergedResults = mergeSearchResults(taskResults);return mergedResults;}
}
getSearchQueryTasks中构建了各种类型的task,如DatabaseResultTask/InstalledAppResultTask等等。这些task都继承于SearchQueryTask.QueryWorker。
public class SearchFeatureProviderImpl implements SearchFeatureProvider {@Overridepublic List<SearchQueryTask> getSearchQueryTasks(Context context, String query) {final List<SearchQueryTask> tasks = new ArrayList<>();final String cleanQuery = cleanQuery(query);tasks.add(DatabaseResultTask.newTask(context, getSiteMapManager(), cleanQuery));tasks.add(InstalledAppResultTask.newTask(context, getSiteMapManager(), cleanQuery));tasks.add(AccessibilityServiceResultTask.newTask(context, getSiteMapManager(), cleanQuery));tasks.add(InputDeviceResultTask.newTask(context, getSiteMapManager(), cleanQuery));return tasks;}
}
而SearchQueryTask又继承于FutureTask,call方法去处理任务,完成后返回结果。
public class SearchQueryTask extends FutureTask<List<? extends SearchResult>> {public static abstract class QueryWorker implements Callable<List<? extends SearchResult>> {@Overridepublic List<? extends SearchResult> call() throws Exception {final long startTime = System.currentTimeMillis();try {return query();} finally {final long endTime = System.currentTimeMillis();FeatureFactory.get(mContext).metricsFeatureProvider(mContext).logEvent(getQueryWorkerId(), endTime - startTime);}}}
}
我们以DatabaseResultTask为例,查看它实现的query方法。query方法通过一系列的查询方法将数据添加到resultSet中,可以看到query方法中获取SQLite数据库实例,IndexDatabaseHelper中初始化数据库,可以看到数据库名为search_index.db,表名和表字段。最后通过query方法查询数据。
public class DatabaseResultTask extends SearchQueryTask.QueryWorker {public static SearchQueryTask newTask(Context context, SiteMapManager siteMapManager,String query) {return new SearchQueryTask(new DatabaseResultTask(context, siteMapManager, query));}@Overrideprotected List<? extends SearchResult> query() {if (mQuery == null || mQuery.isEmpty()) {return new ArrayList<>();}// Start a Future to get search result scores.FutureTask<List<Pair<String, Float>>> rankerTask = mFeatureProvider.getRankerTask(mContext, mQuery);if (rankerTask != null) {ExecutorService executorService = mFeatureProvider.getExecutorService();executorService.execute(rankerTask);}final Set<SearchResult> resultSet = new HashSet<>();resultSet.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));resultSet.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));resultSet.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));resultSet.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));// Try to retrieve the scores in time. Otherwise use static ranking.if (rankerTask != null) {try {final long timeoutMs = mFeatureProvider.smartSearchRankingTimeoutMs(mContext);List<Pair<String, Float>> searchRankScores = rankerTask.get(timeoutMs,TimeUnit.MILLISECONDS);return getDynamicRankedResults(resultSet, searchRankScores);} catch (TimeoutException | InterruptedException | ExecutionException e) {Log.d(TAG, "Error waiting for result scores: " + e);}}List<SearchResult> resultList = new ArrayList<>(resultSet);Collections.sort(resultList);return resultList;}private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {final String whereClause = buildSingleWordWhereClause(matchColumns);final String query = mQuery + "%";final String[] selection = buildSingleWordSelection(query, matchColumns.length);return query(whereClause, selection, baseRank);}private Set<SearchResult> query(String whereClause, String[] selection, int baseRank) {final SQLiteDatabase database =IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();//查询搜索数据try (Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS,whereClause,selection, null, null, null)) {return mConverter.convertCursor(resultCursor, baseRank, mSiteMapManager);}}
}
那么问题来了,Settings搜索数据存储在SQLite数据库中,我们分析了它的查询流程,那么它是如何存储的呢?
其实在SearchFragment的onCreate就有实现,通过updateIndexAsync刷新数据。
public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener,LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {...mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */);
}
通过indexDatabase方法更新数据。
public class SearchFeatureProviderImpl implements SearchFeatureProvider {@Overridepublic void updateIndexAsync(Context context, IndexingCallback callback) {if (DEBUG) {Log.d(TAG, "updating index async");}getIndexingManager(context).indexDatabase(callback);}
}
IndexingTask继承于AsyncTask。异步执行performIndexing方法,通过queryIntentContentProviders方法获取ContentProvider,然后根据provider查找数据,更新到数据库中。看下intent指定的action PROVIDER_INTERFACE为"android.content.action.SEARCH_INDEXABLES_PROVIDER",在Settings查找是否有定义此action的ContentProvider。
public class DatabaseIndexingManager {public void indexDatabase(IndexingCallback callback) {IndexingTask task = new IndexingTask(callback);task.execute();}public class IndexingTask extends AsyncTask<Void, Void, Void> {@VisibleForTestingIndexingCallback mCallback;private long mIndexStartTime;public IndexingTask(IndexingCallback callback) {mCallback = callback;}@Overrideprotected void onPreExecute() {mIndexStartTime = System.currentTimeMillis();mIsIndexingComplete.set(false);}@Overrideprotected Void doInBackground(Void... voids) {performIndexing();return null;}@Overrideprotected void onPostExecute(Void aVoid) {int indexingTime = (int) (System.currentTimeMillis() - mIndexStartTime);FeatureFactory.get(mContext).metricsFeatureProvider(mContext).logEvent(SettingsIntelligenceLogProto.SettingsIntelligenceEvent.INDEX_SEARCH,indexingTime);mIsIndexingComplete.set(true);if (mCallback != null) {mCallback.onIndexingFinished();}}}public void performIndexing() {final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);final List<ResolveInfo> providers =mContext.getPackageManager().queryIntentContentProviders(intent, 0);final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, providers);if (isFullIndex) {rebuildDatabase();}PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);final long updateDatabaseStartTime = System.currentTimeMillis();updateDatabase(indexData, isFullIndex);IndexDatabaseHelper.setIndexed(mContext, providers);if (DEBUG) {final long updateDatabaseTime = System.currentTimeMillis() - updateDatabaseStartTime;Log.d(TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);}}
}
可以发现,在Settings的AndroidManifest.xml中指定一个Provider。
<providerandroid:name=".search.SettingsSearchIndexablesProvider"android:authorities="com.android.settings"android:multiprocess="false"android:grantUriPermissions="true"android:permission="android.permission.READ_SEARCH_INDEXABLES"android:exported="true"><intent-filter><action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" /></intent-filter></provider>
SettingsSearchIndexablesProvider继承于SearchIndexablesProvider,SearchIndexablesProvider继承于ContentProvider, query方法进行了分类查询,插入,删除,更新均不支持,通过final修饰和抛出UnsupportedOperationException屏蔽了。
public abstract class SearchIndexablesProvider extends ContentProvider {@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder) {try {switch (mMatcher.match(uri)) {case MATCH_RES_CODE:return queryXmlResources(null);case MATCH_RAW_CODE:return queryRawData(null);case MATCH_NON_INDEXABLE_KEYS_CODE:return queryNonIndexableKeys(null);case MATCH_SITE_MAP_PAIRS_CODE:return querySiteMapPairs();case MATCH_SLICE_URI_PAIRS_CODE:return querySliceUriPairs();case MATCH_DYNAMIC_RAW_CODE:return queryDynamicRawData(null);default:throw new UnsupportedOperationException("Unknown Uri " + uri);}} catch (UnsupportedOperationException e) {throw e;} catch (Exception e) {Log.e(TAG, "Provider querying exception:", e);return null;}}@Overridepublic final Uri insert(Uri uri, ContentValues values) {throw new UnsupportedOperationException("Insert not supported");}
}
以queryXmlResources为例,通过getSearchIndexableResourcesFromProvider方法获取数据集合,并保存到cursor中。bundles里一个class类型的集合。
public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {@Overridepublic Cursor queryXmlResources(String[] projection) {final MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);final List<SearchIndexableResource> resources =getSearchIndexableResourcesFromProvider(getContext());for (SearchIndexableResource val : resources) {final Object[] ref = new Object[INDEXABLES_XML_RES_COLUMNS.length];ref[COLUMN_INDEX_XML_RES_RANK] = val.rank;ref[COLUMN_INDEX_XML_RES_RESID] = val.xmlResId;ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = val.className;ref[COLUMN_INDEX_XML_RES_ICON_RESID] = val.iconResId;ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = val.intentAction;ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = val.intentTargetPackage;ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target classcursor.addRow(ref);}return cursor;}private List<SearchIndexableResource> getSearchIndexableResourcesFromProvider(Context context) {final Collection<SearchIndexableData> bundles = FeatureFactory.getFactory(context).getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();List<SearchIndexableResource> resourceList = new ArrayList<>();for (SearchIndexableData bundle : bundles) {Indexable.SearchIndexProvider provider = bundle.getSearchIndexProvider();final List<SearchIndexableResource> resList =provider.getXmlResourcesToIndex(context, true);if (resList == null) {continue;}for (SearchIndexableResource item : resList) {item.className = TextUtils.isEmpty(item.className)? bundle.getTargetClass().getName(): item.className;}resourceList.addAll(resList);}return resourceList;}
}
SearchIndexableResourcesMobile继承于SearchIndexableResourcesBase,
public class SearchFeatureProviderImpl implements SearchFeatureProvider {@Overridepublic SearchIndexableResources getSearchIndexableResources() {if (mSearchIndexableResources == null) {mSearchIndexableResources = new SearchIndexableResourcesMobile();}return mSearchIndexableResources;}
}
SearchIndexableResourcesMobile类生成在IndexableProcessor中,IndexableProcessor设置的注解为SearchIndexable,SearchIndexable注解可以指定target(ALL/MOBILE/TV/WEAR/AUTO/ARC)对应不同平台。通过JavaPoet库来addCode实例化SearchIndexableData,getProviderValues方法返回的是带有SearchIndexable注解的所有类集合。
@SupportedSourceVersion(SourceVersion.RELEASE_9)
@SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
public class IndexableProcessor extends AbstractProcessor {@Overridepublic boolean process(Set<? extends TypeElement> annotations,for (Element element : roundEnvironment.getElementsAnnotatedWith(SearchIndexable.class)) {if (element.getKind().isClass()) {Name className = element.accept(new SimpleElementVisitor8<Name, Void>() {@Overridepublic Name visitType(TypeElement typeElement, Void aVoid) {return typeElement.getQualifiedName();}}, null);if (className != null) {SearchIndexable searchIndexable = element.getAnnotation(SearchIndexable.class);int forTarget = searchIndexable.forTarget();MethodSpec.Builder builder = baseConstructorBuilder;if (forTarget == SearchIndexable.ALL) {builder = baseConstructorBuilder;} else if ((forTarget & SearchIndexable.MOBILE) != 0) {builder = mobileConstructorBuilder;} else if ((forTarget & SearchIndexable.TV) != 0) {builder = tvConstructorBuilder;} else if ((forTarget & SearchIndexable.WEAR) != 0) {builder = wearConstructorBuilder;} else if ((forTarget & SearchIndexable.AUTO) != 0) {builder = autoConstructorBuilder;} else if ((forTarget & SearchIndexable.ARC) != 0) {builder = arcConstructorBuilder;}//实例化SearchIndexableDatabuilder.addCode("$N(new SearchIndexableData($L.class, $L"+ ".SEARCH_INDEX_DATA_PROVIDER));\n",addIndex, className, className);...}}inal MethodSpec getProviderValues = MethodSpec.methodBuilder("getProviderValues").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC).returns(ParameterizedTypeName.get(ClassName.get(Collection.class),searchIndexableData)).addCode("return $N;\n", providers).build();final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE).addModifiers(Modifier.PUBLIC).addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources")).addField(providers).addMethod(baseConstructorBuilder.build()).addMethod(addIndex).addMethod(getProviderValues).build();final JavaFile searchIndexableResourcesBase = JavaFile.builder(PACKAGE, baseClass).build();final JavaFile searchIndexableResourcesMobile = JavaFile.builder(PACKAGE,TypeSpec.classBuilder(CLASS_MOBILE).addModifiers(Modifier.PUBLIC).superclass(ClassName.get(PACKAGE, baseClass.name)).addMethod(mobileConstructorBuilder.build()).build()).build();
}
实例化SearchIndexableData,mTargetClass为className.class,mSearchIndexProvider为className.SEARCH_INDEX_DATA_PROVIDER,其中的className就是对应添加SearchIndexable注解的类名
public class SearchIndexableData {public SearchIndexableData(Class targetClass, Indexable.SearchIndexProvider provider) {mTargetClass = targetClass;mSearchIndexProvider = provider;}
}
总结一下,Settings搜索功能就是在需要被提供的页面添加@SearchIndexable注解,在这页面创建一个常量SEARCH_INDEX_DATA_PROVIDER,这个常量类型必须为Indexable.SearchIndexProvider。以TopLevelSettings为例。添加了@SearchIndexable注解,指定Target为MOBILE,也创建了SEARCH_INDEX_DATA_PROVIDER,Settings封装了一个基础的SearchIndexProvider,不返回任何要索引的数据,类名为BaseSearchIndexProvider。
@SearchIndexable(forTarget = MOBILE)
public class TopLevelSettings extends DashboardFragment implements SplitLayoutListener,PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =new BaseSearchIndexProvider(R.xml.top_level_settings) {@Overrideprotected boolean isPageSearchEnabled(Context context) {// Never searchable, all entries in this page are already indexed elsewhere.return false;}};
}
SearchIndexProvider和BaseSearchIndexProvider扩展的方法可以让我们准确处理菜单搜索需求。
public class BaseSearchIndexProvider implements Indexable.SearchIndexProvider {public BaseSearchIndexProvider() {}public BaseSearchIndexProvider(int xmlRes) {mXmlRes = xmlRes;}//返回SearchIndexableResource@Overridepublic List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) {if (mXmlRes != 0) {final SearchIndexableResource sir = new SearchIndexableResource(context);sir.xmlResId = mXmlRes;return Arrays.asList(sir);}return null;}//返回SearchIndexableRaw集合@Overridepublic List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {return null;}//返回动态数据集合@Overridepublic List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {return null;}//无法搜索的集合@Override@CallSuperpublic List<String> getNonIndexableKeys(Context context) {...}//页面是否启用搜索protected boolean isPageSearchEnabled(Context context) {return true;}//获取xml设置禁用搜索的集合@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)public List<String> getNonIndexableKeysFromXml(Context context, @XmlRes int xmlResId,boolean suppressAllPage) {return getKeysFromXml(context, xmlResId, suppressAllPage);}
}
以上就是Settings的搜索逻辑。要测试新菜单的可搜索性,需要先清除Settings数据,让数据库重新添加数据。
总结
Settings菜单如果想要支持搜索,首先对应页面需要添加@SearchIndexable注解,其次在本页面创建一个常量SEARCH_INDEX_DATA_PROVIDER,然后根据需要重写需要的实现方法。这样这个菜单就支持搜索了。
SettingsIntelligence会扫描这些添加@SearchIndexable注解的页面,将这些页面的菜单添加到数据库中,查询时根据关键词进行匹配查询。
相关文章:
SettingsIntelligence
Android Settings 系列文章: Android Settings解析SettingsIntelligenceSettingsProvider 首语 Android Settings中搜索功能帮助我们可以快速访问设置项,进行自定义设置,以得到更佳的使用体验。Android Settings搜索的实现实际不在Setting…...
C#WPF Prism框架区域管理应用实例
本文实例演示C#WPFPrism框架区域管理应用实例 目录 一、Prism框架区域 二、不使用Prism框架的RegionManager 三、使用Prism框架的RegionManager 一、Prism框架区域...

LabVIEW基于机器视觉的钢轨表面缺陷检测系统
LabVIEW基于机器视觉的钢轨表面缺陷检测系统 机器视觉检测技术和LabVIEW软件程序,可以实现轨道工件的表面质量。CMOS彩色工业相机采集的图像通过图像预处理、图像阈值分割、形态分析、特征定位和图案匹配进行处理和分析。图形显示界面采用LabVIEW软件编程设计&…...

Qt程序的发布和打包,任何电脑都可以安装
## 1. Qt程序的发布 当Qt程序编写完成通过IDE编译就可以得到对应的可执行程序,这个可执行程序在本地运行是完全没有问题的(因为在本地有Qt环境,程序运行过程中可以加载到相关的动态库),但是如果我们想把这个Qt程序给到其他小伙伴使用可能就会出问题了,原因如下: 对方电…...

MD5生成和校验
MD5生成和校验 2021年8月19日席锦 任何类型的一个文件,它都只有一个MD5值,并且如果这个文件被修改过或者篡改过,它的MD5值也将改变。因此,我们会对比文件的MD5值,来校验文件是否是有被恶意篡改过。 什么是MD5ÿ…...
PostgreSQL 正则表达式匹配字段
在 PostgreSQL 数据库中,可以使用 ~ 和 !~ 操作符进行正则表达式的匹配和否定匹配。还可以使用 :: 操作符进行正则表达式的模式匹配。 例如,假设我们有一个名为 users 的表,其中有一个名为 email 的字段,我们可以使用以下 SQL 语句…...

关于iterm2的美化
iterm2 美化 笔者公司最近给发了一个新 M1 mac pro,所以一些软件需要重新安装。其中比较麻烦就是iterm2的一个美化工程 , 由于每次安装的效果都不尽相同所以这次写一个博客来记录一下 安装的过程 。 全程高能开始: 使用brew 来安装 iterm2 …...

Hook原理--逆向开发
今天我们将继续讲解逆向开发工程另一个重要内容--Hook原理讲解。Hook,可以中文译为“挂钩”或者“钩子”,逆向开发中改变程序运行的一种技术。按照如下过程进行讲解 Hook概述Hook技术方式fishhook原理及实例符号表查看函数名称总结 一、Hook概述 在逆…...

做数据可视化,谨记三大要点
数据可视化报表就是“一图胜千言”的最佳例子。数据可视化,也就是将数据图形化、图表化,以良好的视觉效果呈现数据,达到发现、分析、预测、监控、决策等目的。要想做出一份优秀的数据可视化报表,那就要在做报表时谨记三大要点&…...
软件设计原则-接口隔离原则讲解以及代码示例
接口隔离原则 一,介绍 1.前言 接口隔离原则(Interface Segregation Principle,ISP)是面向对象设计中的一个原则,提倡使用多个专门的接口,而不使用单一的大接口。它最早由Robert C. Martin在其《敏捷软件…...

yolov8x-p2 实现 tensorrt 推理
简述 在最开始的yolov8提供的不同size的版本,包括n、s、m、l、x(模型规模依次增大,通过depth, width, max_channels控制大小),这些都是通过P3、P4和P5提取图片特征; 正常的yolov8对象检测模型输出层是P3、…...
Type Script的变量类型
Typescript 的重要特性之一就是数据有类型了。 常见的类型如:字符串、数值、布尔等都有了明确的定义。 变量声明的格式 let 变量名:类型 初始值;字符型 let str:string "abc";数值型 数值型也支持不同的进制,用前缀区分 支持 整…...

系统架构师备考倒计时13天(每日知识点)
1. 数据仓库四大特点 面向主题的。操作型数据库的数据组织面向事务处理任务,各个业务系统之间各自分离,而数据仓库中的数据是按照一定的主题域进行组织的。集成的。数据仓库中的数据是在对原有分散的数据库数据抽取、清理的基础上经过系统加工、汇总和整…...
20 | Spring Data JPA 中文文档
Spring Data JPA 中文文档 1. 前言 Spring Data JPA 为 Jakarta Persistence API(JPA)提供 repository 支持。它简化了需要访问JPA数据源的应用程序的开发。 1.1. 项目元数据 版本控制: https://github.com/spring-projects/spring-data-jpaBug跟踪:…...

【AOA-VMD-LSTM分类故障诊断】基于阿基米德算法AOA优化变分模态分解VMD的长短期记忆网络LSTM分类算法(Matlab代码)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
K8s:Pod 中 command、args 与 Dockerfile 中 CMD、 ENTRYPOINT 的对应关系
写在前面 前几天被问到,这里整理笔记之前也没怎么注意这个问题理解不足小伙伴帮忙指正 曾以为老去是很遥远的事,突然发现年轻是很久以前的事了。时光好不经用,抬眼已是半生,所谓的中年危机,真正让人焦虑的不是孤单、不…...

Visual Studio Code (VS Code)安装教程
Visual Studio Code(简称“VS Code”)。 1.下载安装包 VS Code的官网: Visual Studio Code - Code Editing. Redefined 首先提及一下,vscode是不需要破解操作的; 第一步,看好版本,由于我的系…...
技巧 | 如何解决 zsh: permission denied 问题 | Mac
技巧 | 如何解决 zsh: permission denied 问题 | Mac 问题描述 在 macOS 系统终端执行 sh 程序脚本时,抛出异常 zsh: permission denied 原因分析 用户没有权限,所以才出现了这个错误,所以只需要用 chmod 修改一下权限就可以了 解决方法…...

【JavaEE】线程安全的集合类 -- 多线程篇(9)
线程安全的集合类 多线程环境使用 ArrayList多线程环境使用队列多线程环境使用哈希表 多线程环境使用 ArrayList 自己使用同步机制 (synchronized 或者 ReentrantLock)Collections.synchronizedList(new ArrayList); synchronizedList 是标准库提供的一个基于 synchronized 进…...

【MySQL架构篇】MySQL字符集、大小写规范及默认数据库
文章目录 1. 字符集与字符集比较规则2. 大小写规范3. 默认数据库4. 与文件系统相关 1. 字符集与字符集比较规则 MySQL有4个级别的字符集和比较规则,分别是 服务器级别数据库级别表级别列级别 当创建对应表或列未指定字符集时,默认会取其上一级别的字符…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
flow_controllers
关键点: 流控制器类型: 同步(Sync):发布操作会阻塞,直到数据被确认发送。异步(Async):发布操作非阻塞,数据发送由后台线程处理。纯同步(PureSync…...