启用个人内容索引

通过个人内容索引,您的应用可以针对与用户帐号相关的内容,在 Google 应用中提供相关的搜索结果。用户只能在其设备上的搜索建议和个性化搜索结果中查看个人内容。

例如,如果用户搜索“最喜爱的鸡肉食谱”,则个性化搜索结果标签中会包含他们在食谱应用中为某个鸡肉食谱添加的备注。

如果您的应用不包含任何个人内容,您可以跳过此步骤,直接进入记录用户操作部分。

在开始之前,请确保您支持指向应用内容的链接,并且已经向您的应用添加 App Indexing 库

建立索引并添加对象

创建一个继承 JobIntentService 的类。此文档中概述的实现操作使用 JobIntentService 类将对设备上索引的更新排入队列,不过您也可以使用另一个类来安排这项工作。如果您确实要使用 JobIntentService 类执行此实现操作,还请务必在您项目的 build.gradle 文件中添加 Android 支持库

然后,如需在应用的个人内容索引中添加内容,请在同一个类中创建 Indexable 对象:

结果类型

借助 App Indexing,您可以通过以下两种方式显示搜索结果:

  • 纯文本:Google 搜索会将已编入索引的内容显示为文本结果。
  • Slice:Google 搜索使用 Slice(即应用中的界面模板)提供动态的交互式搜索结果。

您可以选择最适合您应用的结果类型。纯文本结果实现起来不那么复杂,但 Slice 可提供更丰富的用户体验。

示例实现

纯文本结果

Java

public class AppIndexingUpdateService extends JobIntentService {

    // Job-ID must be unique across your whole app.
    private static final int UNIQUE_JOB_ID = 42;

    public static void enqueueWork(Context context) {
        enqueueWork(context, AppIndexingUpdateService.class, UNIQUE_JOB_ID, new Intent());
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // TODO Insert your Indexable objects — for example, the recipe notes look as follows:

        ArrayList<Indexable> indexableNotes = new ArrayList<>();

        for (Recipe recipe : getAllRecipes()) {
            Note note = recipe.getNote();
            if (note != null) {
                Indexable noteToIndex = Indexables.noteDigitalDocumentBuilder()
                        .setName(recipe.getTitle() + " Note")
                        .setText(note.getText())
                        .setUrl(recipe.getNoteUrl())
                        .build();

                indexableNotes.add(noteToIndex);
            }
        }

        if (indexableNotes.size() > 0) {
            Indexable[] notesArr = new Indexable[indexableNotes.size()];
            notesArr = indexableNotes.toArray(notesArr);

            // batch insert indexable notes into index
            FirebaseAppIndex.getInstance().update(notesArr);
        }
    }

    // ...
}

Kotlin

class AppIndexingUpdateService : JobIntentService() {

    companion object {

        // Job-ID must be unique across your whole app.
        private const val UNIQUE_JOB_ID = 42

        fun enqueueWork(context: Context) {
            enqueueWork(context, AppIndexingUpdateService::class.java, UNIQUE_JOB_ID, Intent())
        }
    }

    override fun onHandleWork(intent: Intent) {
        // TODO Insert your Indexable objects — for example, the recipe notes look as follows:

        val indexableNotes = arrayListOf<Indexable>()

        for (recipe in getAllRecipes()) {
            val note = recipe.note
            if (note != null) {
                val noteToIndex = Indexables.noteDigitalDocumentBuilder()
                        .setName(recipe.title + " Note")
                        .setText(note.text)
                        .setUrl(recipe.noteUrl)
                        .build()

                indexableNotes.add(noteToIndex)
            }
        }

        if (indexableNotes.size > 0) {
            val notesArr: Array<Indexable> = indexableNotes.toTypedArray()

            // batch insert indexable notes into index
            FirebaseAppIndex.getInstance().update(*notesArr)
        }
    }

    // ...
}
Slice 结果

