ضبط ميزة التسليم عند الطلب

تتيح لك وحدات الميزات فصل ميزات وموارد معيّنة عن الوحدة الأساسية لتطبيقك وتضمينها في حزمة تطبيقك. من خلال ميزة "إرسال الميزات" من Play، يمكن للمستخدمين مثلاً تنزيل هذه المكونات وتثبيتها لاحقًا عند الطلب بعد تثبيت حزمة APK الأساسية لتطبيقك.

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

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

إذا كنت تريد تقسيم ميزات التطبيق تدريجيًا إلى وحدات مع مرور الوقت، بدون تفعيل خيارات العرض المتقدّمة، مثل العرض عند الطلب، يمكنك بدلاً من ذلك ضبط العرض في وقت التثبيت.

تساعدك هذه الصفحة في إضافة وحدة ميزات إلى مشروع تطبيقك و إعدادها للتسليم عند الطلب. قبل البدء، تأكَّد من استخدام Android Studio 3.5 أو إصدار أحدث وAndroid Gradle Plugin 3.5.0 أو إصدار أحدث.

ضبط وحدة جديدة للعرض عند الطلب

إنّ أسهل طريقة لإنشاء وحدة ميزات جديدة هي باستخدام الإصدار 3.5 من "استوديو Android" أو إصدار أحدث. ولأنّ وحدات الميزات تعتمد بشكلٍ أساسي على وحدة التطبيق الأساسية، لا يمكنك إضافتها إلا إلى مشاريع التطبيقات الحالية.

لإضافة وحدة ميزة إلى مشروع تطبيقك باستخدام "استوديو Android"، يُرجى اتّباع الخطوات التالية:

  1. افتح مشروع تطبيقك في IDE إذا لم يسبق لك إجراء ذلك.
  2. اختَر ملف > جديد > وحدة جديدة من شريط القوائم.
  3. في مربّع الحوار إنشاء وحدة جديدة، اختَر وحدة ميزة ديناميكية وانقر على التالي.
  4. في قسم ضبط وحدتك الجديدة، أكمِل الخطوات التالية:
    1. اختَر وحدة التطبيق الأساسية لمشروع تطبيقك من القائمة المنسدلة.
    2. حدِّد اسم الوحدة. يستخدم IDE هذا الاسم لتحديد الوحدة كمشروع فرعي في Gradle في ملف إعدادات Gradle. عند إنشاء حِزمة تطبيقك، يستخدم Gradle العنصر الأخير من اسم المشروع الفرعي لإدراج سمة <manifest split> في بيان وحدة الميزات.
    3. حدِّد اسم حزمة الوحدة. يقدّم Android Studio تلقائيًا اسم حزمة يجمع بين اسم الحزمة الجذر للوحدة الأساسية واسم الوحدة الذي حدّدته في الخطوة السابقة.
    4. اختَر الحد الأدنى لمستوى واجهة برمجة التطبيقات الذي تريد أن تتوافق معه الوحدة. يجب أن تتطابق هذه القيمة مع قيمة الوحدة الأساسية.
  5. انقر على التالي.
  6. في قسم خيارات تنزيل الوحدات، أكمِل ما يلي:

    1. حدِّد عنوان الوحدة باستخدام ما يصل إلى 50 حرفًا. يستخدم النظام الأساسي هذا العنوان لتعريف الوحدة للمستخدمين عند، على سبيل المثال، تأكيد ما إذا كان المستخدم يريد تنزيل الوحدة. لهذا السبب، يجب أن تتضمّن الوحدة الأساسية لتطبيقك عنوان الوحدة بصفتها مورد سلسلة يمكنك ترجمته. عند إنشاء الوحدة باستخدام "استوديو Android"، يُضيف IDE مورد السلسلة إلى الوحدة الأساسية نيابةً عنك ويُدخل العنصر التالي في بيان وحدة الميزات:

      <dist:module
          ...
          dist:title="@string/feature_title">
      </dist:module>
      
    2. في القائمة المنسدلة ضمن التضمين في وقت التثبيت، اختَر عدم تضمين الوحدة في وقت التثبيت. يُدخل Android Studio العناصر التالية في ملف بيان الوحدة لتعكس اختيارك:

      <dist:module ... >
        <dist:delivery>
            <dist:on-demand/>
        </dist:delivery>
      </dist:module>
      
    3. ضَع علامة في المربّع بجانب دمج إذا كنت تريد أن تكون هذه الوحدة متاحة للأجهزة التي تعمل بنظام التشغيل Android 4.4 (المستوى 20 لواجهة برمجة التطبيقات) والإصدارات الأقدم وأن تكون مضمّنة في حِزم APK متعددة. وهذا يعني أنّه يمكنك تفعيل السلوك عند الطلب لهذه الوحدة وإيقاف الدمج لحذفها من الأجهزة التي لا تتيح تنزيل حِزم APK المجزّأة وتثبيتها. يُدخل Android Studio العناصر التالية في ملف بيان الوحدة لتعكس اختيارك:

      <dist:module ...>
          <dist:fusing dist:include="true | false" />
      </dist:module>
      
  7. انقر على إنهاء.

بعد أن ينتهي "استوديو Android" من إنشاء وحدتك، يمكنك فحص محتوياتها بنفسك من لوحة المشروع (اختَر عرض > نوافذ الأدوات > المشروع من شريط القوائم). يجب أن يكون الرمز البرمجي والموارد والمؤسسة التلقائية مشابهةً لتلك الخاصة بوحدة التطبيق العادية.

