AppSearch هو حلّ بحث عالي الأداء على الجهاز لإدارة البيانات المنظَّمة المُخزَّنة محليًا. وتتضمّن واجهات برمجة تطبيقات للفهرسة واسترجاع البيانات باستخدام البحث النصي الكامل. يمكن للتطبيقات استخدام AppSearch لتقديم إمكانات مخصّصة للبحث داخل التطبيقات، ما يتيح للمستخدمين البحث عن المحتوى حتى في حال عدم الاتصال بالإنترنت.
![رسم بياني يوضّح الفهرسة والبحث ضمن AppSearch](https://developer.android.google.cn/static/images/guide/topics/search/appsearch.png?authuser=3&hl=ar)
يوفّر AppSearch الميزات التالية:
- تنفيذ سريع لمساحة التخزين يمنح الأولوية للأجهزة الجوّالة مع استخدام منخفض لعملية الإدخال/الإخراج
- فهرسة واستعلام عالي الكفاءة على مجموعات البيانات الكبيرة
- التوافق مع لغات متعددة، مثل الإنجليزية والإسبانية
- ترتيب مدى الصلة بالموضوع وتقييم الاستخدام
بسبب انخفاض استخدام الإدخال/الإخراج، يقدّم AppSearch وقت استجابة أقل للفهرسة والبحث في مجموعات البيانات الكبيرة مقارنةً بـ SQLite. يبسط AppSearch طلبات البحث من أنواع متعددة من خلال السماح بطلبات البحث الفردية، في حين تدمج SQLite النتائج من جداول متعددة.
لتوضيح ميزات AppSearch، لنأخذ مثالاً على تطبيق موسیقی يدير الأغاني المفضّلة للمستخدمين ويسمح لهم بالبحث عنها بسهولة. يستمتع المستخدمون بالموسيقى من جميع أنحاء العالم مع عناوين الأغاني بلغات مختلفة، وهو ما يتيح لتطبيق AppSearch فهرستها وإجراء طلبات بحث عنها بشكلٍ أصلي. عندما يبحث المستخدِم عن أغنية حسب العنوان أو اسم الفنّان، يُرسِل التطبيق الطلب إلى AppSearch لاسترداد الأغاني المطابقة بسرعة وكفاءة. يعرض التطبيق النتائج، ما يتيح للمستخدمين بدء تشغيل أغانيهم المفضّلة بسرعة.
ضبط إعدادات الجهاز
لاستخدام AppSearch في تطبيقك، أضِف الملحقات التالية إلىملف build.gradle
في
تطبيقك:
Groovy
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 هي مجموعة من المستندات التي تتوافق مع ملف تعريف قاعدة البيانات. تنشئ تطبيقات العميل قاعدة بيانات من خلال توفير سياق التطبيق واسم قاعدة البيانات. لا يمكن فتح قواعد البيانات إلا من خلال التطبيق الذي أنشأها. عند فتح قاعدة بيانات، يتم عرض جلسة للتفاعل مع قاعدة البيانات. الجلسة هي نقطة الدخول لاستدعاء واجهات برمجة تطبيقات AppSearch وتظل مفتوحة إلى أن يغلقها تطبيق العميل.
المخطّط وأنواع المخطّطات
يمثّل المخطّط البنية التنظيمية للبيانات داخل قاعدة بيانات AppSearch.
يتألّف المخطّط من أنواع مخطّطات تمثّل أنواعًا فريدة من البيانات. تتألف أنواع المخططات من سمات تحتوي على اسم ونوع بيانات ومقدار التكرار. بعد إضافة نوع مخطّط إلى مخطّط قاعدة البيانات، يمكن إنشاء مستندات من نوع المخطّط هذا وإضافتها إلى قاعدة البيانات.
المستندات
في AppSearch، يتم تمثيل وحدة البيانات كمستند. يتم تحديد كل مستند في قاعدة بيانات AppSearch بشكل فريد من خلال مساحة الاسم والمعرّف. تُستخدَم مساحات الأسماء لفصل البيانات من مصادر مختلفة عندما يكون هناك مصدر واحد فقط يحتاج إلى الاستعلام عنه، مثل حسابات المستخدمين.
تحتوي المستندات على طابع زمني للإنشاء ومدة بقاء (TTL) ودرجة يمكن استخدامها للترتيب أثناء الاسترجاع. يتم أيضًا منح المستند نوع schema يصف خصائص البيانات الإضافية التي يجب أن يمتلكها المستند.
فئة المستند هي تمثيل مجرد للمستند. يحتوي على حقول مُشارَك فيها ملاحظات تمثّل محتوى المستند. يحدِّد اسم فئة المستند تلقائيًا اسم نوع المخطّط.
البحث
تتم فهرسة المستندات ويمكن البحث فيها من خلال تقديم طلب بحث. يتم مطابقة المستند وتضمينه في نتائج البحث إذا كان يحتوي على العبارات الواردة في الطلب أو يتطابق مع مواصفات بحث أخرى. يتم ترتيب النتائج استنادًا إلى النتيجة واستراتيجية الترتيب. يتم تمثيل نتائج البحث من خلال صفحات يمكنك استردادها بشكل تسلسلي.
يوفّر AppSearch تخصيصات للبحث، مثل الفلاتر وضبط حجم الصفحة واقتصاص المقتطفات.
مساحة تخزين النظام الأساسي أو مساحة التخزين المحلية أو مساحة تخزين "خدمات Play"
يوفّر AppSearch ثلاثة حلول تخزين: LocalStorage
وPlatformStorage
و
PlayServicesStorage
. باستخدام LocalStorage
، يدير تطبيقك ملفًا فهرسًا
خاصًا بالتطبيق في دليل بيانات التطبيق. باستخدام كل من
PlatformStorage
وPlayServicesStorage
، يساهم تطبيقك في
فهرس مركزي على مستوى النظام. تتم استضافة فهرس PlatformStorage
في خادم النظام، وتتم استضافة فهرس PlayServicesStorage
في مساحة تخزين "خدمة Google Play". يقتصر الوصول إلى البيانات ضمن هذه الفهارس المركزية على البيانات التي ساهم فيها
تطبيقك والبيانات التي تمت مشاركتها معك صراحةً
من خلال تطبيق آخر. تشترك كل خيارات التخزين هذه في واجهة برمجة التطبيقات نفسها ويمكن
تبديلها استنادًا إلى إصدار الجهاز:
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
، يمكن لتطبيقك
مشاركة البيانات بأمان مع التطبيقات الأخرى للسماح لها بالبحث في
بيانات تطبيقك أيضًا. يتم منح إذن مشاركة بيانات التطبيق للقراءة فقط
باستخدام عملية تأكيد الهوية باستخدام شهادة لضمان حصول التطبيق الآخر على
إذن بقراءة البيانات. يمكنك الاطّلاع على مزيد من المعلومات عن واجهة برمجة التطبيقات هذه في مستندات setSchemaTypeVisibilityForPackage()
.
بالإضافة إلى ذلك، باستخدام PlatformStorage
، يمكن عرض البيانات المفهرَسة
على مساحات عرض واجهة مستخدم النظام. يمكن للتطبيقات إيقاف عرض بعض بياناتها أو جميعها
على مساحات عرض واجهة مستخدم النظام. يمكنك الاطّلاع على مزيد من المعلومات عن واجهة برمجة التطبيقات هذه في ملف setSchemaTypeDisplayedBySystem()
.
الميزات | LocalStorage (متوافق مع Android 5.0 والإصدارات الأحدث) |
PlatformStorage (متوافق مع Android 12 والإصدارات الأحدث) |
PlayServicesStorage (متوافق مع Android 5.0 والإصدارات الأحدث) |
---|---|---|---|
البحث الفعّال في النص الكامل | |||
إتاحة المحتوى بعدة لغات | |||
حجم ثنائي مُخفَّض | |||
مشاركة البيانات بين التطبيقات | |||
إمكانية عرض البيانات على مساحات عرض واجهة المستخدم للنظام | |||
يمكن فهرسة عدد غير محدود من المستندات بحجم غير محدود. | |||
عمليات أسرع بدون وقت استجابة إضافي للرابط |
هناك مفاضلات إضافية يجب مراعاتها عند الاختيار بين LocalStorage
وPlatformStorage
. وبما أنّ PlatformStorage
يُغلف واجهات برمجة تطبيقات Jetpack في
خدمة نظام AppSearch، يكون تأثير حجم APK ضئيلًا مقارنةً باستخدام
LocalStorage. ومع ذلك، يعني ذلك أيضًا أنّ عمليات AppSearch تتسبّب في وقت استجابة إضافي
للرابط عند استدعاء خدمة نظام AppSearch. باستخدام PlatformStorage
، يحدّ AppSearch من عدد المستندات وحجمها الذي يمكن للتطبيق
فهرسته لضمان إنشاء فهرس مركزي فعّال. ينطبق على تطبيق PlayServicesStorage
أيضًا
القيود نفسها التي تنطبق على تطبيق PlatformStorage
، ولا يمكن استخدامه إلا على الأجهزة التي تتضمّن
"خدمات Google Play".
بدء استخدام AppSearch
يوضّح المثال في هذا القسم كيفية استخدام واجهات برمجة تطبيقات AppSearch للدمج مع تطبيق افتراضي لحفظ الملاحظات.
كتابة فئة مستند
الخطوة الأولى للدمج مع AppSearch هي كتابة فئة مستند لوصف البيانات التي سيتم إدراجها في قاعدة البيانات. يمكنك وضع علامة على فئة على أنّها فئة مستند
باستخدام التعليق التوضيحي @Document
.يمكنك استخدام نُسخ من فئة المستند لوضع المستندات في قاعدة البيانات و retrieving documents from the database.
تحدِّد التعليمة البرمجية التالية فئة مستند Note باستخدام حقل
@Document.StringProperty
annotated
لفهرسة نص عنصر 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
،
الذي يمثّل الاتصال بقاعدة البيانات ويوفّر واجهات برمجة التطبيقات لعمليات قاعدة البيانات.
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
لتمثيل
مستخدم عشوائي لهذا العيّنة. بعد ذلك، يتم إدخال المستند في قاعدة البيانات
ويتم إرفاق مستمع لمعالجة نتيجة عملية وضع البيانات.
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 (Kotlin): تطبيق لتدوين الملاحظات يستخدم AppSearch لفهرسة ملاحظات المستخدم والسماح للمستخدمين بالبحث في ملاحظاتهم
تقديم تعليقات
يمكنك مشاركة ملاحظاتك وأفكارك معنا من خلال هذه المراجع:
يُرجى الإبلاغ عن الأخطاء لنتمكّن من إصلاحها.