Settings share

1,241 阅读10分钟

简单的自我介绍

大家好,我是杨辛梓,很荣幸今天能给大家分享我的学习成功,emmm ~
不对,应该说是让大家检查下我的学习成功,嗯 对, 还是这样讲比较合适哈~
之前工作几年一直是进行三方App的开发,目前呢刚接触系统应用开发,所以讲解深度不够,
大家多多包涵。

可以简单的介绍下 三方App 与 系统App

系统App

系统路径为 :packages/apps/* 
比如日历、相机、天气、联系人 ··· 属于内置App

三方App

由三方开发人员开发的App,上传至应用市场,由用户下载安装至手机。

简单的描述 Settings

Settings 就是我们在手机上经常看到的设置应用,它属于 Android 原生的系统应用

原生的Settings应用界面

对于开发者来讲,最重要的要知道界面的布局、代码的逻辑、数据的存储。

Settings App

路径 ./packages/apps/Setting/src/com/android/setting/

根据 AndroidManifest.xml(清单文件) 可以发现首页面为 SettingsHomepageActivity,紧接着我们进入到
SettingsHomepageActivity(首页)

分析下继承关系:XXFragment < DashboardFragment < SettingsPreferenceFragment <
InstrumentedPreferenceFragment < ObservablePreferenceFragment(已经到了SettingsLib) < PreferenceFragment

我们可以看到顶层未 PreferenceFragment ,在这里可能大家对于 Preference 不是很熟悉。(简单的讲下preference)
Preference 用来管理应用程序的偏好设置和保证使用这些的每个应用程序的所有参数拥有同样的方式和用户体验,并且系统和其他应用程序的UI保持偏一致,其实说白了也就是系统的一个统一UI样式及一个统一的用法。

布局样式

我们由它的名字可以看出 DashboardFragment 为一个 Fragment。在最顶层 PreferenceFragment 里有一段代码。

 @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ···
    onCreatePreferences(savedInstanceState, rootKey);
}

 /**
 * Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment.
 * Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either
 * directly or via helper methods such as {@link #addPreferencesFromResource(int)}.
 *
 * @param savedInstanceState If the fragment is being re-created from
 *                           a previous saved state, this is the state.
 * @param rootKey If non-null, this preference fragment should be rooted at the
 *                {@link androidx.preference.PreferenceScreen} with this key.
 */
public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey);

 /**
 * 调用此方法,会调用 requirePreferenceManager 重新去布局
 *
 * @param preferencesResId The XML resource ID to inflate.
 */
public void addPreferencesFromResource(@XmlRes int preferencesResId) {
    requirePreferenceManager();

    setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext,
            preferencesResId, getPreferenceScreen()));
}

此时我们再回到 onCreatePreferences

 @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
    refreshAllPreferences(getLogTag());
}

可以看到调用了 refreshAllPreferences 

 /**
 * Refresh all preference items, including both static prefs from xml, and dynamic items from
 * DashboardCategory.
 */
private void refreshAllPreferences(final String TAG) {
    ···
    // Add resource based tiles.
    displayResourceTiles();
    refreshDashboardTiles(TAG);
    ···
    updatePreferenceVisibility(mPreferenceControllers);
}

/**
 * Displays resource based tiles.
 */
private void displayResourceTiles() {
    final int resId = getPreferenceScreenResId();
    if (resId <= 0) {
        return;
    }
    // 重点  添加 XML 样式
    // 此方法就为 PreferenceFragment 内的方法。
    // resId 为 getPreferenceScreenResId()
    addPreferencesFromResource(resId);
    final PreferenceScreen screen = getPreferenceScreen();
    screen.setOnExpandButtonClickListener(this);
    mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
            controller -> controller.displayPreference(screen));
}

到这里,我们就搞清楚了 xxFragment 内的布局是怎么初始化的(不深入)

@Override
 protected int getPreferenceScreenResId() {
    // 加载的布局文件
    return R.xml.keyboard_layout_picker_fragment;
}

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="top_level_settings">

