يشترط Google Play ألا يتجاوز حجم حزمة APK المضغوطة التي ينزّلها المستخدمون 100 ميغابايت. بالنسبة إلى معظم التطبيقات، هذه المساحة كافية لجميع الرموز البرمجية ومواد العرض الخاصة بالتطبيق. ومع ذلك، تحتاج بعض التطبيقات إلى مساحة أكبر للرسومات عالية الدقة أو ملفات الوسائط أو غيرها من مواد العرض الكبيرة. في السابق، إذا تجاوز حجم التنزيل المضغوط لتطبيقك 100 ميغابايت، كان عليك استضافة الموارد الإضافية وتنزيلها بنفسك عندما يفتح المستخدم التطبيق، وكانت استضافة الملفات الإضافية وتقديمها مكلفة، وكانت تجربة المستخدم غالبًا أقل من مثالية. لتسهيل هذه العملية عليك وتحسين تجربة المستخدمين، يتيح لك Google Play إرفاق ملفَي بيانات توسيع كبيرَين يكملان حزمة APK.
يستضيف Google Play ملفات البيانات الموسّعة لتطبيقك ويعرضها على الجهاز بدون أي تكلفة. يتم حفظ ملفات البيانات الموسّعة في مساحة التخزين المشتركة على الجهاز (بطاقة SD أو قسم قابل للتركيب على USB، ويُعرف أيضًا باسم مساحة التخزين "الخارجية") حيث يمكن لتطبيقك الوصول إليها. على معظم الأجهزة، ينزِّل Google Play ملفات البيانات الموسّعة في الوقت نفسه الذي ينزِّل فيه حزمة APK، وبالتالي يتوفّر لتطبيقك كل ما يحتاج إليه عندما يفتحه المستخدم للمرة الأولى. ومع ذلك، في بعض الحالات، يجب أن ينزّل تطبيقك الملفات من Google Play عند بدء تشغيله.
إذا كنت تريد تجنُّب استخدام ملفات البيانات الموسّعة وكان حجم التنزيل المضغوط لتطبيقك أكبر من 100 ميغابايت، عليك بدلاً من ذلك تحميل تطبيقك باستخدام حِزم تطبيقات Android التي تسمح بحجم تنزيل مضغوط يصل إلى 500 ميغابايت. بالإضافة إلى ذلك، بما أنّ استخدام حِزم التطبيقات يؤجّل إنشاء حِزم APK وتوقيعها إلى Google Play، ينزّل المستخدمون حِزم APK محسَّنة تحتوي فقط على الرموز والموارد التي يحتاجون إليها لتشغيل تطبيقك. ولن تحتاج إلى إنشاء حِزم APK أو ملفات بيانات موسّعة متعددة وتوقيعها وإدارتها، وسيتمكّن المستخدمون من تنزيل حِزم أصغر حجمًا وأكثر تحسينًا.
نظرة عامة
في كل مرة تحمّل فيها حِزمة APK باستخدام Google Play Console، يتوفّر لك خيار إضافة ملف أو ملفَين من ملفات البيانات الموسّعة إلى حِزمة APK. يمكن أن يصل حجم كل ملف إلى 2 غيغابايت، ويمكن أن يكون بأي تنسيق تختاره، ولكن ننصحك باستخدام ملف مضغوط للحفاظ على معدل نقل البيانات أثناء التنزيل. من الناحية النظرية، يؤدي كل ملف بيانات موسّعة دورًا مختلفًا:
- ملف البيانات الموسّعة الرئيسي هو ملف البيانات الموسّعة الأساسي الذي يحتوي على الموارد الإضافية التي يتطلّبها تطبيقك.
- ملف البيانات الموسّعة للتصحيح اختياري ومخصّص لإجراء تعديلات بسيطة على ملف البيانات الموسّعة الرئيسي.
على الرغم من إمكانية استخدام ملفَي البيانات الموسّعة بأي طريقة تريدها، ننصحك بأن يوفّر ملف البيانات الموسّعة الرئيسي مواد العرض الأساسية وألا يتم تعديله إلا نادرًا، وأن يكون ملف البيانات الموسّعة الخاص بالحزمة أصغر حجمًا وأن يكون بمثابة "حزمة تحديث"، ويتم تعديله مع كل إصدار رئيسي أو حسب الحاجة.
ومع ذلك، حتى إذا كان تحديث تطبيقك يتطلّب فقط ملف البيانات الموسّعة الاختياري، عليك تحميل حزمة APK جديدة تتضمّن versionCode معدَّلاً في ملف البيان. (لا تتيح لك Play Console تحميل ملف بيانات موسّعة إلى حزمة APK حالية).
ملاحظة: ملف البيانات الموسّعة الاختياري هو نفسه ملف البيانات الموسّعة الأساسي من الناحية الدلالية، ويمكنك استخدام كل ملف بالطريقة التي تريدها.
تنسيق اسم الملف
يمكن أن يكون كل ملف بيانات موسّعة تحمّله بأي تنسيق تختاره (ZIP أو PDF أو MP4 أو غير ذلك). يمكنك أيضًا استخدام أداة JOBB لتغليف وتشفير مجموعة من ملفات الموارد والحِزم اللاحقة لهذه المجموعة. بغض النظر عن نوع الملف، يعتبر Google Play الملفات كائنات ثنائية مبهمة ويعيد تسميتها باستخدام المخطط التالي:
[main|patch].<expansion-version>.<package-name>.obb
يتضمّن هذا المخطط ثلاثة عناصر:
mainأوpatch- تحدّد هذه السمة ما إذا كان الملف هو ملف البيانات الموسّعة الرئيسي أو ملف البيانات الموسّعة الاختياري. يمكن أن يتضمّن كل حزمة APK ملفًا رئيسيًا واحدًا وملف اختياري واحدًا فقط.
<expansion-version>- هذا عدد صحيح يطابق رمز إصدار حزمة APK التي تم ربط حزمة البيانات الموسّعة بها لأول مرة (وهو يطابق قيمة
android:versionCodeللتطبيق).تم التأكيد على كلمة "الأول" لأنّه على الرغم من أنّ Play Console تتيح لك إعادة استخدام ملف بيانات موسّعة تم تحميله مع حِزمة APK جديدة، لا يتغيّر اسم ملف البيانات الموسّعة، بل يحتفظ بالإصدار الذي تم تطبيقه عليه عند تحميل الملف لأول مرة.
<package-name>- اسم حزمة تطبيقك بنمط Java
على سبيل المثال، لنفترض أنّ إصدار حزمة APK هو 314159 واسم الحزمة هو com.example.app. في حال تحميل ملف البيانات الموسّعة الأساسي، سيتم إعادة تسمية الملف على النحو التالي:
main.314159.com.example.app.obb
مكان التخزين
عندما ينزّل Google Play ملفات البيانات الموسّعة إلى أحد الأجهزة، يتم حفظها في موقع التخزين المشترَك بالنظام. لضمان عمل التطبيق بشكل سليم، يجب عدم حذف ملفات البيانات الموسّعة أو نقلها أو إعادة تسميتها. في حال كان تطبيقك بحاجة إلى إجراء عملية التنزيل من Google Play نفسه، عليك حفظ الملفات في الموقع نفسه بالضبط.
تعرض الطريقة getObbDir() الموقع الجغرافي المحدّد لملفات البيانات الموسّعة بالتنسيق التالي:
<shared-storage>/Android/obb/<package-name>/
<shared-storage>هو مسار مساحة التخزين المشتركة المتاحة منgetExternalStorageDirectory().<package-name>هو اسم حزمة تطبيقك بنمط Java، ويمكنك الحصول عليه منgetPackageName().
لا يتضمّن هذا الدليل أكثر من ملفَي بيانات موسّعة لكل تطبيق.
أحدهما هو ملف البيانات الموسّعة الأساسي والآخر هو ملف البيانات الموسّعة الاختياري (إذا لزم الأمر). يتم استبدال الإصدارات السابقة عند تحديث تطبيقك باستخدام ملفات بيانات موسّعة جديدة. منذ الإصدار 4.4 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 19)، يمكن للتطبيقات قراءة ملفات البيانات الموسّعة OBB بدون إذن الوصول إلى وحدة تخزين خارجية. ومع ذلك، لا تزال بعض عمليات تنفيذ الإصدار 6.0 من نظام التشغيل Android (المستوى 23 من واجهة برمجة التطبيقات) والإصدارات الأحدث تتطلّب الحصول على إذن، لذا عليك إدراج الإذن READ_EXTERNAL_STORAGE في ملف بيان التطبيق وطلب الإذن في وقت التشغيل على النحو التالي:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
في الإصدار 6 من نظام التشغيل Android والإصدارات الأحدث، يجب طلب إذن الوصول إلى مساحة التخزين الخارجية في وقت التشغيل. ومع ذلك، لا تتطلّب بعض عمليات تنفيذ نظام التشغيل Android الحصول على إذن لقراءة ملفات OBB. يوضّح مقتطف الرمز البرمجي التالي كيفية التحقّق من إذن الوصول للقراءة قبل طلب إذن التخزين الخارجي:
Kotlin
val obb = File(obb_filename) var open_failed = false try { BufferedReader(FileReader(obb)).also { br -> ReadObbFile(br) } } catch (e: IOException) { open_failed = true } if (open_failed) { // request READ_EXTERNAL_STORAGE permission before reading OBB file ReadObbFileWithPermission() }
Java
File obb = new File(obb_filename); boolean open_failed = false; try { BufferedReader br = new BufferedReader(new FileReader(obb)); open_failed = false; ReadObbFile(br); } catch (IOException e) { open_failed = true; } if (open_failed) { // request READ_EXTERNAL_STORAGE permission before reading OBB file ReadObbFileWithPermission(); }
إذا كان عليك فك حزمة محتويات ملفات البيانات الموسّعة، لا تحذف ملفات البيانات الموسّعة OBB بعد ذلك، ولا تحفظ البيانات التي تم فك حزمتها في الدليل نفسه. يجب حفظ الملفات التي تم فك ضغطها في الدليل
المحدّد بواسطة getExternalFilesDir(). ومع ذلك، إذا كان ذلك ممكنًا، من الأفضل استخدام تنسيق ملف بيانات موسّعة يتيح لك القراءة مباشرةً من الملف بدلاً من الاضطرار إلى فك حزمة البيانات. على سبيل المثال، وفّرنا مشروع مكتبة باسم مكتبة ملفات ZIP لتوسيع حِزم APK يقرأ بياناتك مباشرةً من ملف ZIP.
تنبيه: على عكس ملفات APK، يمكن للمستخدم والتطبيقات الأخرى قراءة أي ملفات محفوظة في مساحة التخزين المشتركة.
ملاحظة: إذا كنت بصدد تجميع ملفات وسائط في ملف ZIP، يمكنك استخدام طلبات تشغيل الوسائط على الملفات مع عناصر التحكّم في الإزاحة والطول (مثل MediaPlayer.setDataSource() وSoundPool.load()) بدون الحاجة إلى فك حزمة ZIP. ولكي تنجح هذه العملية، يجب عدم إجراء ضغط إضافي على ملفات الوسائط عند إنشاء حِزم ZIP. على سبيل المثال، عند استخدام الأداة zip،
عليك استخدام الخيار -n لتحديد لاحقات الملفات التي يجب عدم ضغطها:
zip -n .mp4;.ogg main_expansion media_files
عملية التنزيل
في معظم الأحيان، ينزِّل Google Play ملفات البيانات الموسّعة ويحفظها في الوقت نفسه الذي ينزِّل فيه حزمة APK على الجهاز. ومع ذلك، في بعض الحالات، لا يمكن لتطبيق Google Play تنزيل ملفات البيانات الموسّعة، أو قد يكون المستخدم قد حذف ملفات البيانات الموسّعة التي تم تنزيلها سابقًا. للتعامل مع هذه الحالات، يجب أن يتمكّن تطبيقك من تنزيل الملفات بنفسه عند بدء النشاط الرئيسي، وذلك باستخدام عنوان URL يوفّره Google Play.
تبدو عملية التنزيل على مستوى عالٍ كما يلي:
- يختار المستخدم تثبيت تطبيقك من Google Play.
- إذا كان بإمكان Google Play تنزيل ملفات البيانات الموسّعة (وهو ما يحدث في معظم الأجهزة)، سيتم تنزيلها مع حزمة APK.
إذا تعذّر على Google Play تنزيل ملفات البيانات الموسّعة، سيتم تنزيل حزمة APK فقط.
- عندما يشغّل المستخدم تطبيقك، يجب أن يتحقّق تطبيقك مما إذا كانت ملفات البيانات الموسّعة محفوظة على الجهاز.
- إذا كانت الإجابة "نعم"، يكون تطبيقك جاهزًا.
- إذا كانت الإجابة "لا"، يجب أن ينزّل تطبيقك ملفات البيانات الموسّعة عبر HTTP من Google Play. يجب أن يرسل تطبيقك طلبًا إلى برنامج Google Play باستخدام خدمة ترخيص التطبيق من Google Play، والتي ترد بالاسم وحجم الملف وعنوان URL لكل ملف بيانات موسّعة. باستخدام هذه المعلومات، يمكنك بعد ذلك تنزيل الملفات وحفظها في موقع التخزين المناسب.
تنبيه: من الضروري تضمين الرمز البرمجي اللازم لتنزيل ملفات البيانات الموسّعة من Google Play في حال لم تكن الملفات متوفّرة على الجهاز عند بدء تشغيل تطبيقك. كما هو موضّح في القسم التالي حول تنزيل ملفات البيانات الموسّعة، أتحنا لك مكتبة تعمل على تبسيط هذه العملية بشكل كبير وتنزيل الملفات من خدمة تتطلّب الحد الأدنى من الرموز البرمجية من جانبك.
قائمة التحقّق الخاصة بالتطوير
في ما يلي ملخّص للمهام التي يجب تنفيذها لاستخدام ملفات البيانات الموسّعة مع تطبيقك:
- عليك أولاً تحديد ما إذا كان حجم التنزيل المضغوط لتطبيقك يجب أن يتجاوز 100 ميغابايت. المساحة التخزينية ثمينة، لذا عليك الحفاظ على إجمالي حجم التنزيل صغيرًا قدر الإمكان. إذا كان تطبيقك يستخدم أكثر من 100 ميغابايت لتوفير إصدارات متعددة من مواد عرض الرسومات لتناسب كثافات شاشة متعددة، ننصحك بدلاً من ذلك بنشر حِزم APK متعددة يحتوي كل منها على مواد العرض المطلوبة للشاشات التي يستهدفها فقط. للحصول على أفضل النتائج عند النشر على Google Play، حمِّل مجموعة حزمات تطبيق Android التي تتضمّن جميع الرموز البرمجية والموارد المجمَّعة لتطبيقك، ولكنها تؤجّل إنشاء حِزم APK وتوقيعها إلى Google Play.
- حدِّد موارد التطبيق التي تريد فصلها عن حزمة APK، ثم ضعها في ملف لاستخدامه كملف البيانات الموسّعة الرئيسي.
في العادة، يجب عدم استخدام ملف البيانات الموسّعة الاختياري إلا عند إجراء تعديلات على ملف البيانات الموسّعة الأساسي. ومع ذلك، إذا تجاوزت مواردك الحدّ الأقصى البالغ 2 غيغابايت لملف البيانات الموسّعة الأساسي، يمكنك استخدام ملف اختياري لتخزين بقية مواد العرض.
- طوِّر تطبيقك بحيث يستخدم الموارد من ملفات البيانات الموسّعة في موقع التخزين المشترَك على الجهاز.
يُرجى العِلم أنّه يجب عدم حذف ملفات البيانات الموسّعة أو نقلها أو إعادة تسميتها.
إذا كان تطبيقك لا يتطلّب تنسيقًا معيّنًا، ننصحك بإنشاء ملفات ZIP لملفات البيانات الموسّعة، ثم قراءتها باستخدام مكتبة APK Expansion Zip.
- أضِف منطقًا إلى النشاط الرئيسي لتطبيقك يتحقّق مما إذا كانت ملفات البيانات الموسّعة متوفّرة على الجهاز عند بدء التشغيل. إذا لم تكن الملفات على الجهاز، استخدِم خدمة ترخيص التطبيقات في Google Play لطلب عناوين URL
للملفات الموسّعة، ثم نزِّلها واحفظها.
لتقليل مقدار الرمز البرمجي الذي عليك كتابته بشكل كبير وضمان تقديم تجربة جيدة للمستخدم أثناء التنزيل، ننصحك باستخدام مكتبة Downloader لتنفيذ سلوك التنزيل.
إذا أنشأت خدمة تنزيل خاصة بدلاً من استخدام المكتبة، يجب الانتباه إلى أنّه يجب عدم تغيير اسم ملفات البيانات الموسّعة وحفظها في موقع التخزين المناسب.
بعد الانتهاء من تطوير تطبيقك، اتّبِع الدليل لاختبار ملفات البيانات الموسّعة.
القواعد والقيود
تتوفّر ميزة إضافة ملفات البيانات الموسّعة الخاصة بحزمة APK عند تحميل تطبيقك باستخدام Play Console. عند تحميل تطبيقك للمرة الأولى أو تعديل تطبيق يستخدم ملفات بيانات إضافية، يجب أن تكون على دراية بالقواعد والقيود التالية:
- يجب ألا يزيد حجم كل ملف بيانات موسّعة عن 2 غيغابايت.
- لتنزيل ملفات البيانات الموسّعة من Google Play، يجب أن يكون المستخدم قد حصل على تطبيقك من Google Play. لن يوفّر Google Play عناوين URL لملفات البيانات الموسّعة إذا تم تثبيت التطبيق بطرق أخرى.
- عند إجراء عملية التنزيل من داخل تطبيقك، يكون عنوان URL الذي يوفّره Google Play لكل ملف فريدًا لكل عملية تنزيل، وتنتهي صلاحية كل عنوان URL بعد فترة قصيرة من تقديمه إلى تطبيقك.
- إذا حدّثت تطبيقك باستخدام حزمة APK جديدة أو حمّلت حِزم APK متعددة للتطبيق نفسه، يمكنك اختيار ملفات البيانات الموسّعة التي حمّلتها لحزمة APK سابقة. لا يتغيّر اسم ملف البيانات الموسّعة، بل يحتفظ بالإصدار الذي تلقّاه حِزمة APK التي كان الملف مرتبطًا بها في الأصل.
- إذا كنت تستخدم ملفات البيانات الموسّعة مع حِزم APK متعددة لتوفير ملفات بيانات موسّعة مختلفة للأجهزة المختلفة، سيظل عليك تحميل حِزم APK منفصلة لكل جهاز من أجل توفير قيمة
versionCodeفريدة وتحديد فلاتر مختلفة لكل حزمة APK. - لا يمكنك طرح تحديث لتطبيقك من خلال تغيير ملفات البيانات الموسّعة فقط، بل يجب تحميل حزمة APK جديدة لتحديث تطبيقك. وإذا كانت التغييرات التي أجريتها تخص مواد العرض في ملفات البيانات الموسّعة فقط، يمكنك تحديث حزمة APK ببساطة عن طريق تغيير
versionCode(وربما أيضًاversionName). - لا تحفظ بيانات أخرى في
obb/دليلك. إذا كان عليك فك حزمة بعض البيانات، احفظها في الموقع الجغرافي الذي تحدّدهgetExternalFilesDir(). - لا تحذف
.obbملف البيانات الموسّعة أو تعيد تسميته (إلا إذا كنت بصدد إجراء تحديث). وسيؤدي ذلك إلى أن يعيد Google Play (أو تطبيقك نفسه) تنزيل ملف البيانات الموسّعة بشكل متكرّر. - عند تعديل ملف بيانات موسّعة يدويًا، يجب حذف ملف البيانات الموسّعة السابق.
تنزيل ملفات البيانات الموسّعة
في معظم الحالات، ينزِّل Google Play ملفات البيانات الموسّعة ويحفظها على الجهاز في الوقت نفسه الذي يثبّت فيه حزمة APK أو يحدّثها. بهذه الطريقة، ستكون ملفات البيانات الموسّعة متاحة عند تشغيل تطبيقك للمرة الأولى. ومع ذلك، في بعض الحالات، يجب أن ينزّل تطبيقك ملفات البيانات الموسّعة بنفسه من خلال طلبها من عنوان URL يتم تقديمه لك في ردّ من خدمة الترخيص في Google Play.
في ما يلي المنطق الأساسي الذي تحتاج إليه لتنزيل ملفات البيانات الموسّعة:
- عند بدء تشغيل تطبيقك، ابحث عن ملفات البيانات الموسّعة في موقع التخزين المشترَك (في الدليل
Android/obb/<package-name>/).- إذا كانت ملفات البيانات الموسّعة متوفّرة، يمكنك مواصلة استخدام تطبيقك.
- إذا لم تكن ملفات البيانات الموسّعة متوفّرة:
- إجراء طلب باستخدام خدمة الترخيص في Google Play للحصول على أسماء ملفات البيانات الموسّعة وأحجامها وعناوين URL الخاصة بتطبيقك
- استخدِم عناوين URL التي يوفّرها Google Play لتنزيل ملفات البيانات الموسّعة وحفظها. يجب حفظ الملفات في موقع التخزين المشترَك
(
Android/obb/<package-name>/) واستخدام اسم الملف نفسه الذي تم تقديمه في ردّ Google Play.ملاحظة: عنوان URL الذي يوفّره Google Play لملفات البيانات الموسّعة يكون فريدًا لكل عملية تنزيل، وتنتهي صلاحية كل عنوان URL بعد فترة قصيرة من تقديمه إلى تطبيقك.
إذا كان تطبيقك مجانيًا (وليس تطبيقًا مدفوعًا)، من المحتمل أنّك لم تستخدم خدمة ترخيص التطبيق. تم تصميمها في الأساس لفرض سياسات الترخيص على تطبيقك والتأكّد من أنّ المستخدم لديه الحق في استخدام تطبيقك (أي أنّه دفع ثمنه بشكل صحيح على Google Play). لتسهيل وظيفة ملف البيانات الموسّعة، تم تحسين خدمة الترخيص لتقديم رد إلى تطبيقك يتضمّن عنوان URL لملفات البيانات الموسّعة الخاصة بتطبيقك والمستضافة على Google Play. لذا، حتى إذا كان تطبيقك مجانيًا للمستخدمين، عليك تضمين مكتبة License Verification Library (LVL) لاستخدام ملفات بيانات APK الموسّعة. بالطبع، إذا كان تطبيقك مجانيًا، لن تحتاج إلى فرض التحقّق من الترخيص، بل ستحتاج فقط إلى المكتبة لتنفيذ الطلب الذي يعرض عنوان URL لملفات البيانات الموسّعة.
ملاحظة: سواء كان تطبيقك مجانيًا أو لا، لن يعرض Google Play عناوين URL لملفات البيانات الموسّعة إلا إذا حصل المستخدم على تطبيقك من Google Play.
بالإضافة إلى LVL، تحتاج إلى مجموعة من الرموز البرمجية التي تنزّل ملفات البيانات الموسّعة عبر اتصال HTTP وتحفظها في الموقع المناسب على مساحة التخزين المشتركة بالجهاز. عند تضمين هذا الإجراء في تطبيقك، هناك عدة مشاكل يجب أخذها في الاعتبار:
- قد لا تتوفّر مساحة كافية على الجهاز لملفات البيانات الموسّعة، لذا عليك التحقّق من ذلك قبل بدء التنزيل وتنبيه المستخدم إذا لم تتوفّر مساحة كافية.
- يجب أن تتم عمليات تنزيل الملفات في خدمة تُشغَّل في الخلفية لتجنُّب حظر تفاعل المستخدم والسماح له بمغادرة تطبيقك أثناء اكتمال عملية التنزيل.
- قد تحدث مجموعة متنوعة من الأخطاء أثناء الطلب والتنزيل، ويجب التعامل معها بشكل سليم.
- يمكن أن يتغير الاتصال بالشبكة أثناء عملية التنزيل، لذا عليك التعامل مع هذه التغييرات، وفي حال حدوث انقطاع، استئناف عملية التنزيل عند الإمكان.
- أثناء عملية التنزيل في الخلفية، عليك تقديم إشعار يوضّح مستوى تقدّم عملية التنزيل، ويُعلم المستخدم عند اكتمالها، ويعيده إلى تطبيقك عند تحديده.
لتسهيل هذه العملية، أنشأنا مكتبة أدوات التنزيل التي تطلب عناوين URL لملفات البيانات الموسّعة من خلال خدمة الترخيص، وتنزّل ملفات البيانات الموسّعة، وتنفّذ جميع المهام المذكورة أعلاه، وتتيح حتى إيقاف التنزيل مؤقتًا واستئنافه. من خلال إضافة مكتبة Downloader وبعض خطافات الرموز البرمجية إلى تطبيقك، سيتم ترميز جميع الإجراءات اللازمة لتنزيل ملفات البيانات الموسّعة نيابةً عنك. لذا، ولتقديم أفضل تجربة للمستخدمين بأقل جهد ممكن من جانبك، ننصحك باستخدام مكتبة Downloader لتنزيل ملفات البيانات الموسّعة. توضّح المعلومات الواردة في الأقسام التالية كيفية دمج المكتبة في تطبيقك.
إذا كنت تفضّل تطوير حلّك الخاص لتنزيل ملفات البيانات الموسّعة باستخدام عناوين URL في Google Play، عليك اتّباع مستندات منح تراخيص التطبيقات لتنفيذ طلب ترخيص، ثم استرداد أسماء ملفات البيانات الموسّعة وأحجامها وعناوين URL الخاصة بها من تفاصيل إضافية مع الرد. عليك استخدام فئة APKExpansionPolicy (المضمّنة في "مكتبة التحقّق من الترخيص") كسياسة الترخيص، وهي تتضمّن أسماء ملفات البيانات الموسّعة وأحجامها وعناوين URL الخاصة بها من خدمة الترخيص.
لمحة عن مكتبة Downloader
لاستخدام ملفات توسيع APK مع تطبيقك وتقديم أفضل تجربة للمستخدم بأقل جهد ممكن من جانبك، ننصحك باستخدام Downloader Library المضمّنة في حزمة Google Play APK Expansion Library. تنزّل هذه المكتبة ملفات البيانات الموسّعة في خدمة تُشغَّل في الخلفية، وتعرض إشعارًا للمستخدم يتضمّن حالة التنزيل، وتتعامل مع فقدان الاتصال بالشبكة، وتستأنف عملية التنزيل عند الإمكان، وغير ذلك.
لتنفيذ عمليات تنزيل ملفات البيانات الموسّعة باستخدام Downloader Library، ما عليك سوى اتّباع الخطوات التالية:
- يمكنك توسيع الفئة الفرعية
Serviceوالفئة الفرعيةBroadcastReceiverاللتين تتطلّبان بضعة أسطر فقط من الرموز البرمجية. - أضِف بعض المنطق إلى نشاطك الرئيسي الذي يتحقّق مما إذا تم تنزيل ملفات البيانات الموسّعة من قبل، وإذا لم يتم ذلك، ابدأ عملية التنزيل واعرض واجهة مستخدم تعرض مستوى التقدّم.
- نفِّذ واجهة دالة ردّ الاتصال مع بعض الطرق في نشاطك الرئيسي الذي يتلقّى إشعارات بشأن مستوى تقدّم التنزيل.
توضّح الأقسام التالية كيفية إعداد تطبيقك باستخدام "مكتبة أداة التنزيل".
الاستعداد لاستخدام مكتبة Downloader
لاستخدام مكتبة Downloader، عليك تنزيل حزمتَين من "أداة إدارة حِزم SDK" وإضافة المكتبات المناسبة إلى تطبيقك.
أولاً، افتح مدير حزمة تطوير البرامج (SDK) لنظام التشغيل Android (الأدوات > مدير حزمة تطوير البرامج (SDK))، وضِمن المظهر والسلوك > إعدادات النظام > حزمة تطوير البرامج (SDK) لنظام التشغيل Android، اختَر علامة التبويب أدوات حزمة تطوير البرامج (SDK) لاختيار وتنزيل ما يلي:
- حزمة "مكتبة الترخيص في Google Play"
- حزمة Google Play APK Expansion Library
أنشِئ وحدة مكتبة جديدة لمكتبة التحقّق من الترخيص ومكتبة Downloader. لكل مكتبة:
- انقر على ملف (File) > جديد (New) > وحدة جديدة (New Module).
- في نافذة إنشاء وحدة جديدة، اختَر مكتبة Android، ثم انقر على التالي.
- حدِّد اسم التطبيق/المكتبة، مثل "مكتبة ترخيص Google Play" و "مكتبة التنزيل من Google Play"، واختَر الحد الأدنى لمستوى حزمة SDK، ثم انقر على إنهاء.
- انقر على ملف > بنية المشروع.
- انقر على علامة التبويب الخصائص، ثم أدخِل المكتبة من دليل
<sdk>/extras/google/في مستودع المكتبة (play_licensing/لـ "مكتبة التحقّق من الترخيص" أوplay_apk_expansion/downloader_library/لـ "مكتبة أداة التنزيل"). - انقر على حسنًا لإنشاء الوحدة الجديدة.
ملاحظة: تعتمد "مكتبة التنزيل" على "مكتبة التحقّق من الترخيص". احرص على إضافة "مكتبة التحقّق من الترخيص" إلى خصائص مشروع "مكتبة أداة التنزيل".
أو من سطر الأوامر، عدِّل مشروعك لتضمين المكتبات:
- غيِّر الأدلة إلى الدليل
<sdk>/tools/. - نفِّذ
android update projectباستخدام الخيار--libraryلإضافة كل من مكتبة LVL ومكتبة Downloader إلى مشروعك. على سبيل المثال:android update project --path ~/Android/MyApp \ --library ~/android_sdk/extras/google/market_licensing \ --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
بعد إضافة كلّ من مكتبة التحقّق من الترخيص ومكتبة Downloader إلى تطبيقك، سيصبح بإمكانك دمج إمكانية تنزيل ملفات البيانات الموسّعة من Google Play بسرعة. إنّ التنسيق الذي تختاره لملفات البيانات الموسّعة وطريقة قراءتها من مساحة التخزين المشتركة هما تنفيذ منفصل يجب أخذه في الاعتبار استنادًا إلى احتياجات تطبيقك.
ملاحظة: تتضمّن حزمة بيانات APK الموسّعة نموذجًا لتطبيق يوضّح كيفية استخدام مكتبة Downloader في تطبيق. ويستخدم النموذج مكتبة تابعة لجهة خارجية متوفّرة في حزمة بيانات APK الموسّعة تُسمّى "مكتبة Zip لبيانات APK الموسّّعة". إذا كنت تخطّط لاستخدام ملفات ZIP لملفات البيانات الموسّعة، ننصحك أيضًا بإضافة مكتبة APK Expansion Zip Library إلى تطبيقك. لمزيد من المعلومات، راجِع القسم أدناه حول استخدام مكتبة APK Expansion Zip Library.
تضمين أذونات المستخدمين في نماذج البيان
لتنزيل ملفات البيانات الموسّعة، تتطلّب مكتبة Downloader عدة أذونات يجب الإشارة إليها في ملف بيان التطبيق. وهي:
<manifest ...> <!-- Required to access Google Play Licensing --> <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> <!-- Required to download files from Google Play --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- Required to poll the state of the network connection and respond to changes --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- Required to check whether Wi-Fi is enabled --> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!-- Required to read and write the expansion files on shared storage --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
ملاحظة: تتطلّب مكتبة Downloader Library تلقائيًا مستوى واجهة برمجة التطبيقات 4، بينما تتطلّب مكتبة APK Expansion Zip Library مستوى واجهة برمجة التطبيقات 5.
تنفيذ خدمة التنزيل
لتنفيذ عمليات التنزيل في الخلفية، توفّر مكتبة Downloader Library فئة فرعية خاصة بها باسم DownloaderService يجب توسيعها.Service بالإضافة إلى تنزيل ملفات البيانات الموسّعة نيابةً عنك، فإنّ DownloaderService يتيح لك أيضًا ما يلي:
- تسجّل هذه السمة
BroadcastReceiverيستمع إلى التغييرات في اتصال الجهاز بالشبكة (عملية البثCONNECTIVITY_ACTION) من أجل إيقاف التنزيل مؤقتًا عند الضرورة (مثل فقدان الاتصال) واستئنافه عند الإمكان (عند استعادة الاتصال). - يجدول هذا الإجراء منبّه
RTC_WAKEUPلإعادة محاولة التنزيل في الحالات التي يتم فيها إيقاف الخدمة. - تنشئ هذه السمة
Notificationمخصّصة تعرض مستوى تقدّم التنزيل وأي أخطاء أو تغييرات في الحالة. - يسمح هذا الإذن لتطبيقك بإيقاف التنزيل مؤقتًا واستئنافه يدويًا.
- تتحقّق هذه السمة من أنّ وحدة التخزين المشتركة مثبَّتة ومتاحة، وأنّ الملفات غير موجودة من قبل، وأنّ هناك مساحة كافية، وذلك قبل تنزيل ملفات البيانات الموسّعة. ثم يتم إعلام المستخدم إذا لم يكن أيّ من هذه الشروط صحيحًا.
كل ما عليك فعله هو إنشاء فئة في تطبيقك توسّع الفئة DownloaderService وتتجاوز ثلاث طرق لتقديم تفاصيل تطبيق معيّن:
getPublicKey()- يجب أن تعرض هذه السمة سلسلة تمثّل المفتاح العام للإعلان المتجاوب على شبكة البحث بترميز Base64 لحساب الناشر، ويمكن الحصول عليها من صفحة الملف الشخصي في Play Console (راجِع الإعداد للترخيص).
getSALT()- يجب أن تعرض هذه السمة مصفوفة من وحدات البايت العشوائية التي تستخدمها
Policyفي إنشاءObfuscator. يضمن هذا النص أن يكون ملفSharedPreferencesالذي يتم فيه حفظ بيانات الترخيص فريدًا وغير قابل للاكتشاف. getAlarmReceiverClassName()- يجب أن تعرض هذه السمة اسم فئة
BroadcastReceiverفي تطبيقك الذي من المفترض أن يتلقّى التنبيه الذي يشير إلى ضرورة إعادة تشغيل عملية التنزيل (وهو ما قد يحدث إذا توقفت خدمة أداة التنزيل بشكل غير متوقّع).
على سبيل المثال، إليك عملية تنفيذ كاملة للسمة DownloaderService:
Kotlin
// You must use the public key belonging to your publisher account const val BASE64_PUBLIC_KEY = "YourLVLKey" // You should also modify this salt val SALT = byteArrayOf( 1, 42, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84 ) class SampleDownloaderService : DownloaderService() { override fun getPublicKey(): String = BASE64_PUBLIC_KEY override fun getSALT(): ByteArray = SALT override fun getAlarmReceiverClassName(): String = SampleAlarmReceiver::class.java.name }
Java
public class SampleDownloaderService extends DownloaderService { // You must use the public key belonging to your publisher account public static final String BASE64_PUBLIC_KEY = "YourLVLKey"; // You should also modify this salt public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84 }; @Override public String getPublicKey() { return BASE64_PUBLIC_KEY; } @Override public byte[] getSALT() { return SALT; } @Override public String getAlarmReceiverClassName() { return SampleAlarmReceiver.class.getName(); } }
ملاحظة: يجب تعديل قيمة BASE64_PUBLIC_KEY
لتصبح المفتاح العام الخاص بحساب الناشر. يمكنك العثور على المفتاح في Developer Console ضمن معلومات ملفك الشخصي. وهذه الخطوة ضرورية حتى عند اختبار عمليات التنزيل.
تذكَّر تعريف الخدمة في ملف البيان:
<app ...> <service android:name=".SampleDownloaderService" /> ... </app>
تنفيذ أداة استقبال المنبّه
لمراقبة مستوى تقدّم عمليات تنزيل الملفات وإعادة تشغيل عملية التنزيل إذا لزم الأمر، يجدول DownloaderService منبه RTC_WAKEUP يرسل Intent إلى BroadcastReceiver في تطبيقك. يجب تحديد BroadcastReceiver لاستدعاء واجهة برمجة تطبيقات من Downloader Library تتحقّق من حالة التنزيل وتعيد تشغيله إذا لزم الأمر.
ما عليك سوى إلغاء طريقة onReceive() لاستدعاء DownloaderClientMarshaller.startDownloadServiceIfRequired().
على سبيل المثال:
Kotlin
class SampleAlarmReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { try { DownloaderClientMarshaller.startDownloadServiceIfRequired( context, intent, SampleDownloaderService::class.java ) } catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() } } }
Java
public class SampleAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { try { DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, SampleDownloaderService.class); } catch (NameNotFoundException e) { e.printStackTrace(); } } }
يُرجى العِلم أنّ هذه هي الفئة التي يجب عرض اسمها في طريقة getAlarmReceiverClassName() الخاصة بالخدمة (راجِع القسم السابق).
يُرجى تذكُّر تعريف أداة الاستقبال في ملف البيان:
<app ...> <receiver android:name=".SampleAlarmReceiver" /> ... </app>
بدء التنزيل
النشاط الرئيسي في تطبيقك (النشاط الذي يبدأ عند النقر على رمز مشغّل التطبيقات) هو المسؤول عن التحقّق مما إذا كانت ملفات البيانات الموسّعة متوفّرة على الجهاز وبدء عملية التنزيل إذا لم تكن متوفّرة.
يتطلّب بدء التنزيل باستخدام "مكتبة أداة التنزيل" الإجراءات التالية:
- تحقَّق ممّا إذا تم تنزيل الملفات.
تتضمّن مكتبة Downloader بعض واجهات برمجة التطبيقات في الفئة
Helperللمساعدة في هذه العملية:getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)doesFileExist(Context c, String fileName, long fileSize)
على سبيل المثال، يستدعي التطبيق النموذجي المقدَّم في حزمة بيانات APK الموسّعة الطريقة التالية في طريقة
onCreate()الخاصة بالنشاط للتحقّق مما إذا كانت ملفات البيانات الموسّعة متوفّرة على الجهاز:Kotlin
fun expansionFilesDelivered(): Boolean { xAPKS.forEach { xf -> Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion).also { fileName -> if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false)) return false } } return true }
Java
boolean expansionFilesDelivered() { for (XAPKFile xf : xAPKS) { String fileName = Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion); if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false)) return false; } return true; }
في هذه الحالة، يحتوي كل عنصر
XAPKFileعلى رقم الإصدار وحجم الملف لملف بيانات موسّعة معروف وقيمة منطقية تحدّد ما إذا كان ملف البيانات الموسّعة الرئيسي. (يمكنك الاطّلاع على فئةSampleDownloaderActivityفي نموذج التطبيق لمعرفة التفاصيل).إذا عرضت هذه الطريقة القيمة "خطأ"، يجب أن يبدأ التطبيق عملية التنزيل.
- ابدأ التنزيل من خلال استدعاء الطريقة الثابتة
DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass).تتطلّب الطريقة المَعلمات التالية:
context:ContextلتطبيقكnotificationClient:PendingIntentلبدء نشاطك الرئيسي يتم استخدام هذا المعرّف فيNotificationالذي ينشئهDownloaderServiceلعرض مستوى تقدّم عملية التنزيل. عندما يختار المستخدم الإشعار، يستدعي النظامPendingIntentالذي تقدّمه هنا، ويجب أن يفتح النشاط الذي يعرض تقدّم التنزيل (عادةً ما يكون النشاط نفسه الذي بدأ التنزيل).serviceClass: عنصرClassالخاص بتنفيذك لـDownloaderService، وهو مطلوب لبدء الخدمة وبدء التنزيل إذا لزم الأمر.
تعرض الطريقة عددًا صحيحًا يشير إلى ما إذا كان التنزيل مطلوبًا أم لا. القيم المحتملة هي:
-
NO_DOWNLOAD_REQUIRED: يتم عرض هذا الرمز إذا كانت الملفات متوفّرة أو إذا كان التنزيل قيد التقدّم. -
LVL_CHECK_REQUIRED: يتم عرض هذا الرمز إذا كان التحقّق من الترخيص مطلوبًا للحصول على عناوين URL لملف البيانات الموسّعة. -
DOWNLOAD_REQUIRED: يتم عرض هذا الرمز إذا كانت عناوين URL لملفات البيانات الموسّعة معروفة، ولكن لم يتم تنزيلها.
إنّ سلوك السمتين
LVL_CHECK_REQUIREDوDOWNLOAD_REQUIREDهو نفسه بشكل أساسي، ولا داعي للقلق بشأنهما عادةً. في نشاطك الرئيسي الذي يستدعيstartDownloadServiceIfRequired()، يمكنك ببساطة التحقّق مما إذا كانت الاستجابةNO_DOWNLOAD_REQUIREDأم لا. إذا كانت الاستجابة غيرNO_DOWNLOAD_REQUIRED، تبدأ "مكتبة أداة التنزيل" عملية التنزيل، وعليك تعديل واجهة مستخدم النشاط لعرض مستوى تقدّم عملية التنزيل (راجِع الخطوة التالية). إذا كانت الاستجابة هيNO_DOWNLOAD_REQUIRED، يعني ذلك أنّ الملفات متاحة ويمكن لتطبيقك البدء.على سبيل المثال:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Check if expansion files are available before going any further if (!expansionFilesDelivered()) { val pendingIntent = // Build an Intent to start this activity from the Notification Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP }.let { notifierIntent -> PendingIntent.getActivity( this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT ) } // Start the download service (if required) val startResult: Int = DownloaderClientMarshaller.startDownloadServiceIfRequired( this, pendingIntent, SampleDownloaderService::class.java ) // If download has started, initialize this activity to show // download progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download // progress (next step) ... return } // If the download wasn't necessary, fall through to start the app } startApp() // Expansion files are available, start the app }
Java
@Override public void onCreate(Bundle savedInstanceState) { // Check if expansion files are available before going any further if (!expansionFilesDelivered()) { // Build an Intent to start this activity from the Notification Intent notifierIntent = new Intent(this, MainActivity.getClass()); notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); ... PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); // Start the download service (if required) int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize this activity to show // download progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download // progress (next step) ... return; } // If the download wasn't necessary, fall through to start the app } startApp(); // Expansion files are available, start the app }
- عندما يعرض الإجراء
startDownloadServiceIfRequired()أي قيمة غيرNO_DOWNLOAD_REQUIRED، أنشئ مثيلاً منIStubعن طريق استدعاءDownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService). يوفرIStubربطًا بين نشاطك وخدمة التنزيل، ما يتيح لنشاطك تلقّي عمليات ردّ بشأن مدى تقدّم عملية التنزيل.لتنفيذ
IStubمن خلال استدعاءCreateStub()، يجب تمرير تنفيذ واجهةIDownloaderClientوتنفيذDownloaderService. يتناول القسم التالي حول تلقّي معلومات عن تقدّم عملية التنزيل واجهةIDownloaderClient، والتي يجب عادةً تنفيذها في فئةActivityحتى تتمكّن من تعديل واجهة مستخدم النشاط عند تغيُّر حالة التنزيل.ننصحك باستدعاء
CreateStub()لإنشاء مثيلIStubأثناء تنفيذ طريقةonCreate()في نشاطك، بعد أن يبدأstartDownloadServiceIfRequired()عملية التنزيل.على سبيل المثال، في عينة التعليمات البرمجية السابقة الخاصة بـ
onCreate()، يمكنك الردّ على النتيجةstartDownloadServiceIfRequired()على النحو التالي:Kotlin
// Start the download service (if required) val startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( this@MainActivity, pendingIntent, SampleDownloaderService::class.java ) // If download has started, initialize activity to show progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // Instantiate a member instance of IStub downloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService::class.java) // Inflate layout that shows download progress setContentView(R.layout.downloader_ui) return }
Java
// Start the download service (if required) int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize activity to show progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // Instantiate a member instance of IStub downloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService.class); // Inflate layout that shows download progress setContentView(R.layout.downloader_ui); return; }
بعد أن يعرض الإجراء
onCreate()النتيجة، يتلقّى نشاطك طلبًا إلىonResume()، وهو المكان الذي يجب أن تستدعي فيهconnect()علىIStub، مع تمريرContextالخاص بتطبيقك. في المقابل، عليك استدعاءdisconnect()في دالة معاودة الاتصالonStop()الخاصة بالنشاط.Kotlin
override fun onResume() { downloaderClientStub?.connect(this) super.onResume() } override fun onStop() { downloaderClientStub?.disconnect(this) super.onStop() }
Java
@Override protected void onResume() { if (null != downloaderClientStub) { downloaderClientStub.connect(this); } super.onResume(); } @Override protected void onStop() { if (null != downloaderClientStub) { downloaderClientStub.disconnect(this); } super.onStop(); }
يؤدي استدعاء
connect()فيIStubإلى ربط نشاطك بـDownloaderService، ما يتيح لنشاطك تلقّي عمليات ردّ بشأن التغييرات التي تطرأ على حالة التنزيل من خلال واجهةIDownloaderClient.
مستوى تقدّم عملية التنزيل
لتلقّي إشعارات بشأن تقدّم عملية التنزيل والتفاعل مع DownloaderService، يجب تنفيذ واجهة IDownloaderClient في Downloader Library.
عادةً، يجب أن ينفّذ النشاط الذي تستخدمه لبدء التنزيل هذه الواجهة من أجل عرض مستوى تقدّم التنزيل وإرسال الطلبات إلى الخدمة.
طُرق الواجهة المطلوبة لـ IDownloaderClient هي:
onServiceConnected(Messenger m)- بعد إنشاء مثيل
IStubفي نشاطك، ستتلقّى طلبًا إلى هذه الطريقة، التي تنقل عنصرMessengerمرتبطًا بمثيلDownloaderService. لإرسال طلبات إلى الخدمة، مثل إيقاف عمليات التنزيل مؤقتًا واستئنافها، عليك الاتصال بـDownloaderServiceMarshaller.CreateProxy()لتلقّي واجهةIDownloaderServiceالمرتبطة بالخدمة.يبدو التنفيذ المقترَح على النحو التالي:
Kotlin
private var remoteService: IDownloaderService? = null ... override fun onServiceConnected(m: Messenger) { remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply { downloaderClientStub?.messenger?.also { messenger -> onClientUpdated(messenger) } } }
Java
private IDownloaderService remoteService; ... @Override public void onServiceConnected(Messenger m) { remoteService = DownloaderServiceMarshaller.CreateProxy(m); remoteService.onClientUpdated(downloaderClientStub.getMessenger()); }
بعد تهيئة الكائن
IDownloaderService، يمكنك إرسال أوامر إلى خدمة التنزيل، مثل إيقاف التنزيل مؤقتًا واستئنافه (requestPauseDownload()وrequestContinueDownload()). onDownloadStateChanged(int newState)- تطلب خدمة التنزيل هذا الإجراء عند حدوث تغيير في حالة التنزيل، مثل بدء التنزيل أو اكتماله.
ستكون قيمة
newStateإحدى القيم المحتملة المتعددة المحددة في بواسطة أحد الثوابتSTATE_*لفئةIDownloaderClient.لتقديم رسالة مفيدة للمستخدمين، يمكنك طلب سلسلة مطابقة لكل حالة من خلال استدعاء
Helpers.getDownloaderStringResourceIDFromState(). تعرض هذه السمة معرّف المورد لأحد السلاسل المضمّنة في مكتبة Downloader. على سبيل المثال، تتوافق السلسلة "تم إيقاف التنزيل مؤقتًا لأنّك تتجوّل" معSTATE_PAUSED_ROAMING. onDownloadProgress(DownloadProgressInfo progress)- تطلب خدمة التنزيل هذا الإجراء لعرض عنصر
DownloadProgressInfo، الذي يصف معلومات مختلفة حول مستوى التقدّم في عملية التنزيل، بما في ذلك الوقت المتبقي المقدَّر والسرعة الحالية ومستوى التقدّم العام والإجمالي، ما يتيح لك تعديل واجهة المستخدم الخاصة بمستوى التقدّم في عملية التنزيل.
ملاحظة: للاطّلاع على أمثلة على عمليات معاودة الاتصال هذه التي تعدّل واجهة المستخدم الخاصة بتقدّم التنزيل، يُرجى الرجوع إلى SampleDownloaderActivity في نموذج التطبيق المتوفّر مع حزمة Apk Expansion.
في ما يلي بعض الطرق العامة لواجهة IDownloaderService التي قد تجدها مفيدة:
requestPauseDownload()- يؤدي إلى إيقاف التنزيل مؤقتًا.
requestContinueDownload()- يستأنف عملية تنزيل تم إيقافها مؤقتًا.
setDownloadFlags(int flags)- تضبط هذه السمة الإعدادات المفضّلة للمستخدمين لأنواع الشبكات التي يمكن تنزيل الملفات عليها. يتيح التنفيذ الحالي استخدام علامة واحدة، وهي
FLAGS_DOWNLOAD_OVER_CELLULAR، ولكن يمكنك إضافة علامات أخرى. لا يتم تفعيل هذا الخيار تلقائيًا، لذا يجب أن يكون المستخدم متصلاً بشبكة Wi-Fi لتنزيل ملفات البيانات الموسّعة. يمكنك توفير إعدادات مفضّلة للمستخدمين تتيح تنزيل المحتوى عبر شبكة الجوّال. في هذه الحالة، يمكنك الاتصال على:Kotlin
remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply { ... setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR) }
Java
remoteService .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
استخدام APKExpansionPolicy
إذا قررت إنشاء خدمة تنزيل خاصة بك بدلاً من استخدام مكتبة Downloader في Google Play، عليك مواصلة استخدام APKExpansionPolicy المتوفّرة في مكتبة التحقّق من الترخيص. الفئة APKExpansionPolicy مطابقة تقريبًا للفئة ServerManagedPolicy (المتاحة في مكتبة التحقّق من الترخيص في Google Play)، ولكنها تتضمّن معالجة إضافية لـ تفاصيل إضافية مع الرد على ملف بيانات موسّعة لحزمة APK.
ملاحظة: إذا كنت تستخدم مكتبة أداة التنزيل كما هو موضّح في القسم السابق، ستتولّى المكتبة جميع التفاعلات مع APKExpansionPolicy، لذا لن تحتاج إلى استخدام هذه الفئة مباشرةً.
تتضمّن الفئة طرقًا لمساعدتك في الحصول على المعلومات اللازمة حول ملفات البيانات الموسّعة المتاحة:
getExpansionURLCount()getExpansionURL(int index)getExpansionFileName(int index)getExpansionFileSize(int index)
لمزيد من المعلومات حول كيفية استخدام APKExpansionPolicy عندما لا تستخدم مكتبة أدوات التنزيل، يُرجى الاطّلاع على مستندات إضافة الترخيص إلى تطبيقك التي توضّح كيفية تنفيذ سياسة ترخيص مثل هذه.
قراءة ملف البيانات الموسّعة
بعد حفظ ملفات توسيع APK على الجهاز، تعتمد طريقة قراءة الملفات على نوع الملف الذي استخدمته. كما هو موضّح في النظرة العامة، يمكن أن تكون ملفات البيانات الموسّعة أي نوع من الملفات التي تريدها، ولكن تتم إعادة تسميتها باستخدام تنسيق معيّن لاسم الملف ويتم حفظها في <shared-storage>/Android/obb/<package-name>/.
بغض النظر عن طريقة قراءة ملفاتك، عليك دائمًا التأكّد أولاً من أنّ وحدة التخزين الخارجية متاحة للقراءة. من المحتمل أنّ المستخدم قد ربط مساحة التخزين بجهاز كمبيوتر عبر USB أو أنّه أزال بطاقة SD.
ملاحظة: عند بدء تشغيل تطبيقك، عليك دائمًا التحقّق مما إذا كانت مساحة التخزين الخارجية متاحة ويمكن قراءتها من خلال استدعاء getExternalStorageState(). تعرض هذه السمة إحدى السلاسل المحتملة التي تمثّل حالة وحدة التخزين الخارجية. ولكي يتمكّن تطبيقك من قراءة القيمة المعروضة، يجب أن تكون MEDIA_MOUNTED.
الحصول على أسماء الملفات
كما هو موضّح في النظرة العامة، يتم حفظ ملفات البيانات الموسّعة لحزمة APK باستخدام تنسيق معيّن لاسم الملف:
[main|patch].<expansion-version>.<package-name>.obb
للحصول على موقع وأسماء ملفات البيانات الموسّعة، عليك استخدام الطريقتَين getExternalStorageDirectory() وgetPackageName() لإنشاء مسار ملفاتك.
في ما يلي طريقة يمكنك استخدامها في تطبيقك للحصول على مصفوفة تحتوي على المسار الكامل لكلا ملفَي البيانات الموسّعة:
Kotlin
fun getAPKExpansionFiles(ctx: Context, mainVersion: Int, patchVersion: Int): Array<String> { val packageName = ctx.packageName val ret = mutableListOf<String>() if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { // Build the full path to the app's expansion files val root = Environment.getExternalStorageDirectory() val expPath = File(root.toString() + EXP_PATH + packageName) // Check that expansion file path exists if (expPath.exists()) { if (mainVersion > 0) { val strMainPath = "$expPath${File.separator}main.$mainVersion.$packageName.obb" val main = File(strMainPath) if (main.isFile) { ret += strMainPath } } if (patchVersion > 0) { val strPatchPath = "$expPath${File.separator}patch.$mainVersion.$packageName.obb" val main = File(strPatchPath) if (main.isFile) { ret += strPatchPath } } } } return ret.toTypedArray() }
Java
// The shared path to all app expansion files private final static String EXP_PATH = "/Android/obb/"; static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) { String packageName = ctx.getPackageName(); Vector<String> ret = new Vector<String>(); if (Environment.getExternalStorageState() .equals(Environment.MEDIA_MOUNTED)) { // Build the full path to the app's expansion files File root = Environment.getExternalStorageDirectory(); File expPath = new File(root.toString() + EXP_PATH + packageName); // Check that expansion file path exists if (expPath.exists()) { if ( mainVersion > 0 ) { String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb"; File main = new File(strMainPath); if ( main.isFile() ) { ret.add(strMainPath); } } if ( patchVersion > 0 ) { String strPatchPath = expPath + File.separator + "patch." + mainVersion + "." + packageName + ".obb"; File main = new File(strPatchPath); if ( main.isFile() ) { ret.add(strPatchPath); } } } } String[] retArray = new String[ret.size()]; ret.toArray(retArray); return retArray; }
يمكنك استدعاء هذا الإجراء من خلال تمرير Context لتطبيقك ورقم إصدار ملف البيانات الموسّعة المطلوب.
هناك العديد من الطرق التي يمكنك من خلالها تحديد رقم إصدار ملف البيانات الموسّعة. إحدى الطرق البسيطة هي حفظ الإصدار في ملف SharedPreferences عند بدء التنزيل، وذلك من خلال طلب البحث عن اسم ملف البيانات الموسّعة باستخدام طريقة getExpansionFileName(int index) في الفئة APKExpansionPolicy. يمكنك بعد ذلك الحصول على رمز الإصدار من خلال قراءة ملف SharedPreferences عندما تريد الوصول إلى ملف البيانات الموسّعة.
لمزيد من المعلومات حول القراءة من مساحة التخزين المشتركة، يُرجى الاطّلاع على مستندات تخزين البيانات.
استخدام مكتبة Zip لملفات بيانات APK الموسّعة
تتضمّن حزمة بيانات APK الموسّعة في Google Market مكتبة تُسمى "مكتبة ZIP لبيانات APK الموسّعة" (الموجودة في <sdk>/extras/google/google_market_apk_expansion/zip_file/). هذه المكتبة اختيارية وتساعدك في قراءة ملفات البيانات الموسّعة عند حفظها كملفات ZIP. يتيح لك استخدام هذه المكتبة قراءة الموارد بسهولة من ملفات ZIP الموسّعة كنظام ملفات افتراضي.
تتضمّن مكتبة Zip لتوسيع حِزمة APK الفئات وواجهات برمجة التطبيقات التالية:
APKExpansionSupport- توفّر بعض الطرق للوصول إلى أسماء ملفات البيانات الموسّعة وملفات ZIP:
getAPKExpansionFiles()- الطريقة نفسها الموضّحة أعلاه التي تعرض مسار الملف الكامل لكل من ملفَي البيانات الموسّعة.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)- تعرض هذه السمة
ZipResourceFileيمثّل مجموع كل من الملف الرئيسي وملف اختياري. أي أنّه في حال تحديد كل منmainVersionوpatchVersion، سيتم عرضZipResourceFileيتيح إذن الوصول للقراءة إلى كل البيانات، مع دمج بيانات ملف التصحيح فوق الملف الرئيسي.
ZipResourceFile- يمثّل ملف ZIP على وحدة التخزين المشتركة وينفّذ جميع العمليات اللازمة لتوفير نظام ملفات افتراضي استنادًا إلى ملفات ZIP. يمكنك الحصول على مثيل باستخدام
APKExpansionSupport.getAPKExpansionZipFile()أو باستخدامZipResourceFileمن خلال تمرير مسار ملف البيانات الموسّعة إليه. يتضمّن هذا الصف مجموعة متنوّعة من الطرق المفيدة، ولكنّك لن تحتاج عادةً إلى الوصول إلى معظمها. في ما يلي طريقتان مهمّتان:getInputStream(String assetPath)
توفّر -
InputStreamلقراءة ملف داخل ملف ZIP. يجب أن يكونassetPathهو المسار إلى الملف المطلوب، نسبةً إلى جذر محتوى ملف ZIP. getAssetFileDescriptor(String assetPath)- توفّر
AssetFileDescriptorلملف داخل ملف ZIP. يجب أن يكونassetPathهو مسار الملف المطلوب، نسبةً إلى جذر محتوى ملف ZIP. ويكون ذلك مفيدًا لبعض واجهات برمجة تطبيقات Android التي تتطلّبAssetFileDescriptor، مثل بعض واجهات برمجة تطبيقاتMediaPlayer.
APEZProvider- لا تحتاج معظم التطبيقات إلى استخدام هذه الفئة. يحدّد هذا الصف
ContentProviderالذي ينظّم البيانات من ملفات ZIP من خلال موفّر محتوىUriمن أجل توفير إمكانية الوصول إلى الملفات لبعض واجهات برمجة التطبيقات في Android التي تتوقّع إذن الوصولUriإلى ملفات الوسائط. على سبيل المثال، يكون ذلك مفيدًا إذا أردت تشغيل فيديو باستخدامVideoView.setVideoURI().
تخطّي ضغط ملفات الوسائط بتنسيق ZIP
إذا كنت تستخدم ملفات البيانات الموسّعة لتخزين ملفات الوسائط، سيظل بإمكانك استخدام ملف ZIP
لإجراء طلبات تشغيل وسائط Android التي توفّر عناصر تحكّم في الإزاحة والمدة (مثل MediaPlayer.setDataSource() وSoundPool.load()). ولكي ينجح ذلك، يجب عدم إجراء ضغط إضافي على ملفات الوسائط عند إنشاء حِزم ZIP. على سبيل المثال، عند استخدام الأداة zip، يجب استخدام الخيار -n
لتحديد لاحقات الملفات التي يجب عدم ضغطها:
zip -n .mp4;.ogg main_expansion media_files
القراءة من ملف ZIP
عند استخدام مكتبة ZIP لملفات بيانات APK الموسّعة، تتطلّب قراءة ملف من ملف ZIP عادةً ما يلي:
Kotlin
// Get a ZipResourceFile representing a merger of both the main and patch files val expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion) // Get an input stream for a known file inside the expansion file ZIPs expansionFile.getInputStream(pathToFileInsideZip).use { ... }
Java
// Get a ZipResourceFile representing a merger of both the main and patch files ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion); // Get an input stream for a known file inside the expansion file ZIPs InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
يوفّر الرمز البرمجي أعلاه إمكانية الوصول إلى أي ملف متوفّر في ملف البيانات الموسّعة الأساسي أو ملف البيانات الموسّعة الاختياري، وذلك من خلال القراءة من خريطة مدمجة لجميع الملفات من كلا الملفين. كل ما عليك تقديمه لطريقة getAPKExpansionFile() هو android.content.Context لتطبيقك ورقم الإصدار لكل من ملف البيانات الموسّعة الرئيسي وملف البيانات الموسّعة الخاص بالتصحيح.
إذا كنت تفضّل القراءة من ملف بيانات موسّعة معيّن، يمكنك استخدام الدالة الإنشائية ZipResourceFile مع المسار إلى ملف البيانات الموسّعة المطلوب:
Kotlin
// Get a ZipResourceFile representing a specific expansion file val expansionFile = ZipResourceFile(filePathToMyZip) // Get an input stream for a known file inside the expansion file ZIPs expansionFile.getInputStream(pathToFileInsideZip).use { ... }
Java
// Get a ZipResourceFile representing a specific expansion file ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip); // Get an input stream for a known file inside the expansion file ZIPs InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
لمزيد من المعلومات حول استخدام هذه المكتبة لملفات البيانات الموسّعة، اطّلِع على فئة SampleDownloaderActivity في التطبيق النموذجي، والتي تتضمّن رمزًا إضافيًا للتحقّق من الملفات التي تم تنزيلها باستخدام CRC. يُرجى العِلم أنّه في حال استخدام هذا النموذج كأساس للتنفيذ، يجب التعريف بحجم البايت لملفات البيانات الموسّعة في مصفوفة xAPKS.
اختبار ملفات البيانات الموسّعة
قبل نشر تطبيقك، هناك أمران عليك اختبارهما، وهما: قراءة ملفات البيانات الموسّعة وتنزيل الملفات.
اختبار قراءة الملفات
قبل تحميل تطبيقك إلى Google Play، عليك اختبار قدرة تطبيقك على قراءة الملفات من مساحة التخزين المشتركة. كل ما عليك فعله هو إضافة الملفات إلى الموقع المناسب في مساحة التخزين المشتركة على الجهاز وتشغيل تطبيقك:
- على جهازك، أنشئ الدليل المناسب في مساحة التخزين المشتركة التي سيحفظ فيها Google Play ملفاتك.
على سبيل المثال، إذا كان اسم الحزمة هو
com.example.android، عليك إنشاء الدليلAndroid/obb/com.example.android/في مساحة التخزين المشتركة. (وصِّل جهاز الاختبار بجهاز الكمبيوتر لتثبيت وحدة التخزين المشتركة وإنشاء هذا الدليل يدويًا). - أضِف ملفات البيانات الموسّعة يدويًا إلى هذا الدليل. احرص على إعادة تسمية ملفاتك لتتوافق مع تنسيق اسم الملف الذي سيستخدمه Google Play.
على سبيل المثال، بغض النظر عن نوع الملف، يجب أن يكون ملف البيانات الموسّعة الأساسي لتطبيق
com.example.androidهوmain.0300110.com.example.android.obb. يمكن أن يكون رمز الإصدار أي قيمة تريدها. تذكَّر ما يلي:- يبدأ ملف البيانات الموسّعة الأساسي دائمًا بـ
mainويبدأ ملف اختياري بـpatch. - يتطابق اسم الحزمة دائمًا مع اسم حزمة APK التي تم إرفاق الملف بها على Google Play.
- يبدأ ملف البيانات الموسّعة الأساسي دائمًا بـ
- بعد أن أصبحت ملفات البيانات الموسّعة متوفّرة على الجهاز، يمكنك تثبيت تطبيقك وتشغيله لاختبار ملفات البيانات الموسّعة.
في ما يلي بعض التذكيرات حول التعامل مع ملفات البيانات الموسّعة:
- لا تحذف ملفات البيانات الموسّعة
.obbأو تعِد تسميتها (حتى إذا فككت حزمة البيانات إلى موقع مختلف). سيؤدي ذلك إلى أن يعيد Google Play (أو تطبيقك نفسه) تنزيل ملف البيانات الموسّعة بشكل متكرّر. - لا تحفظ بيانات أخرى في
obb/دليلك. إذا كان عليك فك حزمة بعض البيانات، احفظها في الموقع الجغرافي الذي تحدّدهgetExternalFilesDir().
اختبار عمليات تنزيل الملفات
بما أنّ تطبيقك يحتاج أحيانًا إلى تنزيل ملفات البيانات الموسّعة يدويًا عند فتحه للمرة الأولى، من المهم اختبار هذه العملية للتأكّد من أنّ تطبيقك يمكنه طلب عناوين URL بنجاح وتنزيل الملفات وحفظها على الجهاز.
لاختبار تنفيذ تطبيقك لإجراء التنزيل اليدوي، يمكنك نشره في مسار الاختبار الداخلي ليكون متاحًا للمختبِرين المعتمَدين فقط. إذا سارت الأمور على النحو المتوقّع، من المفترض أن يبدأ تطبيقك في تنزيل ملفات البيانات الموسّعة فور بدء النشاط الرئيسي.
ملاحظة: كان بإمكانك في السابق اختبار تطبيق من خلال تحميل إصدار "مسودة" غير منشور. لم تعُد هذه الوظيفة متاحة. بدلاً من ذلك، عليك نشره في مسار اختبار داخلي أو مغلق أو مفتوح. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة لم يعُد بإمكانك استخدام التطبيقات التجريبية.
تحديث تطبيقك
من المزايا الرائعة لاستخدام ملفات البيانات الموسّعة على Google Play إمكانية تحديث تطبيقك بدون إعادة تنزيل جميع مواد العرض الأصلية. بما أنّ Google Play يتيح لك توفير ملفَي بيانات موسّعة مع كل حزمة APK، يمكنك استخدام الملف الثاني كـ "حزمة تصحيح" توفّر التحديثات والأصول الجديدة. ويؤدي ذلك إلى تجنُّب الحاجة إلى إعادة تنزيل ملف البيانات الموسّعة الرئيسي الذي قد يكون كبيرًا ومكلفًا للمستخدمين.
ملف البيانات الموسّعة الاختياري هو نفسه ملف البيانات الموسّعة الأساسي من الناحية الفنية، ولا يجري نظام التشغيل Android ولا Google Play أي عملية تصحيح فعلية بين ملفات البيانات الموسّعة الأساسية وملفات البيانات الموسّعة الاختيارية. يجب أن ينفّذ رمز تطبيقك أي تصحيحات ضرورية بنفسه.
إذا كنت تستخدم ملفات ZIP كملفات بيانات موسّعة، فإنّ مكتبة APK Expansion Zip المضمّنة في حزمة Apk Expansion تتضمّن إمكانية دمج ملف اختياري مع ملف البيانات الموسّعة الأساسي.
ملاحظة: حتى إذا كنت بحاجة إلى إجراء تغييرات على ملف حزمة التصحيح فقط، يجب تعديل حزمة APK ليتمكّن Google Play من إجراء التحديث.
إذا لم تكن بحاجة إلى إجراء تغييرات على الرمز في التطبيق، ما عليك سوى تعديل versionCode في ملف البيان.
طالما أنّك لم تغيّر ملف البيانات الموسّعة الرئيسي المرتبط بحزمة APK في Play Console، لن يضطر المستخدمون الذين سبق لهم تثبيت تطبيقك إلى تنزيل ملف البيانات الموسّعة الرئيسي. يتلقّى المستخدمون الحاليون حزمة APK المعدَّلة وملف البيانات الموسّعة الجديد فقط (مع الاحتفاظ بملف البيانات الموسّعة الرئيسي السابق).
في ما يلي بعض المشاكل التي يجب وضعها في الاعتبار بشأن تحديثات ملفات البيانات الموسّعة:
- يمكن أن يتضمّن تطبيقك ملفَي بيانات موسّعة فقط في كل مرة. ملف بيانات موسّعة رئيسي واحد وملف بيانات موسّعة اختياري واحد أثناء تحديث ملف، يحذف Google Play الإصدار السابق (ويجب أن يفعل تطبيقك ذلك عند إجراء التحديثات يدويًا).
- عند إضافة ملف البيانات الموسّعة الاختياري، لا يعدّل نظام التشغيل Android تطبيقك أو ملف البيانات الموسّعة الأساسي. يجب تصميم تطبيقك ليتوافق مع بيانات التصحيح. ومع ذلك، تتضمّن حزمة بيانات APK الموسّعة مكتبة لاستخدام ملفات ZIP كملفات بيانات موسّعة، ما يدمج البيانات من ملف اختياري في ملف البيانات الموسّعة الأساسي حتى تتمكّن من قراءة جميع بيانات ملف البيانات الموسّعة بسهولة.