Settings搜索系统SettingsIntelligence

目录

概要

代码流程图

代码流程分析

1.SettingsHomepageActivity.java

2.SearchFeatureProvider.java

3、SearchFeatureProviderImpl.java

4、SearchActivity.java

5.SearchFragment.java

6.SettingsIntelligence-SearchFeatureProviderImpl.java

7.DatabaseIndexingManager.java

8.PreIndexDataCollector.java

9.IndexDatabaseHelper.java

小结


概要

在Android系统中,Setting菜单非常多,有些菜单很难找到,因此Google支持搜索菜单功能。搜索的主要逻辑在packages/apps/SettingsIntelligence模块中。模块包名为com.android.settings.intelligence。

该模块会检索所有继承自SearchIndexablesProvider的ContentProvider,将所有数据保存至数据库

/data/data/com.android.settings.intelligence/databases/search_index.db。然后在SearchFragment中通过访问该数据库来实现搜索菜单的功能。本文重点探讨保存数据库这步流程

代码流程图

第一次从设置首页进入搜索时,会跳转到SettingsIntelligence加载数据库,以下为这个过程的流程图

代码流程分析

接下来,我会按照流程图的顺序,逐步分析相关代码,所有代码示例皆来自Android14

1.SettingsHomepageActivity.java

这个为设置首页,从这个界面顶端进入搜索

SettingsHomepageActivity.java:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        initSearchBarView();  //1.搜索相关的初始化
}

private void initSearchBarView() {
        final Toolbar toolbar = findViewById(R.id.search_action_bar); //获取search控件

        //通过Factory模式,将搜索初始化放在SearchFeatureProvider中实现
        FeatureFactory.getFactory(this).getSearchFeatureProvider() 
                .initSearchToolbar(this /* activity */, toolbar,  
 SettingsEnums.SETTINGS_HOMEPAGE);

        if (mIsEmbeddingActivityEnabled) {   //支持分栏的设备会走这里
            final Toolbar toolbarTwoPaneVersion = findViewById(R.id.search_action_bar_two_pane);
            FeatureFactory.getFactory(this).getSearchFeatureProvider()
                    .initSearchToolbar(this /* activity */, toolbarTwoPaneVersion,
                            SettingsEnums.SETTINGS_HOMEPAGE);
        }
    }

2.SearchFeatureProvider.java

主要处理Setting Search相关逻辑,具体实现类为SearchFeatureProviderImpl

SearchFeatureProvider.java

default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) {

        final Context context = activity.getApplicationContext();
        //2.获取SearchActivity的Intent,用于跳转
        final Intent intent = buildSearchIntent(context, pageId)  
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        //增加OnClickListener,点击搜索控件时,会回调onClick跳转到SearchActivity
        toolbar.setOnClickListener(tb -> startSearchActivity(context, activity, pageId, intent));

}


private static void startSearchActivity(
            Context context, FragmentActivity activity, int pageId, Intent intent) {
        FeatureFactory.getFactory(context).getSlicesFeatureProvider()
                .indexSliceDataAsync(context);

        FeatureFactory.getFactory(context).getMetricsFeatureProvider()
                .logSettingsTileClick(KEY_HOMEPAGE_SEARCH_BAR, pageId);

        final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle();
        //3.start SearchActivity
        activity.startActivity(intent, bundle);
    }

3、SearchFeatureProviderImpl.java

从这里跳转到SearchActivity

SearchFeatureProviderImpl.java

 @Override
    public Intent buildSearchIntent(Context context, int pageId) {
        return new Intent(Settings.ACTION_APP_SEARCH_SETTINGS) //获取SearchActivity action
                .setPackage(getSettingsIntelligencePkgName(context)) //获取包名
                .putExtra(Intent.EXTRA_REFERRER, buildReferrer(context, pageId));
    }

//获取的包名为com.android.settings.intelligence
default String getSettingsIntelligencePkgName(Context context) {
        return context.getString(R.string.config_settingsintelligence_package_name);
    }


Intent.java
     //SearchActivity会配置该action
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";

4、SearchActivity.java

Search界面