<Preference
    android:key="top_level_network" 
    android:title="@string/network_dashboard_title"
    android:summary="@string/summary_placeholder"
    android:icon="@drawable/ic_homepage_network"
    android:order="-120"
    android:fragment="com.android.settings.network.NetworkDashboardFragment"
    settings:controller="com.android.settings.network.TopLevelNetworkEntryPreferenceController"/>
···
<Preference
    android:key="top_level_system"
    android:title="@string/header_category_system"
    android:summary="@string/system_dashboard_summary"
    android:icon="@drawable/ic_homepage_system_dashboard"
    android:order="10"
    android:fragment="com.android.settings.system.SystemDashboardFragment"/>
···
<Preference
    android:key="top_level_about_device"
    android:title="@string/about_settings"
    android:summary="@string/summary_placeholder"
    android:icon="@drawable/ic_homepage_about"
    android:order="20"
    android:fragment="com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment"
    settings:controller="com.android.settings.deviceinfo.aboutphone.TopLevelAboutDevicePreferenceController"/>
</PreferenceScreen>

我们可以看到对应界面的布局文件样式。

key: 唯一标识,SharedPreferences可以将此值保存为key。
title:对应界面的大标题。
summary:对应界面的小标题。
icon: 对应的icon图标。
order: 偏好顺序。
fragment: 对应的界面。
controller: 对应的数据提供。

我们可以进入一个简单的界面看下 MyDeviceInfoFragment 

可以看到它也是继承与 DashboardFragment ,所以它也是对应的 Preference 布局,
@Override
protected int getPreferenceScreenResId() {
    return R.xml.my_device_info;
}

<com.android.settingslib.widget.LayoutPreference
    android:key="my_device_info_header"
    android:order="0"
    android:layout="@layout/settings_entity_header"
    android:selectable="false"
    settings:isPreferenceVisible="false"/>

<!-- Device name -->
<com.android.settings.widget.ValidatedEditTextPreference
    android:key="device_name"
    android:order="1"
    android:title="@string/my_device_info_device_name_preference_title"
    android:summary="@string/summary_placeholder"
    settings:controller="com.android.settings.deviceinfo.DeviceNamePreferenceController"
    settings:enableCopying="true"/>

<!-- Account name -->
<Preference
    android:key="branded_account"
    android:order="2"
    android:title="@string/my_device_info_account_preference_title"
    android:summary="@string/summary_placeholder"
    settings:controller="com.android.settings.deviceinfo.BrandedAccountPreferenceController"/>

<!-- Phone number -->
<Preference
    android:key="phone_number"
    android:order="3"
    android:title="@string/status_number"
    android:summary="@string/summary_placeholder"
    android:selectable="false"
    settings:controller="com.android.settings.deviceinfo.PhoneNumberPreferenceController"
    settings:enableCopying="true"/>

<Preference
    android:key="emergency_info"
    android:order="14"
    android:title="@string/emergency_info_title"
    android:summary="@string/summary_placeholder"
    settings:controller="com.android.settings.accounts.EmergencyInfoPreferenceController"/>

<!-- Legal information -->
<Preference
    android:key="legal_container"
    android:order="15"
    android:title="@string/legal_information"
    android:fragment="com.android.settings.LegalSettings"
    settings:allowDividerAbove="true"
    settings:searchable="false"/>

<!-- Regulatory labels -->
<Preference
    android:key="regulatory_info"
    android:order="16"
    android:title="@string/regulatory_labels">
    <intent android:action="android.settings.SHOW_REGULATORY_INFO"/>
</Preference>

<!-- Safety & regulatory manual -->
<Preference
    android:key="safety_info"
    android:order="17"
    android:title="@string/safety_and_regulatory_info">
    <intent android:action="android.settings.SHOW_SAFETY_AND_REGULATORY_INFO"/>
</Preference>

