AppSearch הוא פתרון לחיפוש במכשיר עם ביצועים מעולים לניהול מקומי. נתונים מובנים מאוחסנים. הוא מכיל ממשקי API להוספת נתונים לאינדקס ולאחזור נתונים באמצעות חיפוש טקסט מלא. אפליקציות יכולות להשתמש ב-AppSearch כדי להציע התאמה אישית בתוך האפליקציה יכולות חיפוש, באופן שמאפשר למשתמשים לחפש תוכן גם במצב אופליין.
ב-AppSearch יש את התכונות הבאות:
- הטמעה מהירה של אחסון שמותאם לניידים עם שימוש נמוך בקלט/פלט (I/O)
- הוספה לאינדקס והרצת שאילתות בצורה יעילה מאוד על בסיס קבוצות נתונים גדולות
- תמיכה בכמה שפות, כמו אנגלית וספרדית
- דירוג הרלוונטיות וציון השימוש
עקב שימוש נמוך בקלט/פלט (I/O), ב-AppSearch יש זמן אחזור קצר יותר להוספה לאינדקס ולחיפוש ממערכי נתונים גדולים בהשוואה ל-SQLite. AppSearch מפשט שאילתות שונות על סוגים שונים באמצעות תמיכה בשאילתות בודדות, ואילו SQLite ממזג תוצאות מכמה טבלאות.
כדי להמחיש את התכונות של AppSearch, נבחן דוגמה למוזיקה אפליקציה שמנהלת את השירים האהובים על המשתמשים ומאפשרת למשתמשים לחפש בקלות עבורם. המשתמשים נהנים ממוזיקה מכל העולם, עם שמות של שירים בשפות שונות חדשות, ש-AppSearch תומכת בהן במקור באינדקס ובשאילתות. כאשר משתמש מחפש שיר לפי כותרת או שם אומן, האפליקציה פשוט עוברת בקשה ל-AppSearch כדי לאחזר במהירות וביעילות שירים תואמים. מציגה את התוצאות ומאפשרת למשתמשים להתחיל לשחק במהירות את השירים האהובים עליהם.
הגדרה
כדי להשתמש ב-AppSearch באפליקציה, צריך להוסיף את יחסי התלות הבאים
קובץ build.gradle
של האפליקציה:
Groovy
dependencies { def appsearch_version = "1.1.0-alpha07" 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-alpha07" 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 והאינטראקציות שלהם.
מסד נתונים וסשן
מסד הנתונים של AppSearch הוא אוסף של מסמכים שתואמים למסד הנתונים. של Google. אפליקציות לקוח יוצרות מסד נתונים באמצעות את ההקשר ואת השם של מסד הנתונים. רק האפליקציה יכולה לפתוח מסדי נתונים שיצר אותם. כשמסד נתונים נפתח, מוחזר סשן כדי לבצע אינטראקציה למסד הנתונים. הסשן הוא נקודת הכניסה לקריאה ל-AppSearch APIs. והוא נשאר פתוח עד שאפליקציית הלקוח סוגרת אותו.
סוגי סכימות וסכימות
סכימה מייצגת את המבנה הארגוני של הנתונים ב-AppSearch מסד נתונים.
הסכימה מורכבת מסוגי סכימות שמייצגות סוגים ייחודיים של נתונים. סוגי סכימה מורכבים ממאפיינים שכוללים שם, סוג נתונים עוצמה (cardinality). אחרי שמוסיפים סוג סכימה לסכימה של מסד הנתונים, מסמכים של את סוג הסכימה שאפשר ליצור ולהוסיף למסד הנתונים.
מסמכים
ב-AppSearch, יחידת נתונים מיוצגת כמסמך. כל מסמך ב מסד הנתונים של AppSearch מזוהה באופן ייחודי לפי מרחב השמות והמזהה שלו. מרחבי שמות משמשים להפרדת נתונים ממקורות שונים כאשר רק מקור אחד צריך כדי לקבל שאילתות, למשל חשבונות משתמשים.
המסמכים מכילים חותמת זמן של יצירה, אורך חיים (TTL) וציון יכול לשמש לדירוג במהלך האחזור. למסמך מוקצית גם סכימה שמתאר מאפייני נתונים נוספים שהמסמך חייב להיות.
מחלקה של מסמך היא הפשטה של מסמך. הוא מכיל שדות עם הערות שמייצגים את התוכן של מסמך. כברירת מחדל, שם המסמך מחלקה מגדירה את השם של סוג הסכימה.
חיפוש
המסמכים נוספו לאינדקס וניתן לחפש אותם על ידי הזנת שאילתה. מסמך הוא תואמת לתוצאות החיפוש, אם הן מכילות את המונחים שבשאילתה או תואם למפרט חיפוש אחר. התוצאות מסודרות לפי דירוג ואסטרטגיית דירוג. תוצאות חיפוש מיוצגות על ידי דפים שאתה יכול מאוחזרות ברצף.
AppSearch מציעה התאמות אישיות לחיפוש, למשל מסננים, הגדרות גודל הדף וקטעי מידע.
אחסון פלטפורמה לעומת אחסון מקומי
ב-AppSearch יש שני פתרונות אחסון: LocalStorage ו-PlatformStorage. באמצעות LocalStorage, האפליקציה שלך מנהלת אינדקס ספציפי לאפליקציה שנמצא ב- של ספריית הנתונים של האפליקציה. עם PlatformStorage, האפליקציה שלכם יוצרת אינדקס מרכזי ברמת המערכת. גישה לנתונים בתוך האינדקס המרכזי מוגבל לנתונים שהאפליקציה שלך תרמה ולנתונים שאפליקציה אחרת שיתפה איתכם באופן מפורש. גם LocalStorage וגם ל-PlatformStorage יש את אותו ממשק API ואפשר להחליף אותו בהתאם version:
Kotlin
if (BuildCompat.isAtLeastS()) { appSearchSessionFuture.setFuture( PlatformStorage.createSearchSession( PlatformStorage.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 { mAppSearchSessionFuture.setFuture(LocalStorage.createSearchSession( new LocalStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); }
באמצעות PlatformStorage, האפליקציה יכולה לשתף נתונים באופן מאובטח עם אחרים כדי לאפשר להם לחפש גם בנתוני האפליקציה. קריאה-בלבד השיתוף של נתוני האפליקציות מתבצע בלחיצת יד של האישור, כדי לוודא לאפליקציה האחרת יש הרשאה לקרוא את הנתונים. מידע נוסף על ה-API הזה בתיעוד של setSchemaTypeVisibilityForPackage().
בנוסף, נתונים שנוספו לאינדקס יכולים להופיע בפלטפורמות של ממשק המשתמש של המערכת. אפליקציות יכולות לבטל את ההסכמה להצגת חלק מהנתונים שלהן או את כולם במערכת בפלטפורמות שונות של ממשק משתמש. מידע נוסף על ה-API הזה זמין במאמר בנושא setSchemaTypeDisplayedBySystem().
תכונות | LocalStorage (compatible with Android 4.0+) |
PlatformStorage (compatible with Android 12+) |
---|---|---|
Efficient full-text search |
||
Multi-language support |
||
Reduced binary size |
||
Application-to-application data sharing |
||
Capability to display data on System UI surfaces |
||
Unlimited document size and count can be indexed |
||
Faster operations without additional binder latency |
יש שיקולים נוספים שצריך לקחת בחשבון כשבוחרים בין LocalStorage ו-PlatformStorage. מכיוון ש-PlatformStorage אורזת ממשקי API של Jetpack שירות המערכת של AppSearch, ההשפעה של גודל ה-APK מינימלית בהשוואה לשימוש LocalStorage. עם זאת, המשמעות היא שפעולות של AppSearch צוברים זמן האחזור של binder בזמן קריאה לשירות המערכת של AppSearch. עם PlatformStorage, ב-AppSearch יש הגבלה על מספר המסמכים והגודל של המסמכים שזמינים לאפליקציה יכול להוסיף לאינדקס כדי להבטיח אינדקס מרכזי יעיל.
תחילת העבודה עם AppSearch
הדוגמה בקטע הזה ממחישה איך להשתמש בממשקי API של AppSearch כדי לשלב באמצעות אפליקציה היפותטית לניהול פתקים.
כתיבת כיתה למסמכים
השלב הראשון בשילוב עם AppSearch הוא לכתוב מחלקה של מסמכים
שמתארים את הנתונים שרוצים להוסיף למסד הנתונים. סימון כיתה ככיתת מסמך
באמצעות @Document
אנוטציה.תוכלו להשתמש במופעים של כיתת המסמך כדי להכניס מסמכים
לאחזר מסמכים ממסד הנתונים.
הקוד הבא מגדיר מחלקה של מסמך Note עם
נוספו הערות ל-@Document.StringProperty
להוספה לאינדקס של טקסט של אובייקט 'הערה'.
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
באמצעות Note
לבניית כיתות מסמכים. היא מגדירה את מרחב השמות של המסמך user1
לייצג
משתמש שרירותי בדוגמה הזו. לאחר מכן המסמך יתווסף למסד הנתונים.
ו-listener מצורף לעיבוד התוצאה של פעולת ההכנסה.
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);
חיפוש
אפשר לחפש במסמכים שנוספו לאינדקס באמצעות פעולות החיפוש שצוינו
בקטע הזה. הקוד הבא מבצע שאילתות עבור המונח "פרי" מעל
מסד נתונים עבור מסמכים ששייכים למרחב השמות 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
מכונה, שמעניקה גישה לדפים של 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
צריך להיסגר כשאפליקציה כבר לא קוראת למסד נתונים כלשהו
ב-AI. הקוד הבא סוגר את הסשן של 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 כדי להוסיף לאינדקס הערות של משתמש ומאפשרת למשתמשים כדי לחפש בהערות שלהם.
שליחת משוב
אפשר לשתף איתנו משוב ורעיונות דרך המשאבים הבאים:
יש לדווח על באגים כדי שנוכל לתקן אותם.