بعد ذلك، عليك تنفيذ وظيفة التثبيت عند الطلب باستخدام مكتبة "عرض الميزات في Play".

تضمين مكتبة "عرض الميزات في Play" في مشروعك

قبل البدء، عليك أولاً إضافة "مكتبة إرسال ميزات Play" إلى مشروعك.

طلب وحدة عند الطلب

عندما يحتاج تطبيقك إلى استخدام وحدة ميزة، يمكنه طلب وحدة أثناء ظهوره في المقدّمة من خلال فئة SplitInstallManager. عند إجراء طلب، يجب أن يحدِّد تطبيقك اسم الوحدة كما هو محدَّد في عنصر split في بيان الوحدة المستهدَفة. عند إنشاء وحدة ميزة باستخدام "استوديو Android"، يستخدم نظام الإنشاء اسم الوحدة الذي تقدّمه لإدراج هذه السمة في بيان الوحدة في وقت الترجمة. لمزيد من المعلومات، اطّلِع على ملفات بيان وحدات الميزات.

على سبيل المثال، لنفترض أنّ هناك تطبيقًا يتضمّن وحدة عند الطلب لالتقاط رسائل مصوّرة وإرسالها باستخدام كاميرا الجهاز، وتحدّد هذه الوحدة عند الطلب split="pictureMessages" في بيانها. يستخدم المثال التالي SplitInstallManager لطلب وحدة pictureMessages (بالإضافة إلى وحدة إضافية لبعض الفلاتر الترويجية):

Kotlin

// Creates an instance of SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)

// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

Java

// Creates an instance of SplitInstallManager.
SplitInstallManager splitInstallManager =
    SplitInstallManagerFactory.create(context);

// Creates a request to install a module.
SplitInstallRequest request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build();

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener(sessionId -> { ... })
    .addOnFailureListener(exception -> { ... });

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

ملاحظة: لا بأس بطلب وحدة ميزة تم تثبيتها من قبل على الجهاز. تُعتبر واجهة برمجة التطبيقات الطلب مكتملاً على الفور إذا رصدت أنّه سبق تثبيت الوحده. بالإضافة إلى ذلك، بعد تثبيت إحدى الوحدات، يحافظ Google Play على تحديثها تلقائيًا. وهذا يعني أنّه عند تحميل إصدار جديد من حِزمة تطبيقك، يُحدِّث النظام الأساسي جميع حِزم APK المثبَّتة التي تنتمي إلى تطبيقك. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة إدارة تحديثات التطبيقات.

للوصول إلى رمز الوحدة ومواردها، يجب أن يُفعّل تطبيقك SplitCompat. يُرجى العِلم أنّ حزمة SplitCompat ليست مطلوبة لتطبيقات Android الفورية.

تأجيل تثبيت الوحدات عند الطلب

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

يمكنك تحديد وحدة لتنزيلها لاحقًا باستخدام الإجراء deferredInstall() ، كما هو موضّح أدناه. وعلى عكس SplitInstallManager.startInstall()، لا يحتاج تطبيقك إلى أن يكون في المقدّمة لبدء طلب للقيام بأحد عمليات التثبيت المؤجّل.

Kotlin

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(listOf("promotionalFilters"))

Java

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(Arrays.asList("promotionalFilters"));

يتم تنفيذ طلبات عمليات التثبيت المؤجّلة بأفضل ما يمكن، ولا يمكنك تتبُّع مستوى تقدّمها. لذلك، قبل محاولة الوصول إلى وحدة حددّتها للتثبيت المُؤجَّل، عليك التحقّق من تثبيت الوحدة. إذا كنت بحاجة إلى توفّر الوحدة على الفور، استخدِم بدلاً من ذلك SplitInstallManager.startInstall() لطلبها، كما هو موضّح في القسم السابق.

مراقبة حالة الطلب

لتتمكّن من تعديل شريط التقدم، أو تنشيط نية بعد التثبيت، أو معالجة خطأ في الطلب بشكل سلس، عليك الاستماع إلى تعديلات الحالة من مهمة SplitInstallManager.startInstall() غير المتزامنة. قبل أن تتمكّن من بدء تلقّي آخر المعلومات عن طلب التثبيت، عليك تسجيل ملف استماع والحصول على معرّف الجلسة للطلب، كما هو موضّح أدناه.

Kotlin

// Initializes a variable to later track the session ID for a given request.
var mySessionId = 0

// Creates a listener for request status updates.
val listener = SplitInstallStateUpdatedListener { state ->
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
}

// Registers the listener.
splitInstallManager.registerListener(listener)

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener { sessionId -> mySessionId = sessionId }
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener { exception ->
        // Handle request errors.
    }

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener)

Java

// Initializes a variable to later track the session ID for a given request.
int mySessionId = 0;

// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener = state -> {
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
};

// Registers the listener.
splitInstallManager.registerListener(listener);

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener(sessionId -> { mySessionId = sessionId; })
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener(exception -> {
        // Handle request errors.
    });

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener);

التعامل مع أخطاء الطلبات

يُرجى العِلم أنّ تثبيت وحدات الميزات عند الطلب قد يتعذّر أحيانًا، تمامًا كما أنّ تثبيت التطبيق لا ينجح دائمًا. يمكن أن يرجع تعذُّر التثبيت إلى مشاكل مثل انخفاض مساحة التخزين على الجهاز أو انقطاع الاتصال بالشبكة أو عدم تسجيل دخول المستخدم إلى "متجر Google Play". للحصول على اقتراحات حول كيفية التعامل مع هذه المواقف بطريقة سلسة من وجهة نظر المستخدم، اطّلِع على إرشادات تجربة المستخدم للتسليم عند الطلب.