<!-- SIM status -->
<Preference
    android:key="sim_status"
    android:order="18"
    android:title="@string/sim_status_title"
    settings:keywords="@string/keywords_sim_status"
    android:summary="@string/summary_placeholder"
    settings:allowDividerAbove="true"/>

<!-- Model & hardware -->
<Preference
    android:key="device_model"
    android:order="31"
    android:title="@string/hardware_info"
    settings:keywords="@string/keywords_model_and_hardware"
    android:summary="@string/summary_placeholder"
    android:fragment="com.android.settings.deviceinfo.hardwareinfo.HardwareInfoFragment"
    settings:controller="com.android.settings.deviceinfo.HardwareInfoPreferenceController"/>

<!-- IMEI -->
<Preference
    android:key="imei_info"
    android:order="32"
    android:title="@string/status_imei"
    settings:keywords="@string/keywords_imei_info"
    android:summary="@string/summary_placeholder"
    settings:controller="com.android.settings.deviceinfo.imei.ImeiInfoPreferenceController"/>

<!-- Android version -->
<Preference
    android:key="firmware_version"
    android:order="42"
    android:title="@string/firmware_version"
    android:summary="@string/summary_placeholder"
    android:fragment="com.android.settings.deviceinfo.firmwareversion.FirmwareVersionSettings"
    settings:controller="com.android.settings.deviceinfo.firmwareversion.FirmwareVersionPreferenceController"/>

<!--IP address -->
<Preference
    android:key="wifi_ip_address"
    android:order="44"
    android:title="@string/wifi_ip_address"
    android:summary="@string/summary_placeholder"
    android:selectable="false"
    settings:allowDividerAbove="true"
    settings:enableCopying="true"/>

<!-- Wi-Fi MAC address -->
<Preference
    android:key="wifi_mac_address"
    android:order="45"
    android:title="@string/status_wifi_mac_address"
    android:summary="@string/summary_placeholder"
    android:selectable="false"
    settings:enableCopying="true"/>

<!-- Bluetooth address -->
<Preference
    android:key="bt_address"
    android:order="46"
    android:title="@string/status_bt_address"
    android:summary="@string/summary_placeholder"
    android:selectable="false"
    settings:enableCopying="true"/>

<!-- Device up time -->
<Preference
    android:key="up_time"
    android:order="47"
    android:title="@string/status_up_time"
    android:summary="@string/summary_placeholder"
    android:selectable="false"/>

<!-- Manual -->
<Preference
    android:key="manual"
    android:order="50"
    android:title="@string/manual">
    <intent android:action="android.settings.SHOW_MANUAL"/>
</Preference>

<!-- Feedback on the device -->
<Preference
    android:key="device_feedback"
    android:order="51"
    android:title="@string/device_feedback"
    settings:keywords="@string/keywords_device_feedback"/>

<!-- Device FCC equipment id -->
<Preference
    android:key="fcc_equipment_id"
    android:order="52"
    android:title="@string/fcc_equipment_id"
    android:summary="@string/summary_placeholder"/>

<!-- Build number -->
<Preference
    android:key="build_number"
    android:order="53"
    android:title="@string/build_number"
    android:summary="@string/summary_placeholder"
    settings:allowDividerAbove="true"
    settings:enableCopying="true"
    settings:controller="com.android.settings.deviceinfo.BuildNumberPreferenceController"/>
</PreferenceScreen>

emm,这里呢就是我们常见的关于手机界面,对应的这些资源呢。。。
得去一个个查名字,我在这里也就不一一说明了。(其实是我不知道)
在这里,我们 settings 的一二级界面就构建完毕了。

数据来源

SettingsIntelligence

此模块为搜索模块,在 SettingsFragment 内。
  // 初始化搜索模块
  final Toolbar toolbar = findViewById(R.id.search_action_bar);
    FeatureFactory.getFactory(this).getSearchFeatureProvider()
            .initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);

    
 /**
 * 具体实例化搜索模块 并设置点击事件
 * Initializes the search toolbar.
 */