借助 Slice,您可以指定应用的部分内容显示在 Google 搜索查询的响应中。这些 Slice 充当您的 Indexables 内容的界面,所以每个 Slice 应直接映射到一个 Indexable。请按照以下步骤为您的应用生成启用 Slice 的结果:

  1. 按照 Slice 入门指南中的说明将 Slice 添加到您的应用。
  2. 使用 Slice 元数据和侦听器添加 Indexables
package com.example.myapp;

import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v4.app.JobIntentService;
import com.google.firebase.appindexing.builders.Indexables;
import com.google.firebase.appindexing.FirebaseAppIndex;
import com.google.firebase.appindexing.Indexable;
import java.util.ArrayList;

public class AppIndexingUpdateService extends JobIntentService {
  // Job-ID must be unique across your whole app.
  private static final int UNIQUE_JOB_ID = 42;

  public static void enqueueWork(Context context) {
    enqueueWork(context, AppIndexingUpdateService.class, UNIQUE_JOB_ID, new Intent());
  }

  @Override
  protected void onHandleWork(@NonNull Intent intent) {
    // TODO Insert your Indexable objects — for example, the recipe notes look as follows:

    ArrayList<Indexable> indexableNotes = new ArrayList<>();

    for (Recipe recipe : getAllRecipes()) {
        Note note = recipe.getNote();
        if (note != null) {
            Indexable noteToIndex = Indexables.noteDigitalDocumentBuilder()
                    .setName(recipe.getTitle() + " Note")
                    .setText(note.getText())
                    .setUrl(recipe.getNoteUrl())
                    .setMetadata(
                      new Indexable.Metadata.Builder().setSliceUri(
                        Uri.parse("content://com.example.myapp.sliceprovider/note?id=" +
                                  recipe.getNoteId())
                    ))
                    .build();

            indexableNotes.add(noteToIndex);
        }
    }

    if (indexableNotes.size() > 0) {
        Indexable[] notesArr = new Indexable[indexableNotes.size()];
        notesArr = indexableNotes.toArray(notesArr);

        // batch insert indexable notes into index
        FirebaseAppIndex.getInstance().update(notesArr)
                        .addOnSuccessListener(unused -> {
                          Log.d("RecipeApp", "Update succeeded!");
                        })
                        .addOnFailureListener(exception -> {
                          Log.d("RecipeApp", "Update failed: " + exception);
                        });
    }
  }
}

个人内容索引中应包含什么内容?

为以下类型的内容定义 Indexable 对象:

  • 针对特定用户的内容,例如消息、照片或文档。
  • 对用户很重要的内容,例如收藏内容或他们可能想要经常访问的内容。例如,他们已加入书签或标记为离线使用的文档或歌曲。
  • 在您的应用中生成的内容,而不是通过该应用仅仅访问过的内容。例如,由用户直接在您的应用中创建并由您的应用存储的联系人信息,而不是来自手机通讯录的联系人信息。

向您的应用添加广播接收器

Google Play 服务会定期向您的应用发送请求,以更新其设备上的索引。这样一来,设备上的索引就可以从您的应用获取最新的内容。BroadcastReceiver 类会收到此请求并触发 JobIntentService 来处理索引编制工作。下面的示例使用的是上一步中提到的 AppIndexingUpdateService 类。

Java

/** Receives broadcast for App Indexing Update. */
public class AppIndexingUpdateReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null
                && FirebaseAppIndex.ACTION_UPDATE_INDEX.equals(intent.getAction())) {
            // Schedule the job to be run in the background.
            AppIndexingUpdateService.enqueueWork(context);
        }
    }

}

Kotlin

/** Receives broadcast for App Indexing Update. */
class AppIndexingUpdateReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent?) {
        if (intent != null && FirebaseAppIndex.ACTION_UPDATE_INDEX == intent.action) {
            // Schedule the job to be run in the background.
            AppIndexingUpdateService.enqueueWork(context)
        }
    }
}

生成和刷新索引