من حيث الرمز، عليك معالجة حالات تعذُّر تنزيل وحدة أو تثبيتها باستخدام addOnFailureListener()، كما هو موضّح أدناه:

Kotlin

splitInstallManager
    .startInstall(request)
    .addOnFailureListener { exception ->
        when ((exception as SplitInstallException).errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                // Display a message that requests the user to establish a
                // network connection.
            }
            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads()
            ...
        }
    }

fun checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .sessionStates
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // Check for active sessions.
                for (state in task.result) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        }
}

Java

splitInstallManager
    .startInstall(request)
    .addOnFailureListener(exception -> {
        switch (((SplitInstallException) exception).getErrorCode()) {
            case SplitInstallErrorCode.NETWORK_ERROR:
                // Display a message that requests the user to establish a
                // network connection.
                break;
            case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
                checkForActiveDownloads();
            ...
    });

void checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .getSessionStates()
        .addOnCompleteListener( task -> {
            if (task.isSuccessful()) {
                // Check for active sessions.
                for (SplitInstallSessionState state : task.getResult()) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        });
}

يوضّح الجدول التالي حالات الخطأ التي قد يحتاج تطبيقك إلى معالجتها:

رمز الخطأ الوصف الإجراء المقترَح
ACTIVE_SESSIONS_LIMIT_EXCEEDED يتم رفض الطلب لأنّ هناك طلبًا واحدًا على الأقل جارٍ تنزيله حاليًا. تحقّق ممّا إذا كانت هناك أي طلبات لا تزال قيد التنزيل، كما هو موضّح في المثال أعلاه.
MODULE_UNAVAILABLE يتعذّر على Google Play العثور على الوحدة المطلوبة استنادًا إلى الإصدار المثبّت الحالي من التطبيق والجهاز وحساب المستخدم على Google Play. إذا لم يكن لدى المستخدم إذن الوصول إلى الوحدة، أرسِل إليه إشعارًا بذلك.
INVALID_REQUEST تلقّى Google Play الطلب، ولكن الطلب غير صالح. تأكَّد من أنّ المعلومات المضمّنة في الطلب كاملة ودقيقة.
SESSION_NOT_FOUND لم يتم العثور على جلسة لرقم تعريف جلسة معيّن. إذا كنت تحاول تتبُّع حالة طلب من خلال معرّف الجلسة، تأكَّد من أنّ معرّف الجلسة صحيح.
API_NOT_AVAILABLE لا تتوفّر "مكتبة إرسال الميزات" في Play على الجهاز الحالي. وهذا يعني أنّ الجهاز لا يمكنه تنزيل الميزات المثبَّتة وتركيبها عند الطلب. بالنسبة إلى الأجهزة التي تعمل بالإصدار 4.4 من نظام التشغيل Android (المستوى 20 من واجهة برمجة التطبيقات) أو إصدار أقدم، يجب تضمين وحدات الميزات في وقت التثبيت باستخدام dist:fusing سمة البيان. لمزيد من المعلومات، يُرجى الاطّلاع على بيان وحدة الميزات.
NETWORK_ERROR تعذّرت تلبية الطلب بسبب خطأ في الشبكة. اطلب من المستخدم إما إنشاء اتصال بالشبكة أو التبديل إلى شبكة مختلفة.
ACCESS_DENIED يتعذّر على التطبيق تسجيل الطلب بسبب عدم توفّر أذونات كافية. ويحدث ذلك عادةً عندما يكون التطبيق في الخلفية. حاوِل إرسال الطلب عندما يعود التطبيق إلى المقدّمة.
INCOMPATIBLE_WITH_EXISTING_SESSION يحتوي الطلب على وحدة واحدة أو أكثر سبق أن تم طلبها ولكن لم يتم تثبيتها بعد. يمكنك إنشاء طلب جديد لا يتضمّن وحدات سبق أن طلبها تطبيقك، أو الانتظار إلى أن تكتمل عملية تثبيت كل الوحدات المطلوبة حاليًا قبل إعادة محاولة إرسال الطلب.

يُرجى العِلم أنّ طلب وحدة سبق أن تم تثبيتها لا يؤدي إلى ظهور خطأ.

SERVICE_DIED تعذّر على الخدمة المسؤولة عن معالجة الطلب تنفيذها. يُرجى إعادة إرسال الطلب.

يتلقّى جهاز SplitInstallStateUpdatedListener SplitInstallSessionState يتضمّن رمز الخطأ هذا والحالة FAILED ورقم تعريف الجلسة -1.

INSUFFICIENT_STORAGE لا يتوفّر مساحة تخزين كافية على الجهاز لتثبيت وحدة الميزة. أبلِغ المستخدم بأنّه لا تتوفّر لديه مساحة تخزين كافية لتثبيت هذه الميزة.
SPLITCOMPAT_VERIFICATION_ERROR, SPLITCOMPAT_EMULATION_ERROR, SPLITCOMPAT_COPY_ERROR تعذّر على SplitCompat تحميل وحدة الميزات. من المفترض أن يتم حلّ هذه الأخطاء تلقائيًا بعد إعادة تشغيل التطبيق التالي.
PLAY_STORE_NOT_FOUND لم يتم تثبيت تطبيق "متجر Play" على الجهاز. أطلِع المستخدم على أنّه يجب تثبيت تطبيق "متجر Play" لتنزيل هذه الميزة.
APP_NOT_OWNED لم يتم تثبيت التطبيق من خلال Google Play ولا يمكن تنزيل الميزة. لا يمكن أن يحدث هذا الخطأ إلا لعمليات التثبيت المؤجّلة. إذا أردت أن يحصل المستخدم على التطبيق من Google Play، استخدِم startInstall() الذي يمكنه الحصول على تأكيد المستخدم اللازم.
INTERNAL_ERROR حدث خطأ داخلي في "متجر Play". يُرجى إعادة إرسال الطلب.

إذا طلب أحد المستخدمين تنزيل وحدة نمطية عند الطلب وحدث خطأ، ننصح بعرض مربّع حوار يقدّم خيارَين للمستخدم: إعادة المحاولة (لإعادة محاولة الطلب) وإلغاء (لإلغاء الطلب). للحصول على مساعدة إضافية، يجب أيضًا توفير رابط مساعدة يؤدي إلى توجيه المستخدمين إلى مركز مساعدة Google Play.

التعامل مع تعديلات الحالة

بعد تسجيل مستمع وتسجيل معرّف الجلسة لطلبك، استخدِم StateUpdatedListener.onStateUpdate() للتعامل مع تغييرات الحالة، كما هو موضّح أدناه.

Kotlin

override fun onStateUpdate(state : SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) {
       // Retry the request.
       return
    }
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.DOWNLOADING -> {
              val totalBytes = state.totalBytesToDownload()
              val progress = state.bytesDownloaded()
              // Update progress bar.
            }
            SplitInstallSessionStatus.INSTALLED -> {

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return;
    }
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
              int totalBytes = state.totalBytesToDownload();
              int progress = state.bytesDownloaded();
              // Update progress bar.
              break;

            case SplitInstallSessionStatus.INSTALLED:

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
        }
    }
}

