البحث في التطبيقات

يوفّر AppSearch أداة بحث عالية الأداء على الجهاز لإدارة البيانات المنظَّمة والمخزنة محليًا. يحتوي على واجهات برمجة تطبيقات لفهرسة البيانات واسترداد البيانات باستخدام البحث في النص الكامل. يمكن للتطبيقات استخدام AppSearch لتقديم إمكانات بحث مخصصة داخل التطبيق، ما يسمح للمستخدمين بالبحث عن المحتوى حتى في وضع عدم الاتصال.

رسم بياني يوضّح الفهرسة والبحث ضمن AppSearch

توفّر خدمة AppSearch الميزات التالية:

  • تنفيذ سريع لمساحة تخزين معتمِدة على الأجهزة الجوّالة أولاً مع تقليل استخدام وحدات الإدخال والإخراج
  • الفهرسة وإجراء طلبات البحث بكفاءة عالية في مجموعات البيانات الكبيرة
  • توفُّر عدة لغات، مثل الإنجليزية والإسبانية
  • ترتيب مدى الصلة بالموضوع ونتيجة الاستخدام

توفّر AppSearch وقت استجابة أقل للفهرسة والبحث في مجموعات البيانات الكبيرة مقارنةً بـ SQLite بسبب قلة استخدام وحدات الإدخال والإخراج. تعمل AppSearch على تبسيط طلبات البحث متعددة الأنواع من خلال إتاحة طلبات البحث الفردية بينما تدمج SQLite النتائج من جداول متعددة.

لتوضيح ميزات AppSearch، لنأخذ مثالًا على تطبيق موسيقي يدير الأغاني المفضّلة لدى المستخدمين ويسمح للمستخدمين بالبحث عنها بسهولة. يستمتع المستخدمون بموسيقى من جميع أنحاء العالم باستخدام عناوين أغانٍ بلغات مختلفة، والتي تتيح خدمة AppSearch في الأساس الفهرسة والبحث عنها. عندما يبحث المستخدم عن أغنية باستخدام العنوان أو اسم الفنان، يعمل التطبيق ببساطة على تمرير الطلب إلى AppSearch لاسترداد الأغاني المطابقة بسرعة وفعالية. يعرض التطبيق النتائج، مما يسمح للمستخدمين ببدء تشغيل أغانيهم المفضلة بسرعة.

ضبط إعدادات

لاستخدام AppSearch في تطبيقك، أضِف التبعيات التالية إلى ملف build.gradle في تطبيقك:

رائع

dependencies {
    def appsearch_version = "1.1.0-alpha03"

    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-alpha03"

    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 التالية: قاعدة بيانات AppSearch والمخطط وأنواع المخططات والمستندات
والجلسة والبحث. الشكل 1. رسم تخطيطي لمفاهيم AppSearch: قاعدة بيانات AppSearch، والمخطط، وأنواع المخططات، والمستندات، والجلسة، والبحث.

قاعدة البيانات والجلسة

قاعدة بيانات AppSearch هي مجموعة من المستندات التي تتوافق مع مخطط قاعدة البيانات. تنشئ تطبيقات العملاء قاعدة بيانات من خلال توفير سياق التطبيق واسم قاعدة البيانات. يمكن فتح قواعد البيانات فقط بواسطة التطبيق الذي أنشأها. عند فتح قاعدة بيانات، يتم إرجاع جلسة للتفاعل مع قاعدة البيانات. الجلسة هي نقطة الدخول لاستدعاء واجهات برمجة تطبيقات AppSearch API وتظل مفتوحة حتى يتم إغلاقها بواسطة تطبيق العميل.

أنواع المخططات والمخططات

يمثل المخطط الهيكل التنظيمي للبيانات داخل قاعدة بيانات AppSearch.

يتكون المخطط من أنواع المخططات التي تمثل أنواعًا فريدة من البيانات. تتكون أنواع المخططات من خصائص تحتوي على الاسم ونوع البيانات وعدد القيم الفريدة للسمة. بمجرد إضافة نوع المخطط إلى مخطط قاعدة البيانات، يمكن إنشاء مستندات من هذا النوع من المخطط وإضافتها إلى قاعدة البيانات.

المستندات

في AppSearch، يتم تمثيل وحدة بيانات في شكل مستند. يتم تحديد كل مستند في قاعدة بيانات AppSearch بشكل فريد من خلال مساحة الاسم والرقم التعريفي. يتم استخدام مساحات الاسم لفصل البيانات من مصادر مختلفة عندما يحتاج مصدر واحد فقط إلى الاستعلام، مثل حسابات المستخدمين.

وتحتوي المستندات على الطابع الزمني للإنشاء ومدة البقاء (TTL) ودرجة يمكن استخدامها للترتيب أثناء الاسترداد. يتم أيضًا تعيين نوع مخطط للمستند يصف خصائص بيانات إضافية يجب أن يحتوي عليها المستند.

فئة المستند هي فكرة مجرّدة عن مستند. يحتوي على حقول تعليقات توضيحية تمثل محتويات وثيقة. بشكل افتراضي، يُعيِّن اسم فئة المستند اسم نوع المخطط.

تتم فهرسة المستندات ويمكن البحث عنها من خلال تقديم طلب بحث. تتم مطابقة المستند وتضمينه في نتائج البحث إذا كان يحتوي على عبارات في طلب البحث أو يتطابق مع مواصفات بحث أخرى. يتم ترتيب النتائج بناءً على النتيجة واستراتيجية الترتيب. تمثل نتائج البحث صفحات يمكنك استردادها بشكل تسلسلي.

يقدم AppSearch تخصيصات للبحث، مثل الفلاتر وإعدادات حجم الصفحة والمقتطفات.

مقارنة بين مساحة التخزين على المنصة ومساحة التخزين المحلية

تقدّم AppSearch حلَّين للتخزين، وهما LocalStorage وPlatformStorage. باستخدام LocalStorage، يعمل تطبيقك على إدارة فهرس محدد للتطبيق يوجد في دليل بيانات التطبيق. باستخدام PlatformStorage، يساهم التطبيق في فهرس مركزي على مستوى النظام. يقتصر الوصول إلى البيانات ضمن الفهرس المركزي على البيانات التي ساهم بها تطبيقك والبيانات التي شاركها معك بشكل صريح من خلال تطبيق آخر. يتشارك كل من LocalStorage وPlatformStorage نفس واجهة برمجة التطبيقات ويمكن تبادلهما بناءً على إصدار الجهاز:

لغة 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()
        )
    )
}