SettingsIntelligence AndroidManifest.xml

 <activity
            android:name=".search.SearchActivity"
            android:exported="true"
            android:theme="@style/Theme.Settings.NoActionBar"
            android:windowSoftInputMode="adjustResize">
            <intent-filter priority="-1">
                <!--通过匹配这个action来跳转SearchActivity-->
                <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>
public class SearchActivity extends FragmentActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.search_main);

        FragmentManager fragmentManager = getSupportFragmentManager();
        Fragment fragment = fragmentManager.findFragmentById(R.id.main_content);
        if (fragment == null) {
            fragmentManager.beginTransaction()
                    .add(R.id.main_content, new SearchFragment())  //4.嵌入SearchFragment,主要逻辑在Fragment里面
                    .commit();
        }
    }

    @Override
    public boolean onNavigateUp() {
        finish();
        return true;
    }
}

5.SearchFragment.java

搜索界面,搜索逻辑从这里开始调用

SearchFragment.java
  
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        long startTime = System.currentTimeMillis();
        setHasOptionsMenu(true);

        final LoaderManager loaderManager = getLoaderManager();
        //给搜索结果使用的数据
        mSearchAdapter = new SearchResultsAdapter(this /* fragment */);
        mSavedQueryController = new SavedQueryController(
                getContext(), loaderManager, mSearchAdapter);
        mSearchFeatureProvider.initFeedbackButton();

        if (savedInstanceState != null) {
            //保存搜索结果
            mQuery = savedInstanceState.getString(SearchCommon.STATE_QUERY);
            mNeverEnteredQuery = savedInstanceState.getBoolean(SearchCommon.STATE_NEVER_ENTERED_QUERY);
            mShowingSavedQuery = savedInstanceState.getBoolean(SearchCommon.STATE_SHOWING_SAVED_QUERY);
        } else {
            mShowingSavedQuery = true;
        }
        5.加载搜索数据库逻辑从这里开始
        mSearchFeatureProvider.updateIndexAsync(getContext(), this /* indexingCallback */);   //load database 1
        if (SearchFeatureProvider.DEBUG) {
            Log.d(TAG, "onCreate spent " + (System.currentTimeMillis() - startTime) + " ms");
        }
    }

//IndexingTask数据处理完成后onPostExecute会通过mCallback.onIndexingFinished()进行回调返回这里
 @Override
    public void onIndexingFinished() {   //load database 完成后回调
        if (getActivity() == null) {
            return;
        }
        if (mShowingSavedQuery) {
            mSavedQueryController.loadSavedQueries();
        } else {
            final LoaderManager loaderManager = getLoaderManager();
            loaderManager.initLoader(SearchCommon.SearchLoaderId.SEARCH_RESULT, null /* args */,
                    this /* callback */);
        }

        requery();
    }

6.SettingsIntelligence-SearchFeatureProviderImpl.java

此处为SettingsIntelligence中的类,和前面的不是同一个

/**
 * FeatureProvider for the refactored search code.
 */
SearchFeatureProviderImpl.java

@Override
    public void updateIndexAsync(Context context, IndexingCallback callback) {  //load database 2
        if (DEBUG) {
            Log.d(TAG, "updating index async");
        }
//6.此处就是一个衔接的作用,最终要调用DatabaseIndexingManager中进行处理
        getIndexingManager(context).indexDatabase(callback);  
    }

7.DatabaseIndexingManager.java

DatabaseIndexingManager.IndexingTask

* Consumes the SearchIndexableProvider content providers.
* Updates the Resource, Raw Data and non-indexable data for Search.

该文件主要是检索分析所有SearchIndexableProvider的之类,将数据分类保存到数据库/data/data/com.android.settings.intelligence/databases

IndexingTask内部类则用于异步处理保存数据库的耗时操作
DatabaseIndexingManager.java
/**
 * Consumes the SearchIndexableProvider content providers.
 * Updates the Resource, Raw Data and non-indexable data for Search.
*/


//7.将数据库加载的耗时操作放入IndexingTask
public void indexDatabase(IndexingCallback callback) { 
        IndexingTask task = new IndexingTask(callback);
        task.execute();
    }