في الجدول أدناه، يتم توضيح الحالات المحتمَلة لطلب التثبيت.

حالة الطلب الوصف الإجراء المقترَح
في انتظار المراجعة تم قبول الطلب ومن المفترض أن يبدأ التنزيل بعد قليل. يمكنك بدء مكونات واجهة المستخدم، مثل شريط التقدم، لإطلاع المستخدم على معلومات حول عملية التنزيل.
REQUIRES_USER_CONFIRMATION يتطلب التنزيل تأكيد المستخدم. تحدث هذه الحالة غالبًا إذا لم يتم تثبيت التطبيق من خلال Google Play. اطلب من المستخدم تأكيد تنزيل الميزة من خلال Google Play. لمزيد من المعلومات، انتقِل إلى القسم الذي يتناول كيفية الحصول على تأكيد من المستخدم.
التنزيل جارٍ تنزيل الفيديو. إذا كنت تقدّم شريط تقدّم للتنزيل، استخدِم SplitInstallSessionState.bytesDownloaded() وSplitInstallSessionState.totalBytesToDownload() لتعديل واجهة المستخدم (اطّلِع على نموذج الرمز البرمجي أعلاه هذا الجدول).
تم التنزيل نزَّل الجهاز الوحدة ولكن لم يبدأ التثبيت بعد. يجب أن تفعِّل التطبيقات حزمة SplitCompat للوصول إلى الوحدات التي تم تنزيلها وتجنُّب ظهور هذه الحالة. هذا الإجراء مطلوب للوصول إلى رمز وحدة الميزات و مواردها.
جارٍ التثبيت يُثبِّت الجهاز الوحدة حاليًا. عدِّل شريط التقدّم. وعادةً ما تكون هذه الحالة قصيرة.
تم التثبيت تم تثبيت الوحدة على الجهاز. رمز الوصول والمرجع في الوحدة لمواصلة رحلة المستخدِم

إذا كانت الوحدة مخصّصة لتطبيق Android فوري يعمل على الإصدار 8.0 من نظام التشغيل Android (المستوى 26 لواجهة برمجة التطبيقات) أو إصدار أحدث، عليك استخدام splitInstallHelper لتحديث مكوّنات التطبيق باستخدام الوحدة الجديدة.

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

الحصول على تأكيد من المستخدم

في بعض الحالات، قد يطلب Google Play تأكيد المستخدم قبل تنفيذ طلب تنزيل. على سبيل المثال، إذا لم يتم تثبيت تطبيقك من خلال Google Play أو إذا كنت تحاول تنزيل ملف كبير باستخدام بيانات الجوّال. في هذه الحالات، تُبلغ حالة الطلب عن REQUIRES_USER_CONFIRMATION، ويجب أن يحصل تطبيقك على تأكيد المستخدم قبل أن يتمكّن الجهاز من تنزيل الوحدات في الطلب وتثبيتها. للحصول على تأكيد، يجب أن يطلب تطبيقك من المستخدم تأكيد الموافقة على النحو التالي:

Kotlin

override fun onSessionStateUpdate(state: SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher)
    }
    ...
 }

Java

@Override void onSessionStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher);
    }
    ...
 }

يمكنك تسجيل مشغّل نتائج نشاط باستخدام العقدة المضمّنة ActivityResultContracts.StartIntentSenderForResult. اطّلِع على واجهات برمجة التطبيقات لنتائج الأنشطة.

يتم تعديل حالة الطلب استنادًا إلى ردّ المستخدم:

  • إذا وافق المستخدم على التأكيد، تتغير حالة الطلب إلى PENDING ويستمر التنزيل.
  • إذا رفض المستخدم التأكيد، تتغيّر حالة الطلب إلى CANCELED.
  • إذا لم يحدّد المستخدم خيارًا قبل إغلاق مربّع الحوار، تظل حالة الطلب على القيمة REQUIRES_USER_CONFIRMATION. يمكن لتطبيقك مطالبة المستخدم مرة أخرى بإكمال الطلب.

