Android Settings 快速搜索

来源:互联网 发布:百诺巧克力怎么样知乎 编辑:程序博客网 时间:2024/06/06 00:36
Settings 之 SearchIndexablesProvider首先需要在清单文件中注册action为"android.content.action.SEARCH_INDEXABLES_PROVIDER"的provider,如下:      <provider            android: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" /> //注册此 action            </intent-filter>        </provider>搜索数据库路径:/data/user_de/0/com.android.settings/databases/search_index.db//search_index.db 数据库的prefs_index表格中存放的就是搜索的设置选项此数据库的初始化不是在开机阶段,而是在每一次打开settings或者当前切换用户(因为系统为每一个用户维护一个单独的search_index.db),或者是当前的语言发生变化会更新数据库.数据库的初始化:Index#update    public void update() {        AsyncTask.execute(new Runnable() {            @Override            public void run() {                // 查找系统中所有的配置了"android.content.action.SEARCH_INDEXABLES_PROVIDER"的Provider                final Intent intent = new Intent(SearchIndexablesContract.PROVIDER_INTERFACE);                List<ResolveInfo> list =                        mContext.getPackageManager().queryIntentContentProviders(intent, 0);                final int size = list.size();                for (int n = 0; n < size; n++) {                    final ResolveInfo info = list.get(n);                    if (!isWellKnownProvider(info)) {                        continue;                    }                    final String authority = info.providerInfo.authority;                    final String packageName = info.providerInfo.packageName;                    //打印packageName为:                    //01-01 17:38:09.257  5207  5511 E qcdds   : packageName= com.android.cellbroadcastreceiver                    //01-01 17:38:09.678  5207  5511 E qcdds   : packageName= com.android.phone                    //01-01 17:38:09.777  5207  5511 E qcdds   : packageName= com.android.settings                    // 添加其他APP的设置项                    addIndexablesFromRemoteProvider(packageName, authority); //主要方法                    // 添加其他APP中不需要被搜索到的设置项                    addNonIndexablesKeysFromRemoteProvider(packageName, authority);                }                mDataToProcess.fullIndex = true;                // 上面的addIndexablesFromRemoteProvider会添加设置项到内存中的一个mDataToProcess对象里,updateInternal将该对象更新到数据库中                updateInternal();            }        });    }    private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {        try {            // rank是按照指定算法计算出的一个值,用来搜索的时候,展示给用户的优先级            final int baseRank = Ranking.getBaseRankForAuthority(authority);            // mBaseAuthority是com.android.settings,authority是其他APP的包名            final Context context = mBaseAuthority.equals(authority) ?                    mContext : mContext.createPackageContext(packageName, 0);            // 构建搜索的URI            final Uri uriForResources = buildUriForXmlResources(authority);            // 两种添加到数据库的方式,我们以addIndexablesForXmlResourceUri为例            addIndexablesForXmlResourceUri(context, packageName, uriForResources,                    SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS, baseRank);            final Uri uriForRawData = buildUriForRawData(authority);            addIndexablesForRawDataUri(context, packageName, uriForRawData,                    SearchIndexablesContract.INDEXABLES_RAW_COLUMNS, baseRank);            return true;        } catch (PackageManager.NameNotFoundException e) {            Log.w(LOG_TAG, "Could not create context for " + packageName + ": "                    + Log.getStackTraceString(e));            return false;        }     }上面代码主要做了下面事情:    根据当前包名创建对应包的context对象。    根据当前包名构建指定URI,例如,settings:content://com.android.settings/settings/indexables_xml_res    然后通过context对象查找对应的Provider的数据    之所以构建出content://com.android.settings/settings/indexables_xml_res 这样的URI是因为所有的需要被搜索到的设置项所在的APP,其Provider都需要继承自SearchIndexablesProvider//SearchIndexablesProvider 继承 ContentProviderpublic abstract class SearchIndexablesProvider extends ContentProvider {    ....        //定义了查询路径        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);        mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_XML_RES_PATH,                MATCH_RES_CODE);        mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_RAW_PATH,                MATCH_RAW_CODE);        mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH,                MATCH_NON_INDEXABLE_KEYS_CODE);    ....    @Override    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,                        String sortOrder) {        switch (mMatcher.match(uri)) {            // 匹配不同的Uri进行查找            case MATCH_RES_CODE:                return queryXmlResources(null);            case MATCH_RAW_CODE:                return queryRawData(null);            case MATCH_NON_INDEXABLE_KEYS_CODE:                return queryNonIndexableKeys(null);            default:                throw new UnsupportedOperationException("Unknown Uri " + uri);        }    }    @Override    public String getType(Uri uri) {        switch (mMatcher.match(uri)) {            case MATCH_RES_CODE:                return SearchIndexablesContract.XmlResource.MIME_TYPE;            case MATCH_RAW_CODE:                return SearchIndexablesContract.RawData.MIME_TYPE;            case MATCH_NON_INDEXABLE_KEYS_CODE:                return SearchIndexablesContract.NonIndexableKey.MIME_TYPE;            default:                throw new IllegalArgumentException("Unknown URI " + uri);        }    }    ....}//采用 MatrixCursor 构建虚拟的数据表public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider {    private static final String TAG = "SettingsSearchIndexablesProvider";    @Override    public boolean onCreate() {        return true;    }    @Override    public Cursor queryXmlResources(String[] projection) {        MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);        //通过 SearchIndexableResources.values() 获取到所有添加到map集合中的 SearchIndexableResource        /*          SearchIndexableResource的路径为: /frameworks/base/core/java/android/provider/SearchIndexableResource.java          其中定义了    this.rank = rank;                        this.xmlResId = xmlResId;                        this.className = className;                        this.iconResId = iconResId;          等属性        */        Collection<SearchIndexableResource> values = SearchIndexableResources.values();         for (SearchIndexableResource val : values) {            Object[] ref = new Object[7];            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] = null; // intent action            ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = null; // intent target package            ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = null; // intent target class            cursor.addRow(ref);        }        return cursor;    }    @Override    public Cursor queryRawData(String[] projection) {        MatrixCursor result = new MatrixCursor(INDEXABLES_RAW_COLUMNS);        return result;    }    // 该方法返回当前布局不想被搜索到的设置项    @Override    public Cursor queryNonIndexableKeys(String[] projection) {        MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);        return cursor;    }}接着//Index#addIndexablesForXmlResourceUriprivate void addIndexablesForXmlResourceUri(Context packageContext, String packageName,            Uri uri, String[] projection, int baseRank) {        // 获取指定包对应的ContentResolver        final ContentResolver resolver = packageContext.getContentResolver();        final Cursor cursor = resolver.query(uri, projection, null, null, null);        if (cursor == null) {            Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());            return;        }        try {            final int count = cursor.getCount();            if (count > 0) {                while (cursor.moveToNext()) {                    final int providerRank = cursor.getInt(COLUMN_INDEX_XML_RES_RANK);                    final int rank = (providerRank > 0) ? baseRank + providerRank : baseRank;                    final int xmlResId = cursor.getInt(COLUMN_INDEX_XML_RES_RESID);                    final String className = cursor.getString(COLUMN_INDEX_XML_RES_CLASS_NAME);                    final int iconResId = cursor.getInt(COLUMN_INDEX_XML_RES_ICON_RESID);                    final String action = cursor.getString(COLUMN_INDEX_XML_RES_INTENT_ACTION);                    final String targetPackage = cursor.getString(                            COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE);                    final String targetClass = cursor.getString(                            COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS);                    SearchIndexableResource sir = new SearchIndexableResource(packageContext);                    sir.rank = rank;                    sir.xmlResId = xmlResId;                    sir.className = className;                    sir.packageName = packageName;                    sir.iconResId = iconResId;                    sir.intentAction = action;                    sir.intentTargetPackage = targetPackage;                    sir.intentTargetClass = targetClass;                    // 解析cursor数据,并且添加到内存UpdateData的dataToUpdate属性上, dataToUpdate属性是一个list集合                    addIndexableData(sir);                }            }        } finally {            cursor.close();        }}public void addIndexableData(SearchIndexableData data) {        synchronized (mDataToProcess) {            mDataToProcess.dataToUpdate.add(data);        }}//Index#updateInternal更新到数据库中private void updateInternal() {        synchronized (mDataToProcess) {            final UpdateIndexTask task = new UpdateIndexTask();            // 拷贝一个mDataToProcess对象的副本,前面将数据添加到mDataToProcess对象中。            UpdateData copy = mDataToProcess.copy();            // 执行UpdateIndexTask,UpdateIndexTask会将copy对象保存到数据库里            task.execute(copy);            mDataToProcess.clear();        }}//接下来的调用流程为:Index$UpdateIndexTask  doInBackground -->       processDataToUpdate(database, localeStr, dataToUpdate, nonIndexableKeys, forceUpdate) --> // 插入或者更新当前数据库内容             indexOneSearchIndexableData(database, localeStr, data, nonIndexableKeys) -->  // 继续indexOneSearchIndexableData更新数据库private void indexOneSearchIndexableData(SQLiteDatabase database, String localeStr,            SearchIndexableData data, Map<String, List<String>> nonIndexableKeys) {        //两种方式添加数据库        if (data instanceof SearchIndexableResource) {            indexOneResource(database, localeStr, (SearchIndexableResource) data, nonIndexableKeys);        } else if (data instanceof SearchIndexableRaw) {            indexOneRaw(database, localeStr, (SearchIndexableRaw) data);        }}        接下来调用:        indexFromResource() -->  //List<SearchIndexableResource> resList = provider.getXmlResourcesToIndex(context, enabled) 获取当前布局不想被搜索到的设置项                indexFromProvider() --> //List<SearchIndexableResource> resList = provider.getXmlResourcesToIndex(context, enabled)  需要解析的布局                      indexFromResource() -->  //使用XmlResourceParser解析xml布局                            updateOneRowWithFilteredData() -->                                   updateOneRow() -->  //此方法最终将解析的数据更新至数据库//在settings中添加搜索项:在SearchIndexableResources 中维护了一个sResMap,其中添加了所有的SearchIndexableResource,每一个子页面对应一个SearchIndexableResource,并且在SearchIndexableResources 提供了一个values()方法,用来返回当前集合中的所有数据,其实SearchIndexableResources.values()是在SettingsSearchIndexablesProvider中用到的,SettingsSearchIndexablesProvider重写了queryXmlResources方法,并且通过SearchIndexableResources.values()会返回setting中所有的子页面,最后封装成一个cursor.在SearchIndexableResources的静态代码块中初始化了:    static {        sResMap.put(WifiSettings.class.getName(),                new SearchIndexableResource(                        Ranking.getRankForClassName(WifiSettings.class.getName()),                        NO_DATA_RES_ID,                        WifiSettings.class.getName(),                        R.drawable.ic_settings_wireless));            sResMap.put(SavedAccessPointsWifiSettings.class.getName(),                new SearchIndexableResource(                        Ranking.getRankForClassName(SavedAccessPointsWifiSettings.class.getName()),                        R.xml.wifi_display_saved_access_points,                        SavedAccessPointsWifiSettings.class.getName(),                        R.drawable.ic_settings_wireless));    }两种方式,一种是直接在new SearchIndexableResource() 时传入布局文件,另一种为"NO_DATA_RES_ID"表示此搜索项匹配没有需要解析的xml文件,此xml的解析在Index.java中,采用第二种时,需要在对应的类中创建一个SEARCH_INDEX_DATA_PROVIDER,类型为SearchIndexProvider,继承BaseSearchIndexProvider并复写其两个方法: getXmlResourcesToIndex 和 getNonIndexableKeys.以SecuritySettings为例:    /**     * For Search. Please keep it in sync when updating "createPreferenceHierarchy()"     */    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new SecuritySearchIndexProvider();    private static class SecuritySearchIndexProvider extends BaseSearchIndexProvider {        @Override        public List<SearchIndexableResource> getXmlResourcesToIndex(                Context context, boolean enabled) {            final List<SearchIndexableResource> index = new ArrayList<SearchIndexableResource>();            //返回需要解析的布局            index.add(getSearchResource(context, R.xml.security_settings_misc));            return index;        }        @Override        public List<String> getNonIndexableKeys(Context context) {            // 该方法返回当前布局不想被搜索到的设置项            final List<String> keys = new ArrayList<String>();            LockPatternUtils lockPatternUtils = new LockPatternUtils(context);            // Do not display SIM lock for devices without an Icc card            final UserManager um = UserManager.get(context);            final TelephonyManager tm = TelephonyManager.from(context);            if (!um.isAdminUser() || !tm.hasIccCard()) {                keys.add(KEY_SIM_LOCK);            }            if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_CREDENTIALS)) {                keys.add(KEY_CREDENTIALS_MANAGER);            }            // TrustAgent settings disappear when the user has no primary security.            if (!lockPatternUtils.isSecure(MY_USER_ID)) {                // keys.add(KEY_TRUST_AGENT); //Move To LockScreen Settings                keys.add(KEY_MANAGE_TRUST_AGENTS);            }            return keys;        }在搜索过程中,会从数据库中查找匹配,点击筛选结果,根据className启动对应的界面,代码实现在SearchResultsSummary类中:    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,                             Bundle savedInstanceState) {        final View view = inflater.inflate(R.layout.search_panel, container, false);        mLayoutSuggestions = (ViewGroup) view.findViewById(R.id.layout_suggestions);        mLayoutResults = (ViewGroup) view.findViewById(R.id.layout_results);        mResultsListView = (ListView) view.findViewById(R.id.list_results);        mResultsListView.setAdapter(mResultsAdapter);        mResultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { // mResultsListView就是查询的结果列表            @Override            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {                // We have a header, so we need to decrement the position by one                position--;                // Some Monkeys could create a case where they were probably clicking on the                // List Header and thus the position passed was "0" and then by decrement was "-1"                if (position < 0) {                    return;                }                final Cursor cursor = mResultsAdapter.mCursor;                cursor.moveToPosition(position);                final String className = cursor.getString(Index.COLUMN_INDEX_CLASS_NAME);                 final String screenTitle = cursor.getString(Index.COLUMN_INDEX_SCREEN_TITLE);                final String action = cursor.getString(Index.COLUMN_INDEX_INTENT_ACTION);                final String key = cursor.getString(Index.COLUMN_INDEX_KEY);                final SettingsActivity sa = (SettingsActivity) getActivity();                sa.needToRevertToInitialFragment();                if (TextUtils.isEmpty(action)) {                    Bundle args = new Bundle();                    args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);                    Utils.startWithFragment(sa, className, args, null, 0, -1, screenTitle); // 通过className启动Settings中对应的Fragment界面                } else {                    final Intent intent = new Intent(action);                    final String targetPackage = cursor.getString(                            Index.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);                    final String targetClass = cursor.getString(                            Index.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS);                    if (!TextUtils.isEmpty(targetPackage) && !TextUtils.isEmpty(targetClass)) {                        final ComponentName component =                                new ComponentName(targetPackage, targetClass);                        intent.setComponent(component);                    }                    intent.putExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);                    sa.startActivity(intent);                }                saveQueryToDatabase();            }        });    }

4 0
原创粉丝点击