public void performIndexing() {   //load database 5 关键代码

        final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);  //查询所有search数据的intent,该intent为"android.content.action.SEARCH_INDEXABLES_PROVIDER"
        final List<ResolveInfo> providers =
                mContext.getPackageManager().queryIntentContentProviders(intent, 0);  //查询所有该intent匹配的Provider

        //检查是否要全部更新,如修改语言,OTA后
        final boolean isFullIndex = IndexDatabaseHelper.isFullIndex(mContext, providers);  

        if (isFullIndex) {
            rebuildDatabase();  //若要全部更新,则删除数据库Table,重建Table
        }

        //9.收集数据,返回格式为indexData
        PreIndexData indexData = getIndexDataFromProviders(providers, isFullIndex);   

        final long updateDatabaseStartTime = System.currentTimeMillis();
        updateDatabase(indexData, isFullIndex);    //load database 7   更新数据库
         //设一些标志位,可能会用于判断isFullIndex,结果保存在indexing_manager.xml,格式为
SharedPreferences,包括语言,包括搜索数据的应用包名和fingerprint
        IndexDatabaseHelper.setIndexed(mContext, providers);   

        if (DEBUG) {
            final long updateDatabaseTime = System.currentTimeMillis() - updateDatabaseStartTime;
            Log.d(TAG, "performIndexing updateDatabase took time: " + updateDatabaseTime);
        }
    }

//9.搜集搜索数据,返回格式为PreIndexData 
@VisibleForTesting
    PreIndexData getIndexDataFromProviders(List<ResolveInfo> providers, boolean isFullIndex) {
        if (mCollector == null) {
            mCollector = new PreIndexDataCollector(mContext);
        }
//9.搜集搜索数据,返回格式为PreIndexData 
        return mCollector.collectIndexableData(providers, isFullIndex); 
    }

//数据返回后,更新数据库
 @VisibleForTesting
    void updateDatabase(PreIndexData preIndexData, boolean isFullIndex) { 
        final SQLiteDatabase database = getWritableDatabase();  //IndexDatabaseHelper用于管理数据库
.
.
.

            insertIndexData(database, indexData);  //10.插入数据库,插入成功后,则数据库更新完成
         
    }
 private void insertIndexData(SQLiteDatabase database, List<IndexData> indexData) {  //load database 7.3
        ContentValues values;

        for (IndexData dataRow : indexData) {
            if (TextUtils.isEmpty(dataRow.normalizedTitle)) {
                continue;
            }

            values = new ContentValues();
            values.put(DATA_TITLE, dataRow.updatedTitle);
            values.put(DATA_TITLE_NORMALIZED, dataRow.normalizedTitle);
            values.put(DATA_SUMMARY_ON, dataRow.updatedSummaryOn);
            values.put(DATA_SUMMARY_ON_NORMALIZED, dataRow.normalizedSummaryOn);
            values.put(DATA_ENTRIES, dataRow.entries);
            values.put(DATA_KEYWORDS, dataRow.spaceDelimitedKeywords);
            values.put(DATA_PACKAGE, dataRow.packageName);
            values.put(DATA_AUTHORITY, dataRow.authority);
            values.put(CLASS_NAME, dataRow.className);
            values.put(SCREEN_TITLE, dataRow.screenTitle);
            values.put(INTENT_ACTION, dataRow.intentAction);
            values.put(INTENT_TARGET_PACKAGE, dataRow.intentTargetPackage);
            values.put(INTENT_TARGET_CLASS, dataRow.intentTargetClass);
            values.put(ICON, dataRow.iconResId);
            values.put(ENABLED, dataRow.enabled);
            values.put(DATA_KEY_REF, dataRow.key);
            values.put(PAYLOAD_TYPE, dataRow.payloadType);
            values.put(PAYLOAD, dataRow.payload);
            values.put(TOP_LEVEL_MENU_KEY, dataRow.topLevelMenuKey);

            //database类为IndexDatabaseHelper
            database.replaceOrThrow(TABLE_PREFS_INDEX, null, values);  //写入数据库
        }
    }


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

        @VisibleForTesting
        IndexingCallback mCallback;
        private long mIndexStartTime;

        public IndexingTask(IndexingCallback callback) {
            mCallback = callback;
        }

        @Override
        protected void onPreExecute() {
            mIndexStartTime = System.currentTimeMillis();
            mIsIndexingComplete.set(false);
        }

       //8.马上进入真正的保存数据库操作
        @Override
        protected Void doInBackground(Void... voids) {
            performIndexing();   //load database 4
            return null;
        }

        @Override
        protected 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();   //执行完成后,此处将结果回调给SearchFragment
            }
        }
    }

