当前位置: 首页 > news >正文

时时彩网站开发教程怎么推广自己的店铺

时时彩网站开发教程,怎么推广自己的店铺,网站建设 sql,网站编辑没有经验可以做吗Android Settings 系列文章: Android Settings解析SettingsIntelligenceSettingsProvider 首语 Android Settings中搜索功能帮助我们可以快速访问设置项,进行自定义设置,以得到更佳的使用体验。Android Settings搜索的实现实际不在Setting…

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注解的页面,将这些页面的菜单添加到数据库中,查询时根据关键词进行匹配查询。

http://www.khdw.cn/news/14145.html

相关文章:

  • 网站标题logo制作郑州seo价格
  • wordpress 微博客seo新人怎么发外链
  • 广州建设网站是什么市场推广方法
  • 自己做购物网站好吗长春网站建设路
  • 响应式网站制设计企业网站模板源码
  • 高仿卡地亚手表网站黄页88网站推广效果
  • 长春电商网站建设价格无锡谷歌推广
  • 昆明网站建设一条龙百度推广优化师
  • dedecms转wordpressseo研究协会网
  • 山东建设银行官方网站湖南网络推广排名
  • 最好的网站建设哪家好成都网络营销
  • 北京网站建设怎么样天宁波网站优化
  • 营销型网站有哪些类百度指数关键词搜索趋势
  • 百度 网站地图怎么做软文营销经典案例优秀软文
  • 网站备案类型及条件seo系统源码
  • 免费网站制作范例什么叫百度竞价推广
  • 建站平台社区google官方版下载
  • 南京市网站建设提供搜索引擎优化公司
  • 用 可以做网站软件吗网站优化公司哪家好
  • 怎么在公司网站做超链接windows优化大师免费版
  • 魔站网站建设宜昌网站seo收费
  • 如何说服客户做网站关键路径
  • 广陵建设局网站百度提问登录入口
  • 网页与网站的区别与联系是什么互联网广告代理可靠吗
  • b2c电子商务网站分析职业培训学校加盟
  • 规划设计公司业务管理流程山东seo推广公司
  • 石家庄网站排名软件百度网站打开
  • 网站幻灯通栏代码市场营销策划方案模板
  • 做网站的准备什么软件优化网站页面
  • yy直播下载免费下载济南百度推广优化