لتلقّي مكالمة تلقائية تتضمّن ردّ المستخدم، يمكنك إلغاء ActivityResultCallback كما هو موضّح أدناه.

Kotlin

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult -> {
        // Handle the user's decision. For example, if the user selects "Cancel",
        // you may want to disable certain functionality that depends on the module.
    }
}

Java

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // Handle the user's decision. For example, if the user selects "Cancel",
            // you may want to disable certain functionality that depends on the module.
        }
    });

إلغاء طلب تثبيت

إذا كان تطبيقك بحاجة إلى إلغاء طلب قبل تثبيته، يمكنه استدعاء cancelInstall() باستخدام معرّف جلسة الطلب، كما هو موضّح أدناه.

Kotlin

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId)

Java

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId);

الوصول إلى الوحدات

للوصول إلى الرموز البرمجية والموارد من وحدة تم تنزيلها بعد تنزيلها، يجب أن يفعّل تطبيقك مكتبة SplitCompat لتطبيقك ولكل نشاط في وحدات الميزات التي تنزيلها.

يُرجى العِلم أنّ المنصة تواجه قيودًا التالية على الوصول إلى محتوى الوحدة لبعض الوقت (أيام في بعض الحالات) بعد تنزيل الوحدة:

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

تفعيل SplitCompat

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

بعد تفعيل حزمة SplitCompat لتطبيقك، عليك أيضًا تفعيل حزمة SplitCompat لكل نشاط في وحدات الميزات التي تريد أن يصل إليها تطبيقك.

تعريف فئة SplitCompatApplication في البيان

إنّ أبسط طريقة لتفعيل SplitCompat هي تحديد SplitCompatApplication كالفئة الفرعية Application في ملف بيان تطبيقك، كما هو موضّح أدناه:

<application
    ...
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>

بعد تثبيت التطبيق على جهاز، يمكنك الوصول إلى الرموز البرمجية والموارد من وحدات الميزات التي تم تنزيلها تلقائيًا.

استدعاء SplitCompat في وقت التشغيل

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

إذا كانت لديك فئة Application مخصّصة، اجعلها بدلاً من ذلك تمتد من SplitCompatApplication لتفعيل SplitCompat لتطبيقك، كما هو موضّح أدناه:

Kotlin

class MyApplication : SplitCompatApplication() {
    ...
}

Java

public class MyApplication extends SplitCompatApplication {
    ...
}

تتجاهل SplitCompatApplication ببساطة ContextWrapper.attachBaseContext() لتضمين SplitCompat.install(Context applicationContext). إذا كنت لا تريد أن تشمل فئة Application الصف SplitCompatApplication، يمكنك إلغاء طريقةattachBaseContext() يدويًا على النحو التالي:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this);
}

إذا كانت الوحدة عند الطلب متوافقة مع كلٍّ من التطبيقات الفورية والتطبيقات المثبَّتة، يمكنك استدعاء SplitCompat بشكل مشروط على النحو التالي:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this)
    }
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this);
    }
}

تفعيل SplitCompat لأنشطة الوحدات

بعد تفعيل حزمة SplitCompat لتطبيقك الأساسي، عليك تفعيل حزمة SplitCompat لكل نشاط ينزّله تطبيقك في وحدة ميزة. لإجراء ذلك، استخدِم طريقة SplitCompat.installActivity() على النحو التالي:

Kotlin

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this);
}

الوصول إلى المكوّنات المحدّدة في وحدات الميزات

بدء نشاط محدّد في وحدة ميزة

يمكنك إطلاق الأنشطة المحدّدة في وحدات الميزات باستخدام startActivity() بعد تفعيل SplitCompat.

Kotlin

startActivity(Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...))

Java

startActivity(new Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...));

المَعلمة الأولى التي يتم تمريرها إلى setClassName هي اسم حِزمة التطبيق، والملفَّق المَعلمة الثانية هي اسم الفئة الكامل للنشاط.

عندما يكون لديك نشاط في وحدة ميزة نزّلته عند الطلب، عليك تفعيل SplitCompat في النشاط.

بدء خدمة محدّدة في وحدة ميزة

يمكنك تشغيل الخدمات المحدّدة في وحدات الميزات باستخدام startService() بعد تفعيل SplitCompat.

Kotlin

startService(Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...))

Java

startService(new Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...));

تصدير مكوّن محدّد في وحدة ميزة

يجب عدم تضمين مكوّنات Android التي تم تصديرها داخل وحدات اختيارية.

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

ولا يشكّل ذلك مشكلة في المكوّنات الداخلية، إذ لا يمكن الوصول إليها إلا من خلال التطبيق، ما يتيح للتطبيق التحقّق من تثبيت الوحدة قبل الوصول إلى المكوّن.

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

الوصول إلى الرموز البرمجية والموارد من الوحدات المثبَّتة

في حال تفعيل SplitCompat لسياق التطبيق الأساسي والأنشطة في وحدة الميزات، يمكنك استخدام الرمز البرمجي والموارد من وحدة الميزات كما لو كانت جزءًا من حزمة APK الأساسية، بعد تثبيت الوحدة الاختيارية.

الوصول إلى رمز من وحدة مختلفة

الوصول إلى الرمز الأساسي من وحدة

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

الوصول إلى رمز الوحدة من وحدة أخرى

