settings Fast Search Logic Carding (Part I)

Keywords: Mobile Android Java Database Google

Beginning with Android 5.1, google has provided users with a very convenient search function. Users can easily search settings in settings or other configurations of settings for applications specified to inherit from Search Indexables Provider, which greatly improves the search efficiency.

Based on Android 8.1, this paper combs the index logic of fast search with thick lines.

Settings Activity. java, which is executed when settings are started, initializes Index. The initialization process will be analyzed later:

     if (mIsShowingDashboard|| mIsMainScreen) {
            // Run the Index update only if we have some space
            if (!Utils.isLowStorage(this)) {
                long indexStartTime = System.currentTimeMillis();
                Index.getInstance(getApplicationContext()).update();
                if (DEBUG_TIMING) Log.d(LOG_TAG, "Index.update() took "
                        + (System.currentTimeMillis() - indexStartTime) + " ms");
            } else {
                Log.w(LOG_TAG, "Cannot update the Indexer as we are running low on storage space!");
            }
        }

And every time the language configuration changes, it will be reinitialized:

 /**
     * Switching the current user or changing the current language is called back, and the system maintains a separate search_index.db for each user.
     *
     */
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Index.getInstance(this).update();
    }

Next, the initialization process is analyzed:

First, a singleton Index is created, and then update():

public void update() {
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                /**
                 * Find all Providers in the system with "android.content.action.SEARCH_INDEXABLES_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;

                    addIndexablesFromRemoteProvider(packageName, authority);  //1
                    addNonIndexablesKeysFromRemoteProvider(packageName, authority);
                }

                mDataToProcess.fullIndex = true;
                /**
                 * The addIndexables FromRemoteProvider above adds settings to an mDataToProcess object in memory.
                 * updateInternal Update this object to the database
                 */
                updateInternal();
            }
        });
    }

PROVIDER_INTERFACE has been configured on more than one settings in the system, so a List < ResolveInfo > array is returned, and 1 is executed for each element:

private boolean addIndexablesFromRemoteProvider(String packageName, String authority) {
        LogUtils.printIndexLog("addIndexablesFromRemoteProvider 2222");
        try {
            /**
             * rank Is a value calculated according to a specified algorithm, which is used to display the priority to the user when searching.
             */
            final int baseRank = Ranking.getBaseRankForAuthority(authority);
            /**
             * mBaseAuthority It's com.android.settings, and authority is the package name of other APP s.
             */
            final Context context = mBaseAuthority.equals(authority) ?
                    mContext : mContext.createPackageContext(packageName, 0);
            /**
             * Build the search URI and build it according to authority. For settings, the result is
             * content://com.android.settings/settings/indexables_xml_res
             */
            final Uri uriForResources = buildUriForXmlResources(authority);
            /**
             * Two ways to add to a database, for example
             */
            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;
        }
    }

The above mBaseAuthority is this, com.android.settings, which was passed in when Index was created, and then uri is constructed, and addIndexables ForXmlResourceUri is executed:

private void addIndexablesForXmlResourceUri(Context packageContext, String packageName,
            Uri uri, String[] projection, int baseRank) {
        /**
         * Get resolver from context
         */
        final ContentResolver resolver = packageContext.getContentResolver();
        /**
         * SearchIndexables Provider is called here
         */
        final Cursor cursor = resolver.query(uri, projection, null, null, null);  //1

        if (cursor == null) {
            Log.w(LOG_TAG, "Cannot add index data for Uri: " + uri.toString());
            return;
        }

        try {
            final int count = cursor.getCount();
            LogUtils.printIndexLog("addIndexablesForXmlResourceUri 33333 --- count = "+count);
            if (count > 0) {
                /**
                 * Parse cursor data and add it to the dataToUpdate property of the memory Update Data, which is a list collection
                 */
                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;
                    /**
                     * Add memory
                     */
                    addIndexableData(sir);
                }
            }
        } finally {
            cursor.close();
        }
    }

First, the type of resolver is ApplicationContentResolver. The parent class is located under frameworks/base/core/java/android/content/ContentResolver.java:

 public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
            @Nullable String[] projection, @Nullable Bundle queryArgs,
            @Nullable CancellationSignal cancellationSignal) {
        Preconditions.checkNotNull(uri, "uri");
        IContentProvider unstableProvider = acquireUnstableProvider(uri);  //1
        if (unstableProvider == null) {
            return null;
        }
        IContentProvider stableProvider = null;
        Cursor qCursor = null;
        try {
            long startTime = SystemClock.uptimeMillis();

            ICancellationSignal remoteCancellationSignal = null;
            if (cancellationSignal != null) {
                cancellationSignal.throwIfCanceled();
                remoteCancellationSignal = unstableProvider.createCancellationSignal();
                cancellationSignal.setRemote(remoteCancellationSignal);
            }
            try {
                qCursor = unstableProvider.query(mPackageName, uri, projection,
                        queryArgs, remoteCancellationSignal);
            }
        ......
}

Figure 1

A call to acquireUnstableProvider() function at one point calls to its subclass, the application ContentResolver implementation described above:

 @Override
        protected IContentProvider acquireProvider(Context context, String auth) {
            return mMainThread.acquireProvider(context,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), true);
        }

The final call is mMainThread, the ActivityThread where the activity is located:

public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);  //1
        if (provider != null) {
            return provider;
        }

        // There is a possible race here.  Another thread may try to acquire
        // the same provider at the same time.  When this happens, we want to ensure
        // that the first one wins.
        // Note that we cannot hold the lock while acquiring and installing the
        // provider since it might take a long time to run and it could also potentially
        // be re-entrant in the case where the provider is in the same process.
        ContentProviderHolder holder = null;
        try {
            holder = ActivityManager.getService().getContentProvider(
                    getApplicationThread(), auth, userId, stable);  //2
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        if (holder == null) {
            Slog.e(TAG, "Failed to find provider info for " + auth);
            return null;
        }

        // Install provider will increment the reference count for us, and break
        // any ties in the race.
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);
        return holder.provider;
    }

In order to get the core of the provider, we call 1 directly from acquireExistingProvider, which is actually found in a map according to the name we passed in. If the search fails to execute 2 cross-process calls to AMS, it is actually found from a remote map. AMS instantiates all ContentProvider s and caches them in this map.

So what is it when you find it? For settings, it's actually the Settings SearchIndexables Provider. Java class:

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);
        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;
    }
    .......
}

This article first analyses here, and then combs the logic of the next chapter.

Posted by bachx on Sun, 20 Jan 2019 09:03:12 -0800