8.PreIndexDataCollector.java

在此类中处理数据

PreIndexDataCollector.java

//9.处理返回数据
public PreIndexData collectIndexableData(List<ResolveInfo> providers, boolean isFullIndex) {
        mIndexData = new PreIndexData();

        for (final ResolveInfo info : providers) {
            if (!isWellKnownProvider(info)) {
                continue;
            }
            final String authority = info.providerInfo.authority;
            final String packageName = info.providerInfo.packageName;

            if (isFullIndex) {
                addIndexablesFromRemoteProvider(packageName, authority);   //加载数据
            }

            final long nonIndexableStartTime = System.currentTimeMillis();
            addNonIndexablesKeysFromRemoteProvider(packageName, authority);   //剔除不要的数据
            if (SearchFeatureProvider.DEBUG) {
                final long nonIndexableTime = System.currentTimeMillis() - nonIndexableStartTime;
                Log.d(TAG, "performIndexing update non-indexable for package " + packageName
                        + " took time: " + nonIndexableTime);
            }
        }

        return mIndexData;  //返回结果
    }

9.IndexDatabaseHelper.java

数据库创建读写操作都在这里

IndexDatabaseHelper.java

public class IndexDatabaseHelper extends SQLiteOpenHelper {
 

//这些Table名称
public interface Tables {
        String TABLE_PREFS_INDEX = "prefs_index";
        String TABLE_SITE_MAP = "site_map";
        String TABLE_META_INDEX = "meta_index";
        String TABLE_SAVED_QUERIES = "saved_queries";
    }
//数据库中保存的数据都在这里,包括title,Summary,package,class name,key,icon等
public interface IndexColumns {
        String DATA_TITLE = "data_title";
        String DATA_TITLE_NORMALIZED = "data_title_normalized";
        String DATA_SUMMARY_ON = "data_summary_on";
        String DATA_SUMMARY_ON_NORMALIZED = "data_summary_on_normalized";
        String DATA_SUMMARY_OFF = "data_summary_off";
        String DATA_SUMMARY_OFF_NORMALIZED = "data_summary_off_normalized";
        String DATA_ENTRIES = "data_entries";
        String DATA_KEYWORDS = "data_keywords";
        String DATA_PACKAGE = "package";
        String DATA_AUTHORITY = "authority";
        String CLASS_NAME = "class_name";
        String SCREEN_TITLE = "screen_title";
        String INTENT_ACTION = "intent_action";
        String INTENT_TARGET_PACKAGE = "intent_target_package";
        String INTENT_TARGET_CLASS = "intent_target_class";
        String ICON = "icon";
        String ENABLED = "enabled";
        String DATA_KEY_REF = "data_key_reference";
        String PAYLOAD_TYPE = "payload_type";
        String PAYLOAD = "payload";
        String TOP_LEVEL_MENU_KEY = "top_level_menu_key";
    }

@Override
    public void onCreate(SQLiteDatabase db) {
        bootstrapDB(db);
    }
    //会创建以下几个Table
    private void bootstrapDB(SQLiteDatabase db) {
        db.execSQL(CREATE_INDEX_TABLE);
        db.execSQL(CREATE_META_TABLE);
        db.execSQL(CREATE_SAVED_QUERIES_TABLE);
        db.execSQL(CREATE_SITE_MAP_TABLE);
        db.execSQL(INSERT_BUILD_VERSION);
        Log.i(TAG, "Bootstrapped database");
    }


public void reconstruct(SQLiteDatabase db) {    //load database 5.2
        mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
                .edit()
                .clear()
                .commit();
        dropTables(db);   //删除数据库的表table
        bootstrapDB(db);   //新建数据库的表
    }
}

小结

Settings属于Android原生比较大的模块了,代码也非常多,这只是其中搜索功能的一部分。其他部分将在其他文章中继续探讨

  • 22
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值