لا يمكن الوصول إلى كائن أو فئة داخل وحدة بشكلٍ ثابت من وحدة أخرى مباشرةً، ولكن يمكن الوصول إليهما بشكلٍ غير مباشر باستخدام ميزة "الاستكشاف".

يجب الانتباه إلى عدد المرات التي يحدث فيها ذلك، بسبب تكاليف الأداء للتأمل. بالنسبة إلى حالات الاستخدام المعقّدة، استخدِم إطارات عمل حقن التبعية، مثل Dagger 2 لضمان إجراء مكالمة واحدة لعكس القيمة لكل مدة تشغيل التطبيق.

لتبسيط التفاعلات مع العنصر بعد إنشائه، يُنصح بتحديد واجهة في الوحدة الأساسية وتنفيذها في وحدة الميزات. على سبيل المثال:

Kotlin

// In the base module
interface MyInterface {
  fun hello(): String
}

// In the feature module
object MyInterfaceImpl : MyInterface {
  override fun hello() = "Hello"
}

// In the base module, where we want to access the feature module code
val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl")
    .kotlin.objectInstance as MyInterface).hello();

Java

// In the base module
public interface MyInterface {
  String hello();
}

// In the feature module
public class MyInterfaceImpl implements MyInterface {
  @Override
  public String hello() {
    return "Hello";
  }
}

// In the base module, where we want to access the feature module code
String stringFromModule =
   ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();

الوصول إلى المراجع ومواد العرض من وحدة مختلفة

بعد تثبيت إحدى الوحدات، يمكنك الوصول إلى الموارد ومواد العرض ضمن الوحدة بالطريقة العادية، مع مراعاة أمرَين:

  • إذا كنت تصل إلى مورد من وحدة مختلفة، لن تتمكّن الوحده من الوصول إلى معرّف المورد، إلا أنّه سيظل بإمكانك الوصول إلى المورد بالاسم. يُرجى العلم أنّ الحزمة التي سيتم استخدامها للإشارة إلى المورد هي حزمة الوحدة التي تم فيها تعريف المورد.
  • إذا أردت الوصول إلى مواد العرض أو الموارد المتوفّرة في ملف برمجي مثبَّت حديثًا من ملف برمجي مختلف مثبَّت في تطبيقك، عليك إجراء ذلك باستخدام سياق التطبيق. لن يتم تعديل سياق المكوّن الذي يحاول الوصول إلى الموارد بعد. بدلاً من ذلك، يمكنك إعادة إنشاء هذا المكوّن (على سبيل المثال، من خلال استدعاء Activity.recreate()‎) أو إعادة تثبيت SplitCompat عليه بعد تثبيت ملف ميزة.

تحميل رمز أصلي في تطبيق باستخدام ميزة "العرض عند الطلب"

ننصحك باستخدام ReLinker لتحميل كل مكتباتك الأصلية عند استخدام ميزة "العرض عند الطلب" لمكوّنات الميزات. يحلّ ReLinker مشكلة في تحميل المكتبات المجمّعة من رموز برمجية أصلية بعد تثبيت وحدة ميزة. يمكنك الاطّلاع على مزيد من المعلومات عن ReLinker في مقالة نصائح حول واجهة برمجة التطبيقات JNI في Android.

تحميل رمز أصلي من وحدة اختيارية

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

إذا كنت تستخدم System.loadLibrary() لتحميل الرمز الأصلي وكانت مكتبتك الأصلية تعتمد على مكتبة أخرى في الوحدة، عليك أولاً تحميل تلك المكتبة الأخرى يدويًا. إذا كنت تستخدم ReLinker، تكون العملية المكافئة هي Relinker.recursively().loadLibrary().

إذا كنت تستخدم dlopen() في الرمز الأصلي لتحميل مكتبة محدّدة في وحدة اختيارية، لن تعمل مع مسارات المكتبات النسبية. أفضل حلّ هو استرداد المسار المطلق للمكتبة من رمز Java من خلال ClassLoader.findLibrary() ثم استخدامه في طلب dlopen(). يمكنك إجراء ذلك قبل إدخال الرمز البرمجي الأصلي أو استخدام طلب JNI من الرمز البرمجي الأصلي إلى Java.

الوصول إلى تطبيقات Android الفورية المثبَّتة

بعد أن تُبلغ وحدة تطبيق Android الفوري عن أنّها INSTALLED، يمكنك الوصول إلى الرمز البرمجي والموارد باستخدام سياق التطبيق الذي تم تعديله. إنّ السياق الذي ينشئه تطبيقك قبل تثبيت وحدة (مثل سياق سبق تخزينه في متغيّر) لا يحتوي على محتوى الوحدة الجديدة. أمّا السياق الجديد، فيؤدّي إلى ذلك، ويمكن الحصول عليه مثلاً باستخدام createPackageContext.

Kotlin

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                val newContext = context.createPackageContext(context.packageName, 0)
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                val am = newContext.assets
            }
        }
    }
}

Java

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                AssetManager am = newContext.getAssets();
        }
    }
}

تطبيقات Android الفورية على الإصدار 8.0 من نظام التشغيل Android والإصدارات الأحدث