default void initSearchToolbar(Activity activity, Toolbar toolbar, int pageId) {
    if (activity == null || toolbar == null) {
        return;
    }

    if (!Utils.isDeviceProvisioned(activity) ||
            !Utils.isPackageEnabled(activity, getSettingsIntelligencePkgName(activity))) {
        final ViewGroup parent = (ViewGroup) toolbar.getParent();
        if (parent != null) {
            parent.setVisibility(View.GONE);
        }
        return;
    }
    // Please forgive me for what I am about to do.
    //
    // Need to make the navigation icon non-clickable so that the entire card is clickable
    // and goes to the search UI. Also set the background to null so there's no ripple.
    final View navView = toolbar.getNavigationView();
    navView.setClickable(false);
    navView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
    navView.setBackground(null);
    
    // 设置点击事件
    toolbar.setOnClickListener(tb -> {
        final Context context = activity.getApplicationContext();
        // 跳转的意图 而 buildSearchIntent 的实现在 SearchFeatureProviderImpl 内
        final Intent intent = buildSearchIntent(context, pageId);
        ···
        activity.startActivityForResult(intent, REQUEST_CODE);
    });
}

// buildSearchIntent 的实现
@Override
public Intent buildSearchIntent(Context context, int pageId) {
    // Settings.ACTION_APP_SEARCH_SETTINGS 为跳转意图
    return new Intent(Settings.ACTION_APP_SEARCH_SETTINGS)
            .setPackage(getSettingsIntelligencePkgName(context))
            .putExtra(Intent.EXTRA_REFERRER, buildReferrer(context, pageId));
}

到了此时,程序将会跳转至 android.settings.APP_SEARCH_SETTINGS;
会跳转到另一个程序 SettingsIntelligence 

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.android.settings.intelligence">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
    <uses-permission android:name="android.permission.READ_SEARCH_INDEXABLES" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
    <uses-permission android:name="android.permission.MANAGE_FINGERPRINT" />

    <application
        android:label="@string/app_name_settings_intelligence"
        android:icon="@mipmap/ic_launcher"
        android:supportsRtl="true">
        <service
            android:name=".suggestions.SuggestionService"
            android:exported="true"
            android:permission="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE" />

        <activity
            android:name=".search.SearchActivity"
            android:exported="true"
            android:theme="@style/Theme.Settings.NoActionBar">
            <intent-filter priority="-1">
                // 看到此 action 与之前相对应 
                <action android:name="com.android.settings.action.SETTINGS_SEARCH" />
                <action android:name="android.settings.APP_SEARCH_SETTINGS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>

进入到 SearchActivity 内只看到需要添加 SearchFragment,所以我们进入到 SearchFragment

我们简单的看下 SearchFragment 的布局文件。

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/search_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

    <FrameLayout
        android:id="@+id/search_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/search_panel_background"
        android:elevation="4dp">
        <androidx.cardview.widget.CardView
            android:id="@+id/search_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/search_bar_margin"
            app:cardCornerRadius="2dp"
            app:cardBackgroundColor="?android:attr/colorBackground"
            app:cardElevation="2dp">
            <Toolbar
                android:id="@+id/search_toolbar"
                android:layout_width="match_parent"
                android:layout_height="@dimen/search_bar_height"
                android:background="?android:attr/selectableItemBackground"
                android:contentInsetStart="0dp"
                android:contentInsetStartWithNavigation="0dp"
                android:theme="?android:attr/actionBarTheme">
                // 重点是这个控件 看名字我们就知道是一个搜索控件
                <SearchView
                    android:id="@+id/search_view"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:iconifiedByDefault="false"
                    android:imeOptions="actionSearch|flagNoExtractUi"
                    android:searchIcon="@null"/>
            </Toolbar>
        </androidx.cardview.widget.CardView>
    </FrameLayout>

    <FrameLayout
        android:id="@+id/layout_results"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <!-- Padding is included in the background -->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/list_results"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingStart="@dimen/dashboard_padding_start"
            android:paddingEnd="@dimen/dashboard_padding_end"
            android:paddingTop="@dimen/dashboard_padding_top"
            android:paddingBottom="@dimen/dashboard_padding_bottom"
            android:scrollbarStyle="outsideOverlay"
            android:scrollbars="vertical"/>

        <LinearLayout
            android:id="@+id/no_results_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingTop="35dp"
            android:orientation="vertical"
            android:visibility="gone">

            <Space
                android:layout_width="match_parent"
                android:layout_height="?android:attr/actionBarSize"/>

            <ImageView
                android:layout_height="wrap_content"
                android:layout_width="wrap_content"
                android:layout_gravity="center_horizontal"
                android:src="@drawable/empty_search_results"/>

            <TextView
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:paddingTop="24dp"
                android:textSize="18sp"
                android:text="@string/search_no_results"
                android:gravity="center"/>

        </LinearLayout>

    </FrameLayout>

    <include layout="@layout/search_feedback"/>