جافا

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، يمكن للتطبيق مشاركة البيانات بشكل آمن مع التطبيقات الأخرى للسماح لها بالبحث في بيانات التطبيق أيضًا. وتتم إتاحة مشاركة بيانات التطبيق للقراءة فقط من خلال تأكيد اتصال الشهادة لضمان حصول التطبيق الآخر على الإذن بقراءة البيانات. اطّلع على مزيد من المعلومات حول واجهة برمجة التطبيقات هذه في الوثائق المتعلقة بـ setSchemaTypeVisibilityForPackage().

بالإضافة إلى ذلك، يمكن عرض البيانات التي تمت فهرستها في مساحات عرض واجهة مستخدم النظام. يمكن للتطبيقات إيقاف عرض بعض بياناتها أو جميعها على مساحات عرض واجهة مستخدم النظام. يمكنك قراءة المزيد من المعلومات عن واجهة برمجة التطبيقات هذه في وثائق 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 تلفّ واجهات برمجة تطبيقات Jetpack على خدمة النظام AppSearch، يكون تأثير حجم APK ضئيلاً مقارنةً باستخدام LocalStorage. ومع ذلك، يعني هذا أيضًا أن عمليات AppSearch تتكبّد وقت استجابة إضافيًا عند طلب خدمة نظام AppSearch. باستخدام PlatformStorage، يحد AppSearch من عدد المستندات وحجم المستندات التي يمكن للتطبيق فهرستها لضمان فهرس مركزي فعال.

بدء استخدام AppSearch

يعرض المثال في هذا القسم كيفية استخدام واجهات برمجة تطبيقات 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
)

جافا

@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()
)

جافا

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
)

جافا

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
)

جافا

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
)

جافا

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
)

جافا

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
)

جافا

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)

جافا

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
)

جافا

ListenableFuture<Void> closeFuture = Futures.transform(sessionFuture, session -> {
   session.close();
   return null;
}, mExecutor);

مراجع إضافية

لمزيد من المعلومات عن AppSearch، اطّلِع على المراجع الإضافية التالية:

عيّنات

  • نموذج Android AppSearch (Kotlin)، هو تطبيق لتدوين الملاحظات يستخدم AppSearch لفهرسة ملاحظات المستخدمين ويسمح للمستخدمين بالبحث في ملاحظاتهم.

تقديم الملاحظات

يمكنك مشاركة ملاحظاتك وآرائك معنا من خلال هذه المراجع:

أداة تتبُّع المشاكل

الإبلاغ عن الأخطاء حتى نتمكّن من إصلاحها.