عند طلب وحدة عند الطلب لتطبيق Android فوري على الإصدار 8.0 من Android (المستوى 26 لواجهة برمجة التطبيقات) والإصدارات الأحدث، بعد أن يُبلغ طلب التثبيت عن حالة INSTALLED، عليك تحديث التطبيق باستخدام سياق الوحدة الجديدة من خلال طلب SplitInstallHelper.updateAppInfo(Context context). بخلاف ذلك، لن يكون التطبيق على دراية بعد برمز الوحدات ومواردها. بعد تعديل البيانات الوصفية للتطبيق، عليك تحميل محتويات الوحدات أثناء حدث الخيط الرئيسي التالي من خلال استدعاء Handler جديد، كما هو موضّح أدناه:

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                // You need to perform the following only for Android Instant Apps
                // running on Android 8.0 (API level 26) and higher.
                if (BuildCompat.isAtLeastO()) {
                    // Updates the app’s context with the code and resources of the
                    // installed module.
                    SplitInstallHelper.updateAppInfo(context)
                    Handler().post {
                        // Loads contents from the module using AssetManager
                        val am = context.assets
                        ...
                    }
                }
            }
        }
    }
}

Java

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
            // You need to perform the following only for Android Instant Apps
            // running on Android 8.0 (API level 26) and higher.
            if (BuildCompat.isAtLeastO()) {
                // Updates the app’s context with the code and resources of the
                // installed module.
                SplitInstallHelper.updateAppInfo(context);
                new Handler().post(new Runnable() {
                    @Override public void run() {
                        // Loads contents from the module using AssetManager
                        AssetManager am = context.getAssets();
                        ...
                    }
                });
            }
        }
    }
}

تحميل مكتبات C/C++

إذا كنت تريد تحميل مكتبات C/C++ من وحدة سبق أن حمّلها الجهاز في تطبيق فوري، استخدِم SplitInstallHelper.loadLibrary(Context context, String libName)، كما هو موضّح أدناه:

Kotlin

override fun onStateUpdate(state: SplitInstallSessionState) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.INSTALLED -> {
                // Updates the app’s context as soon as a module is installed.
                val newContext = context.createPackageContext(context.packageName, 0)
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”)
                ...
            }
        }
    }
}

Java

public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.INSTALLED:
                // Updates the app’s context as soon as a module is installed.
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”);
                ...
        }
    }
}

القيود المعروفة

  • لا يمكن استخدام WebView في Android في نشاط يصل إلى موارد أو مواد عرض من وحدة اختيارية. ويعود السبب في ذلك إلى عدم التوافق بين WebView وSplitCompat على المستوى 28 من واجهة برمجة التطبيقات لنظام التشغيل Android والإصدارات الأقدم.
  • لا يمكنك تخزين عناصر ApplicationInfo في Android أو محتوياتها أو العناصر التي تحتوي عليها في ذاكرة التخزين المؤقت داخل تطبيقك. يجب جلب هذه العناصر دائمًا حسب الحاجة من سياق التطبيق. وقد يؤدي تخزين هذه العناصر مؤقتًا إلى تعطُّل التطبيق عند تثبيت وحدة ميزة.

إدارة الوحدات المثبَّتة

للتحقّق من وحدات الميزات المثبَّتة حاليًا على الجهاز، يمكنك استدعاء SplitInstallManager.getInstalledModules()، الذي يعرض Set<String> لأسماء الوحدات المثبَّتة، كما هو موضّح أدناه.

Kotlin

val installedModules: Set<String> = splitInstallManager.installedModules

Java

Set<String> installedModules = splitInstallManager.getInstalledModules();

إلغاء تثبيت الوحدات

يمكنك طلب إلغاء تثبيت الوحدات على الجهاز من خلال استدعاء SplitInstallManager.deferredUninstall(List<String> moduleNames)، كما هو موضّح أدناه.

Kotlin

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))

Java

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));

لا تتم عمليات إلغاء تثبيت الوحدات على الفور. وهذا يعني أنّه يزيل الجهاز هذه التطبيقات في الخلفية حسب الحاجة لتوفير مساحة تخزين. يمكنك التأكّد من أنّ الجهاز قد حذف وحدة عن طريق استدعاء SplitInstallManager.getInstalledModules() وفحص النتيجة، كما هو موضّح في القسم السابق.

تنزيل مراجع لغوية إضافية

باستخدام حِزم التطبيقات، لا تنزِّل الأجهزة سوى الرموز البرمجية والموارد التي تحتاج إليها لتشغيل تطبيقك. وبالنسبة إلى موارد اللغة، لا ينزِّل جهاز المستخدم سوى موارد اللغة في تطبيقك التي تتطابق مع لغة واحدة أو أكثر تم اختيارها حاليًا في إعدادات الجهاز.

إذا كنت تريد أن يحصل تطبيقك على إمكانية الوصول إلى موارد لغوية إضافية، مثلاً لتطبيق أداة اختيار اللغة داخل التطبيق، يمكنك استخدام مكتبة "إرسال الميزات" في Play لتنزيلها عند الطلب. وتتشابه هذه العملية مع عملية تنزيل وحدة ميزة، كما هو موضّح أدناه.

Kotlin

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply()
...

// Creates a request to download and install additional language resources.
val request = SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build()

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request)

Java

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply();
...

// Creates a request to download and install additional language resources.
SplitInstallRequest request =
    SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build();

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request);

تتم معالجة الطلب كما لو كان طلبًا لوحدة ميزة. وهذا يعني أنّه يمكنك مراقبة حالة الطلب كالمعتاد.

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

Kotlin

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

الوصول إلى المراجع اللغوية التي تم تنزيلها

للوصول إلى موارد اللغة التي تم تنزيلها، يجب أن يشغّل تطبيقك SplitCompat.installActivity() ضمن attachBaseContext() لكل نشاط يتطلب الوصول إلى هذه الموارد، كما هو موضّح أدناه.