</LinearLayout>

简单的了解下我们返回到对应的fragment

我们可以找到对 SearchView 设置监听的地方

    mSearchView = toolbar.findViewById(R.id.search_view);
    mSearchView.setQuery(mQuery, false /* submitQuery */);
    mSearchView.setOnQueryTextListener(this);
    mSearchView.requestFocus();

跟随源码,我们去找它的回调位置

 void onTextChanged(CharSequence newText) {
    CharSequence text = mSearchSrcTextView.getText();
    mUserQuery = text;
    boolean hasText = !TextUtils.isEmpty(text);
    updateSubmitButton(hasText);
    updateVoiceButton(!hasText);
    updateCloseButton();
    updateSubmitArea();
    if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) {
        // 在此
        mOnQueryChangeListener.onQueryTextChange(newText.toString());
    }
    mOldQueryText = newText.toString();
}

所以我们直接来看这个方法
 @Override
public boolean onQueryTextChange(String query) {
    if (TextUtils.equals(query, mQuery)) {
        return true;
    }
    mEnterQueryTimestampMs = System.currentTimeMillis();
    final boolean isEmptyQuery = TextUtils.isEmpty(query);

    ···
    if (isEmptyQuery) {
        final LoaderManager loaderManager = getLoaderManager();
        loaderManager.destroyLoader(SearchLoaderId.SEARCH_RESULT);
        mShowingSavedQuery = true;
        mSavedQueryController.loadSavedQueries();
        mSearchFeatureProvider.hideFeedbackButton(getView());
    } else {
        mMetricsFeatureProvider.logEvent(SettingsIntelligenceEvent.PERFORM_SEARCH);
        // 重点 
        restartLoaders();
    }
    return true;
}
    
private void restartLoaders() {
    mShowingSavedQuery = false;
    final LoaderManager loaderManager = getLoaderManager();
    loaderManager.restartLoader(
            SearchLoaderId.SEARCH_RESULT, null /* args */, this /* callback */);
}

我们知道调用 LoaderManager 的时候,会走 LoaderManager 方法
 @Override
public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
    final Activity activity = getActivity();

    switch (id) {
        case SearchLoaderId.SEARCH_RESULT:
            // 返回一个 Loader<List<? extends SearchResult>>
            return mSearchFeatureProvider.getSearchResultLoader(activity, mQuery);
        default:
            return null;
    }
}

找到 SearchFeatureProviderImpl 的 getSearchResultLoader 方法
 @Override
public SearchResultLoader getSearchResultLoader(Context context, String query) {
    return new SearchResultLoader(context, cleanQuery(query));
}

去找 SearchResultLoader
 /**
 * Loads a sorted list of Search results for a given query.
 */
public class SearchResultLoader extends AsyncLoader<List<? extends SearchResult>> {

    private final String mQuery;

    public SearchResultLoader(Context context, String query) {
        super(context);
        mQuery = query;
    }

    @Override
    public List<? extends SearchResult> loadInBackground() {
        SearchResultAggregator aggregator = SearchResultAggregator.getInstance();
        return aggregator.fetchResults(getContext(), mQuery);
    }

