AppSearch הוא פתרון חיפוש במכשיר עם ביצועים גבוהים לניהול נתונים מובְנים שמאוחסנים באופן מקומי. הוא מכיל ממשקי API להוספת נתונים לאינדקס ולאחזור נתונים באמצעות חיפוש טקסט מלא. אפליקציות יכולות להשתמש ב-AppSearch כדי להציע יכולות חיפוש בהתאמה אישית בתוך האפליקציה, שמאפשרות למשתמשים לחפש תוכן גם במצב אופליין.
![תרשים שממחיש הוספה לאינדקס וחיפוש ב-AppSearch](https://developer.android.google.cn/static/images/guide/topics/search/appsearch.png?authuser=0&hl=he)
AppSearch מספק את התכונות הבאות:
- הטמעה מהירה של אחסון שמתאימה במיוחד לנייד, עם שימוש נמוך ב-I/O
- הוספה של נתונים לאינדקס ושליחת שאילתות יעילות במיוחד על קבוצות נתונים גדולות
- תמיכה במספר שפות, כמו אנגלית וספרדית
- דירוג רלוונטיות וסימון שימוש
בגלל השימוש הנמוך יותר ב-I/O, זמן האחזור של AppSearch להוספה לאינדקס ולחיפוש במערכי נתונים גדולים קצר יותר בהשוואה ל-SQLite. AppSearch מפשט שאילתות מסוגים שונים על ידי תמיכה בשאילתות יחידות, בעוד ש-SQLite ממזג תוצאות ממספר טבלאות.
כדי להמחיש את התכונות של AppSearch, ניקח לדוגמה אפליקציית מוזיקה שמנהלת את השירים האהובים של המשתמשים ומאפשרת להם לחפש אותם בקלות. המשתמשים נהנים ממוזיקה מרחבי העולם עם שמות שירים בשפות שונות, ו-AppSearch תומך באופן מקורי בהוספה שלהם לאינדקס ובשליחת שאילתות לגביהם. כשהמשתמש מחפש שיר לפי שם או לפי שם האומן, האפליקציה מעבירה את הבקשה ל-AppSearch כדי לאחזר שירים תואמים במהירות וביעילות. התוצאות מוצגות באפליקציה, ומאפשרות למשתמשים להתחיל להשמיע את השירים האהובים עליהם במהירות.
הגדרה
כדי להשתמש ב-AppSearch באפליקציה, מוסיפים את יחסי התלות הבאים לקובץ build.gradle
של האפליקציה:
מגניב
dependencies { def appsearch_version = "1.1.0-alpha05" implementation "androidx.appsearch:appsearch:$appsearch_version" // Use kapt instead of annotationProcessor if writing Kotlin classes annotationProcessor "androidx.appsearch:appsearch-compiler:$appsearch_version" implementation "androidx.appsearch:appsearch-local-storage:$appsearch_version" // PlatformStorage is compatible with Android 12+ devices, and offers additional features // to LocalStorage. implementation "androidx.appsearch:appsearch-platform-storage:$appsearch_version" }
Kotlin
dependencies { val appsearch_version = "1.1.0-alpha05" implementation("androidx.appsearch:appsearch:$appsearch_version") // Use annotationProcessor instead of kapt if writing Java classes kapt("androidx.appsearch:appsearch-compiler:$appsearch_version") implementation("androidx.appsearch:appsearch-local-storage:$appsearch_version") // PlatformStorage is compatible with Android 12+ devices, and offers additional features // to LocalStorage. implementation("androidx.appsearch:appsearch-platform-storage:$appsearch_version") }
מושגים ב-AppSearch
בתרשים הבא מפורטים המושגים של AppSearch והאינטראקציות ביניהם.
איור 1. תרשים של מושגים ב-AppSearch: מסד נתונים של AppSearch, סכימה, סוגי סכימה, מסמכים, סשן וחיפוש.
מסד נתונים וסשן
מסד נתונים של AppSearch הוא אוסף של מסמכים שתואמים לסכימה של מסד הנתונים. אפליקציות לקוח יוצרות מסד נתונים על ידי מתן ההקשר של האפליקציה ושם של מסד נתונים. רק האפליקציה שיצרה אותן יכולה לפתוח את מסדי הנתונים. כשפותחים מסד נתונים, מוחזר סשן כדי לבצע אינטראקציה עם מסד הנתונים. הסשן הוא נקודת הכניסה לקריאה ל-API של AppSearch, והוא נשאר פתוח עד שאפליקציית הלקוח סוגרת אותו.
סכימות וסוגים של סכימות
סכמה מייצגת את המבנה הארגוני של הנתונים במסד הנתונים של AppSearch.
הסכימה מורכבת מסוגים של סכימה שמייצגים סוגים ייחודיים של נתונים. סוגי סכימות מורכבים ממאפיינים שמכילים שם, סוג נתונים ועושר יחסים. אחרי שמוסיפים סוג סכימה לסכימה של מסד הנתונים, אפשר ליצור מסמכים מסוג הסכימה הזה ולהוסיף אותם למסד הנתונים.
מסמכים
ב-AppSearch, יחידת נתונים מיוצגת כמסמך. כל מסמך במסד נתונים של AppSearch מזוהה באופן ייחודי באמצעות המרחב השם והמזהה שלו. מרחבי שמות משמשים להפרדת נתונים ממקורות שונים, כשצריך לשלוח שאילתה רק למקור אחד, כמו חשבונות משתמשים.
המסמכים מכילים חותמת זמן ליצירה, אורך חיים (TTL) ודירוג שאפשר להשתמש בו לדירוג במהלך אחזור. למסמך מוקצה גם סוג סכימה שמתאר מאפייני נתונים נוספים שהמסמך חייב לכלול.
סיווג מסמך הוא הפשטה של מסמך. הוא מכיל שדות עם הערות שמייצגים את תוכן המסמך. כברירת מחדל, השם של סוג המסמך מגדיר את השם של סוג הסכימה.
חיפוש
המסמכים נוספים לאינדקס וניתן לחפש אותם באמצעות שאילתות. מסמך נחשב כמתאים ומוכלל בתוצאות החיפוש אם הוא מכיל את המונחים בשאילתה או תואם למפרט חיפוש אחר. התוצאות ממוינות לפי הציון שלהן ושיטה הדירוג. תוצאות החיפוש מיוצגות על ידי דפים שאפשר לאחזר ברצף.
ב-AppSearch יש התאמות אישיות לחיפוש, כמו מסננים, הגדרת גודל דף יצירת קטעי קוד.
אחסון בפלטפורמה, אחסון מקומי או אחסון ב-Play Services
ב-AppSearch יש שלושה פתרונות אחסון: LocalStorage
, PlatformStorage
ו-PlayServicesStorage
. באמצעות LocalStorage
, האפליקציה מנהלת אינדקס ספציפי לאפליקציה שנמצא בתיקיית נתוני האפליקציה. כשמשתמשים גם ב-PlatformStorage
וגם ב-PlayServicesStorage
, האפליקציה תורמת לאינדקס מרכזי ברמת המערכת. האינדקס של PlatformStorage
מתארח בשרת המערכת, והאינדקס של PlayServicesStorage
מתארח באחסון של Google Play Services. הגישה לנתונים בתוך המדדים המרכזיים האלה מוגבלת לנתונים שהאפליקציה שלכם תרמה ונתונים שאפליקציה אחרת שיתפה איתכם באופן מפורש. לכל אפשרויות האחסון האלה יש את אותו ממשק API, ואפשר להחליף ביניהן בהתאם לגרסה של המכשיר:
Kotlin
if (BuildCompat.isAtLeastS()) { appSearchSessionFuture.setFuture( PlatformStorage.createSearchSession( PlatformStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build() ) ) } else { if (usePlayServicesStorageBelowS) { appSearchSessionFuture.setFuture( PlayServicesStorage.createSearchSession( PlayServicesStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build() ) ) } else { appSearchSessionFuture.setFuture( LocalStorage.createSearchSession( LocalStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build() ) ) } }
Java
if (BuildCompat.isAtLeastS()) { mAppSearchSessionFuture.setFuture(PlatformStorage.createSearchSession( new PlatformStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); } else { if (usePlayServicesStorageBelowS) { mAppSearchSessionFuture.setFuture(PlayServicesStorage.createSearchSession( new PlayServicesStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); } else { mAppSearchSessionFuture.setFuture(LocalStorage.createSearchSession( new LocalStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); } }
באמצעות PlatformStorage
ו-PlayServicesStorage
, האפליקציה שלכם יכולה לשתף נתונים באופן מאובטח עם אפליקציות אחרות, כדי לאפשר להן לחפש גם בנתונים של האפליקציה. שיתוף נתוני אפליקציה לקריאה בלבד מותנה בהשתמש בלחיצת יד של אישור כדי לוודא שלאפליקציה השנייה יש הרשאה לקרוא את הנתונים. מידע נוסף על ה-API הזה זמין במסמכי העזרה של setSchemaTypeVisibilityForPackage()
.
בנוסף, בעזרת PlatformStorage
אפשר להציג נתונים שנוספו לאינדקס בממשקי System UI. אפליקציות יכולות לבקש שלא יוצגו חלק מהנתונים שלהן או כולם בממשק המשתמש של המערכת. מידע נוסף על ה-API הזה זמין במסמכי התיעוד של setSchemaTypeDisplayedBySystem()
.
תכונות | LocalStorage (תואם ל-Android 5.0 ואילך) |
PlatformStorage (תואם ל-Android 12 ואילך) |
PlayServicesStorage (תואם ל-Android 5.0 ואילך) |
---|---|---|---|
חיפוש טקסט מלא יעיל | |||
תמיכה בכמה שפות | |||
גודל בינארי מופחת | |||
שיתוף נתונים בין אפליקציות | |||
יכולת להציג נתונים בממשקי המשתמש של המערכת | |||
אפשר להוסיף לאינדקס מסמכים בכמות ובגודל בלתי מוגבלים | |||
פעולות מהירות יותר ללא זמן אחזור נוסף של הקישור |
יש עוד שיקולים שצריך להביא בחשבון כשבוחרים בין LocalStorage
לבין PlatformStorage
. מכיוון ש-PlatformStorage
עוטף את ממשקי Jetpack API בשירות המערכת של AppSearch, ההשפעה על גודל ה-APK היא מינימלית בהשוואה לשימוש ב-LocalStorage. עם זאת, המשמעות היא גם שלפעולות AppSearch יש זמן אחזור נוסף של הקישור כשקוראים לשירות המערכת של AppSearch. בעזרת PlatformStorage
, AppSearch מגביל את מספר המסמכים ואת הגודל של המסמכים שאפשר להוסיף לאינדקס של האפליקציה, כדי להבטיח אינדקס מרכזי יעיל. גם ל-PlayServicesStorage
יש את אותן המגבלות כמו ל-PlatformStorage
, והיא נתמכת רק במכשירים עם Google Play Services.
תחילת השימוש ב-AppSearch
בדוגמה שבקטע הזה נסביר איך משתמשים בממשקי ה-API של AppSearch כדי לשלב את השירות עם אפליקציה היפותטית לניהול הערות.
כתיבת סוג מסמך
השלב הראשון בשילוב עם AppSearch הוא לכתוב סיווג מסמכים כדי לתאר את הנתונים שרוצים להוסיף למסד הנתונים. מסמנים כיתה ככיתה של מסמכים באמצעות ההערה @Document
.אפשר להשתמש במופעים של כיתת המסמכים כדי להוסיף מסמכים למסד הנתונים ולשלוף מסמכים ממנו.
הקוד הבא מגדיר סוג מסמך של Note עם שדה הערה @Document.StringProperty
להוספה של טקסט של אובייקט Note לאינדקס.
Kotlin
@Document public data class Note( // Required field for a document class. All documents MUST have a namespace. @Document.Namespace val namespace: String, // Required field for a document class. All documents MUST have an Id. @Document.Id val id: String, // Optional field for a document class, used to set the score of the // document. If this is not included in a document class, the score is set // to a default of 0. @Document.Score val score: Int, // Optional field for a document class, used to index a note's text for this // document class. @Document.StringProperty(indexingType = AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES) val text: String )
Java
@Document public class Note { // Required field for a document class. All documents MUST have a namespace. @Document.Namespace private final String namespace; // Required field for a document class. All documents MUST have an Id. @Document.Id private final String id; // Optional field for a document class, used to set the score of the // document. If this is not included in a document class, the score is set // to a default of 0. @Document.Score private final int score; // Optional field for a document class, used to index a note's text for this // document class. @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES) private final String text; Note(@NonNull String id, @NonNull String namespace, int score, @NonNull String text) { this.id = Objects.requireNonNull(id); this.namespace = Objects.requireNonNull(namespace); this.score = score; this.text = Objects.requireNonNull(text); } @NonNull public String getNamespace() { return namespace; } @NonNull public String getId() { return id; } public int getScore() { return score; } @NonNull public String getText() { return text; } }
פתיחת מסד נתונים
לפני שעובדים עם מסמכים, צריך ליצור מסד נתונים. הקוד הבא יוצר מסד נתונים חדש בשם notes_app
ומקבל ListenableFuture
עבור AppSearchSession
, שמייצג את החיבור למסד הנתונים ומספק את ממשקי ה-API לפעולות במסד הנתונים.
Kotlin
val context: Context = getApplicationContext() val sessionFuture = LocalStorage.createSearchSession( LocalStorage.SearchContext.Builder(context, /*databaseName=*/"notes_app") .build() )
Java
Context context = getApplicationContext(); ListenableFuture<AppSearchSession> sessionFuture = LocalStorage.createSearchSession( new LocalStorage.SearchContext.Builder(context, /*databaseName=*/ "notes_app") .build() );
הגדרת סכימה
צריך להגדיר סכימה לפני שאפשר להוסיף מסמכים למסד הנתונים ולשלוף ממנו מסמכים. הסכימה של מסד הנתונים מורכבת מסוגים שונים של נתונים מובְנים, שנקראים 'סוגי סכימה'. הקוד הבא מגדיר את הסכימה על ידי מתן סוג הסכימה של סיווג המסמך.
Kotlin
val setSchemaRequest = SetSchemaRequest.Builder().addDocumentClasses(Note::class.java) .build() val setSchemaFuture = Futures.transformAsync( sessionFuture, { session -> session?.setSchema(setSchemaRequest) }, mExecutor )
Java
SetSchemaRequest setSchemaRequest = new SetSchemaRequest.Builder().addDocumentClasses(Note.class) .build(); ListenableFuture<SetSchemaResponse> setSchemaFuture = Futures.transformAsync(sessionFuture, session -> session.setSchema(setSchemaRequest), mExecutor);
הוספת מסמך למסד הנתונים
אחרי שמוסיפים סוג סכימה, אפשר להוסיף למסד הנתונים מסמכים מהסוג הזה.
הקוד הבא יוצר מסמך מסוג הסכימה Note
באמצעות ה-builder של סוג המסמך Note
. הוא מגדיר את מרחב השמות של המסמך user1
כדי לייצג משתמש שרירותי בדוגמה הזו. לאחר מכן המסמך מוכנס למסד הנתונים ומצורף מאזין כדי לעבד את התוצאה של פעולת ה-put.
Kotlin
val note = Note( namespace="user1", id="noteId", score=10, text="Buy fresh fruit" ) val putRequest = PutDocumentsRequest.Builder().addDocuments(note).build() val putFuture = Futures.transformAsync( sessionFuture, { session -> session?.put(putRequest) }, mExecutor ) Futures.addCallback( putFuture, object : FutureCallback<AppSearchBatchResult<String, Void>?> { override fun onSuccess(result: AppSearchBatchResult<String, Void>?) { // Gets map of successful results from Id to Void val successfulResults = result?.successes // Gets map of failed results from Id to AppSearchResult val failedResults = result?.failures } override fun onFailure(t: Throwable) { Log.e(TAG, "Failed to put documents.", t) } }, mExecutor )
Java
Note note = new Note(/*namespace=*/"user1", /*id=*/ "noteId", /*score=*/ 10, /*text=*/ "Buy fresh fruit!"); PutDocumentsRequest putRequest = new PutDocumentsRequest.Builder().addDocuments(note) .build(); ListenableFuture<AppSearchBatchResult<String, Void>> putFuture = Futures.transformAsync(sessionFuture, session -> session.put(putRequest), mExecutor); Futures.addCallback(putFuture, new FutureCallback<AppSearchBatchResult<String, Void>>() { @Override public void onSuccess(@Nullable AppSearchBatchResult<String, Void> result) { // Gets map of successful results from Id to Void Map<String, Void> successfulResults = result.getSuccesses(); // Gets map of failed results from Id to AppSearchResult Map<String, AppSearchResult<Void>> failedResults = result.getFailures(); } @Override public void onFailure(@NonNull Throwable t) { Log.e(TAG, "Failed to put documents.", t); } }, mExecutor);
חיפוש
אפשר לחפש מסמכים שנוספו לאינדקס באמצעות פעולות החיפוש שמפורטות בקטע הזה. הקוד הבא מבצע שאילתות למונח 'fruit' במסד הנתונים למסמכים ששייכים למרחב השמות user1
.
Kotlin
val searchSpec = SearchSpec.Builder() .addFilterNamespaces("user1") .build(); val searchFuture = Futures.transform( sessionFuture, { session -> session?.search("fruit", searchSpec) }, mExecutor ) Futures.addCallback( searchFuture, object : FutureCallback<SearchResults> { override fun onSuccess(searchResults: SearchResults?) { iterateSearchResults(searchResults) } override fun onFailure(t: Throwable?) { Log.e("TAG", "Failed to search notes in AppSearch.", t) } }, mExecutor )
Java
SearchSpec searchSpec = new SearchSpec.Builder() .addFilterNamespaces("user1") .build(); ListenableFuture<SearchResults> searchFuture = Futures.transform(sessionFuture, session -> session.search("fruit", searchSpec), mExecutor); Futures.addCallback(searchFuture, new FutureCallback<SearchResults>() { @Override public void onSuccess(@Nullable SearchResults searchResults) { iterateSearchResults(searchResults); } @Override public void onFailure(@NonNull Throwable t) { Log.e(TAG, "Failed to search notes in AppSearch.", t); } }, mExecutor);
איטרציה ב-SearchResults
החיפושים מחזירים מופע של SearchResults
, שמעניק גישה לדפים של אובייקטים מסוג SearchResult
. כל SearchResult
מכיל את GenericDocument
התואם שלו, שהוא הפורמט הכללי של המסמך שאליו כל המסמכים מומרים. הקוד הבא מקבל את הדף הראשון של תוצאות החיפוש וממיר את התוצאה חזרה למסמך Note
.
Kotlin
Futures.transform( searchResults?.nextPage, { page: List<SearchResult>? -> // Gets GenericDocument from SearchResult. val genericDocument: GenericDocument = page!![0].genericDocument val schemaType = genericDocument.schemaType val note: Note? = try { if (schemaType == "Note") { // Converts GenericDocument object to Note object. genericDocument.toDocumentClass(Note::class.java) } else null } catch (e: AppSearchException) { Log.e( TAG, "Failed to convert GenericDocument to Note", e ) null } note }, mExecutor )
Java
Futures.transform(searchResults.getNextPage(), page -> { // Gets GenericDocument from SearchResult. GenericDocument genericDocument = page.get(0).getGenericDocument(); String schemaType = genericDocument.getSchemaType(); Note note = null; if (schemaType.equals("Note")) { try { // Converts GenericDocument object to Note object. note = genericDocument.toDocumentClass(Note.class); } catch (AppSearchException e) { Log.e(TAG, "Failed to convert GenericDocument to Note", e); } } return note; }, mExecutor);
הסרת מסמך
כשהמשתמש מוחק הערה, האפליקציה מוחקת את המסמך התואם מסוג Note
ממסד הנתונים. כך תוכלו לוודא שההערה לא תופיע יותר בשאילתות. הקוד הבא שולח בקשה מפורשת להסרת המסמך Note
מהמסד נתונים לפי מזהה.
Kotlin
val removeRequest = RemoveByDocumentIdRequest.Builder("user1") .addIds("noteId") .build() val removeFuture = Futures.transformAsync( sessionFuture, { session -> session?.remove(removeRequest) }, mExecutor )
Java
RemoveByDocumentIdRequest removeRequest = new RemoveByDocumentIdRequest.Builder("user1") .addIds("noteId") .build(); ListenableFuture<AppSearchBatchResult<String, Void>> removeFuture = Futures.transformAsync(sessionFuture, session -> session.remove(removeRequest), mExecutor);
שמירה בדיסק
כדי לשמור עדכונים של מסדי נתונים בדיסק באופן קבוע, צריך להפעיל את הפונקציה requestFlush()
. הקוד הבא קורא ל-requestFlush()
עם מאזין כדי לקבוע אם הקריאה בוצעה בהצלחה.
Kotlin
val requestFlushFuture = Futures.transformAsync( sessionFuture, { session -> session?.requestFlush() }, mExecutor ) Futures.addCallback(requestFlushFuture, object : FutureCallback<Void?> { override fun onSuccess(result: Void?) { // Success! Database updates have been persisted to disk. } override fun onFailure(t: Throwable) { Log.e(TAG, "Failed to flush database updates.", t) } }, mExecutor)
Java
ListenableFuture<Void> requestFlushFuture = Futures.transformAsync(sessionFuture, session -> session.requestFlush(), mExecutor); Futures.addCallback(requestFlushFuture, new FutureCallback<Void>() { @Override public void onSuccess(@Nullable Void result) { // Success! Database updates have been persisted to disk. } @Override public void onFailure(@NonNull Throwable t) { Log.e(TAG, "Failed to flush database updates.", t); } }, mExecutor);
סגירת סשן
צריך לסגור את AppSearchSession
כשהאפליקציה לא קוראת יותר לפעולות של מסדי נתונים. הקוד הבא סוגר את הסשן של AppSearch שנפתח קודם, ושומר את כל העדכונים בדיסק.
Kotlin
val closeFuture = Futures.transform<AppSearchSession, Unit>(sessionFuture, { session -> session?.close() Unit }, mExecutor )
Java
ListenableFuture<Void> closeFuture = Futures.transform(sessionFuture, session -> { session.close(); return null; }, mExecutor);
מקורות מידע נוספים
מידע נוסף על AppSearch זמין במקורות המידע הבאים:
דוגמיות
- Android AppSearch Sample (Kotlin), אפליקציה לניהול הערות שמשתמשת ב-AppSearch כדי להוסיף הערות של משתמשים לאינדקס ולאפשר למשתמשים לחפש בהן.
שליחת משוב
אתם יכולים לשתף איתנו את המשוב והרעיונות שלכם באמצעות המשאבים הבאים:
דיווח על באגים כדי שנוכל לתקן אותם.