Kotlin

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(base)
  SplitCompat.installActivity(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  SplitCompat.installActivity(this);
}

لكل نشاط تريد استخدام موارد لغوية نزّلها تطبيقك، عدِّل السياق الأساسي واضبط لغة جديدة من خلال Configuration:

Kotlin

override fun attachBaseContext(base: Context) {
  val configuration = Configuration()
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
  val context = base.createConfigurationContext(configuration)
  super.attachBaseContext(context)
  SplitCompat.install(this)
}

Java

@Override
protected void attachBaseContext(Context base) {
  Configuration configuration = new Configuration();
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
  Context context = base.createConfigurationContext(configuration);
  super.attachBaseContext(context);
  SplitCompat.install(this);
}

لكي تصبح هذه التغييرات سارية، عليك إعادة إنشاء نشاطك بعد تثبيت اللغة الجديدة واستعدادها للاستخدام. يمكنك استخدام الطريقة Activity#recreate().

Kotlin

when (state.status()) {
  SplitInstallSessionStatus.INSTALLED -> {
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate()
  }
  ...
}

Java

switch (state.status()) {
  case SplitInstallSessionStatus.INSTALLED:
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate();
  ...
}

إلغاء تثبيت موارد اللغات الإضافية

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

Kotlin

val installedLanguages: Set<String> = splitInstallManager.installedLanguages

Java

Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();

يمكنك بعد ذلك تحديد اللغات التي تريد إلغاء تثبيتها باستخدام الطريقة deferredLanguageUninstall()، كما هو موضّح أدناه.

Kotlin

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Java

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

اختبار عمليات تثبيت الوحدات على الجهاز

تتيح لك "مكتبة عرض الميزات" في Play اختبار قدرة تطبيقك على تنفيذ ما يلي محليًا بدون الاتصال بـ "متجر Play":

توضّح هذه الصفحة كيفية نشر حِزم APK المجزّأة لتطبيقك على جهازك التجريبي كي تتمكّن ميزة "عرض الميزات في Play" من استخدام هذه الحِزم تلقائيًا لمحاكاة طلب الوحدات وتنزيلها وتثبيتها من "متجر Play".

على الرغم من أنّه ليس عليك إجراء أي تغييرات على منطق تطبيقك، عليك استيفاء المتطلبات التالية:

  • نزِّل أحدث إصدار من bundletool وثبِّته. تحتاج إلى bundletool لإنشاء مجموعة جديدة من حِزم APK القابلة للتثبيت من حِزمة تطبيقك.

إنشاء مجموعة من حِزم APK

أنشئ حِزم APK المجزّأة لتطبيقك، إذا لم يسبق لك إجراء ذلك، على النحو التالي:

  1. أنشئ حِزمة تطبيق لتطبيقك باستخدام إحدى الطريقتَين التاليتَين:
  2. استخدِم bundletool لإنشاء مجموعة منملفّات APK لجميع ملفّات ملفّات APK الخاصة بالجهاز باستخدام الأمر التالي:

    bundletool build-apks --local-testing
      --bundle my_app.aab
      --output my_app.apks
    

تتضمّن العلامة --local-testing بيانات وصفية في ملفات بيان حِزم APK التي تُعلِم مكتبة "عرض الميزات في Play" باستخدام حِزم APK المجزّأة على الجهاز لاختبار تثبيت وحدات الميزات بدون الاتصال بمتجر Play.

نشر تطبيقك على الجهاز

بعد إنشاء مجموعة من حِزم APK باستخدام العلامة --local-testing، استخدِم bundletool لتثبيت الإصدار الأساسي من تطبيقك ونقل حِزم APK إضافية إلى مساحة التخزين المتوفّرة على جهازك. يمكنك تنفيذ كلا الإجراءَين باستخدام الأمر التالي:

bundletool install-apks --apks my_app.apks

الآن، عند تشغيل تطبيقك وإكمال مسار المستخدم لتنزيل ملف APK الخاص بأحد bundletool وحدات الميزات وتثبيته، تستخدم مكتبة Play Feature Delivery Library حِزم APK التي bundletool تم نقلها إلى مساحة التخزين التلقائية على الجهاز.

محاكاة خطأ في الشبكة

لمحاكاة عمليات تثبيت الوحدات من "متجر Play"، تستخدم مكتبة "عرض الميزات في Play" SplitInstallManagerبديلاً يُسمى FakeSplitInstallManager، لطلب الوحدة. عند استخدام bundletool مع العلامة --local-testing لإنشاء مجموعة من حِزم APK ونشرها على جهاز الاختبار، يتم تضمين بيانات وصفية تُوجّه مكتبة Play Feature Delivery Library إلى تبديل طلبات بيانات واجهة برمجة التطبيقات في تطبيقك تلقائيًا لتشغيل FakeSplitInstallManager بدلاً من SplitInstallManager.

يتضمّن FakeSplitInstallManager علامة منطقية يمكنك تفعيلها ل simulating a network error في المرة التالية التي يطلب فيها تطبيقك تثبيت وحدة. للوصول إلى FakeSplitInstallManager في اختباراتك، يمكنك الحصول على مثيل منه باستخدام FakeSplitInstallManagerFactory، كما هو موضّح أدناه:

Kotlin

// Creates an instance of FakeSplitInstallManager with the app's context.
val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context)
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true)

Java

// Creates an instance of FakeSplitInstallManager with the app's context.
FakeSplitInstallManager fakeSplitInstallManager =
    FakeSplitInstallManagerFactory.create(context);
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true);