    @Override
    protected void onDiscardResult(List<? extends SearchResult> result) {
    }
}

 // 去找到 SearchResultAggregator.fetchResults
 @NonNull
public 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 tasks
    for (SearchQueryTask task : tasks) {
        executorService.execute(task);
    }

    // Collect results
    final 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 results
    final long mergeStartTime = System.currentTimeMillis();
    if (SearchFeatureProvider.DEBUG) {
        Log.d(TAG, "Total result loader time: " + (mergeStartTime - allTasksStart));
    }
    final List<? extends SearchResult> mergedResults = mergeSearchResults(taskResults);
    if (SearchFeatureProvider.DEBUG) {
        Log.d(TAG, "Total merge time: " + (System.currentTimeMillis() - mergeStartTime));
        Log.d(TAG, "Total aggregator time: " + (System.currentTimeMillis() - allTasksStart));
    }

    return mergedResults;
}

// 反推回来 最终返回的 mergedResults,是由 getSearchQueryTasks()提供的
@Override
public 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;
}

我们进入到 DatabaseResultTask 去看看
可以看到继承于 SearchQueryTask ,不去深究,只需要实现 query()。
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);
    }
}

到了这里,搜索流程就结束了。

问题来了,那我们收到的数据从何而来呢

再次回到 SearchFragment 的onCreate 可以看到 updateIndexAsync()

 @Override
public void updateIndexAsync(Context context, IndexingCallback callback) {
    if (DEBUG) {
        Log.d(TAG, "updating index async");
    }
    getIndexingManager(context).indexDatabase(callback);
}

一步步追踪
  public void indexDatabase(IndexingCallback callback) {
    IndexingTask task = new IndexingTask(callback);
    task.execute();
}

public class IndexingTask extends AsyncTask<Void, Void, Void> {

    ···
    @Override
    protected Void doInBackground(Void... voids) {
        // 去调用
        performIndexing();
        return null;
    }
    ···
}

 /**
 * Accumulate all data and non-indexable keys from each of the content-providers.
 * Only the first indexing for the default language gets static search results - subsequent
 * calls will only gather non-indexable keys.
 */
public void performIndexing() {
    //  public static final String PROVIDER_INTERFACE =
    //  "android.content.action.SEARCH_INDEXABLES_PROVIDER";
    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);
    }
}

到此,我们就知道这边的数据由来了 

然而 PROVIDER_INTERFACE = "android.content.action.SEARCH_INDEXABLES_PROVIDER" 的实现呢在
Settings/SettingsSearchIndexablesProvider 里面,可以看到好多的 query 方法。

随便进入一个看看 
  @Override
public Cursor queryXmlResources(String[] projection) {
    MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
    final List<SearchIndexableResource> resources =
            getSearchIndexableResourcesFromProvider(getContext());
    for (SearchIndexableResource val : resources) {
        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 class
        cursor.addRow(ref);
    }
    return cursor;
}

 private List<SearchIndexableResource> getSearchIndexableResourcesFromProvider(Context context) {
    Collection<Class> values = FeatureFactory.getFactory(context)
            .getSearchFeatureProvider().getSearchIndexableResources().getProviderValues();
    List<SearchIndexableResource> resourceList = new ArrayList<>();

    for (Class<?> clazz : values) {
        // 直接通过反射的方式去获取了对应class的一个字段 "SEARCH_INDEX_DATA_PROVIDER"
        Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
                clazz);

        final List<SearchIndexableResource> resList =
                provider.getXmlResourcesToIndex(context, true);

        if (resList == null) {
            continue;
        }

        for (SearchIndexableResource item : resList) {
            item.className = TextUtils.isEmpty(item.className)
                    ? clazz.getName()
                    : item.className;
        }

        resourceList.addAll(resList);
    }

    return resourceList;
}

SEARCH_INDEX_DATA_PROVIDER 这个东西有没有看到好眼熟呢