接下来,允许 Google Play 服务调用 App Indexing 服务在以下三种情况下将设备上的索引更新为用户个人内容:

  • 当应用已安装在设备上时。
  • 如果现有版本的应用已更新为可支持个人内容索引的版本。
  • 每过一段时间定期调用以准确反映索引内容发生的任何更改,从而确保无缝的用户体验。

此外,如果设备上的索引由于任何原因丢失(例如,如果索引已损坏),则进行更新索引的调用后将重新填充索引。

AndroidManifest.xml 文件中注册 BroadcastReceiver,以便它接收来自 Google Play 服务的更新索引调用。下面的示例使用 JobIntentService 类来安排索引更新:

<!-- Register the BroadcastReceiver -->
<receiver
    android:name=".AppIndexingUpdateReceiver"
    android:exported="true"
    android:permission="com.google.android.gms.permission.APPINDEXING">
    <intent-filter>
        <action android:name="com.google.firebase.appindexing.UPDATE_INDEX" />
    </intent-filter>
</receiver>

<!-- Grant the AppIndexingUpdateService permission and enable it to run after being triggered -->
<service
    android:name=".AppIndexingUpdateService"
    android:permission="android.permission.BIND_JOB_SERVICE" />

更新索引

当用户添加、更新或移除其个人内容时,设备上的索引应当反映这些变化。添加以下代码来更新索引中的内容:

更新纯文本内容

Java

public void indexNote(Recipe recipe) {
    Note note = recipe.getNote();
    Indexable noteToIndex = Indexables.noteDigitalDocumentBuilder()
            .setName(recipe.getTitle())
            .setText(note.getText())
            .setUrl(recipe.getNoteUrl())
            .build();

    Task<Void> task = FirebaseAppIndex.getInstance().update(noteToIndex);
    // ...
}

Kotlin

fun indexNote(recipe: Recipe) {
    val note = recipe.note
    val noteToIndex = Indexables.noteDigitalDocumentBuilder()
            .setName(recipe.title)
            .setText(note!!.text)
            .setUrl(recipe.noteUrl)
            .build()

    val task = FirebaseAppIndex.getInstance().update(noteToIndex)
    // ...
}
更新 Slice

Firebase App Indexing 在编制索引时检索 Slice 内容,存储该内容,并在查询时显示 Slice 快照。如果在索引编制到查询期间 Slice 内容发生变化,则该快照可能会误导或混淆用户。要避免这种情况,请更新 Slice 并同时将 Indexable 重新编入索引,以便 Firebase App Indexing 始终具有最新的 Slice 内容。

private void indexNote() {
  Note note = mRecipe.getNote();
  Indexable noteToIndex = Indexables.noteDigitalDocumentBuilder()
          .setName(mRecipe.getTitle())
          .setText(note.getText())
          .setUrl(mRecipe.getNoteUrl())
          .setKeywords("recipe", "cooking")
          .setMetadata(
              new Indexable.Metadata.Builder().setSliceUri(
              Uri.parse("content://com.example.myapp.sliceprovider/note?id=" + note.getNoteId())))
          .build();

  Task<Void> task = FirebaseAppIndex.getInstance().update(noteToIndex);
   ...
}
移除

如需标识应移除的内容,请使用该内容的网址。将以下代码添加到您的应用针对该内容的删除或移除函数中。

Java

// Deletes or removes the corresponding notes from index.
String noteUrl = recipe.getNoteUrl();
FirebaseAppIndex.getInstance().remove(noteUrl);

Kotlin

// Deletes or removes the corresponding notes from index.
val noteUrl = recipe.noteUrl
FirebaseAppIndex.getInstance().remove(noteUrl)

当用户退出帐号时删除索引

当用户在您的应用中退出帐号从而产生退出帐号事件时,应删除设备上的索引。 添加使用 AppIndexingService 生成和刷新索引的调用,以便当用户再次登录时重新填充设备上的索引。

要在用户从应用退出帐号时删除设备上的索引,请将以下代码添加到您的应用中:

Java

FirebaseAppIndex.getInstance().removeAll();

Kotlin

FirebaseAppIndex.getInstance().removeAll()

下一步:记录用户操作