يتطلّب Google Play ألا يزيد حجم حزمة APK المضغوطة التي ينزّلها المستخدمون عن 100 ميغابايت. بالنسبة إلى معظم التطبيقات، هذه مساحة كبيرة لجميع رموز التطبيق ومواد العرض. ومع ذلك، تحتاج بعض التطبيقات إلى مساحة أكبر لعرض الرسومات أو ملفات الوسائط أو مواد العرض الكبيرة الأخرى العالية الدقة. في السابق، إذا كان حجم تنزيل تطبيقك المضغوط يتجاوز 100 ميغابايت، كان عليك استضافة الموارد الإضافية وتنزيلها بنفسك عندما يفتح المستخدم التطبيق. وقد يكون استضافة الملفات الإضافية وعرضها مكلفًا، وغالبًا ما تكون تجربة المستخدم أقل من مثالية. لتسهيل هذه العملية عليك وجعلها أكثر ملاءمةً للمستخدمين، يتيح لك Google Play إرفاق ملفي بيانات موسّعة كبيرين يكملان حزمة APK.
يستضيف Google Play ملفات البيانات الموسّعة لتطبيقك ويعرضها على الجهاز بدون أي تكلفة. يتم حفظ ملفات البيانات الموسّعة في مساحة التخزين المشتركة على الجهاز (بطاقة SD أو قسم قابل للتركيب على USB، ويُعرف أيضًا باسم مساحة التخزين "الخارجية") حيث يمكن لتطبيقك الوصول إليها. على معظم الأجهزة، ينزِّل Google Play ملفات البيانات الموسّعة في الوقت نفسه الذي ينزِّل فيه حزمة APK، ما يضمن أن يتضمّن تطبيقك كل ما يحتاجه عند فتحه للمرة الأولى. في بعض الحالات، يجب أن ينزِّل تطبيقك الملفات من Google Play عند بدء تشغيله.
إذا كنت تريد تجنُّب استخدام ملفات البيانات الموسّعة وكان حجم تنزيل تطبيقك المضغوط أكبر من 100 ميغابايت، عليك تحميل تطبيقك بدلاً من ذلك باستخدام حِزم تطبيق Android التي تسمح بحجم تنزيل مضغوط يصل إلى 200 ميغابايت. بالإضافة إلى ذلك، بما أنّ استخدام حِزم التطبيقات يؤجل إنشاء حِزم 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()
.
لا يتضمّن هذا الدليل أكثر من ملفي بيانات موسّعة لكل تطبيق.
أحدهما هو ملف البيانات الموسّعة الأساسي والآخر هو ملف البيانات الموسّعة لإصلاح الأخطاء (إذا لزم الأمر). يتم استبدال الإصدارات السابقة عند تحديث تطبيقك باستخدام ملفات بيانات موسّعة جديدة. منذ الإصدار Android
4.4 (المستوى 19 من واجهة برمجة التطبيقات)، يمكن للتطبيقات قراءة OBB
ملفات البيانات الموسّعة بدون إذن الوصول إلى التخزين الخارجي. ومع ذلك، لا تزال بعض عمليات تنفيذ Android 6.0 (المستوى 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()
. ومع ذلك،
من الأفضل استخدام تنسيق ملف بيانات موسّعة يتيح لك القراءة مباشرةً من
الملف بدلاً من مطالبتك بفك ضغط البيانات. على سبيل المثال، قدّمنا مشروع مكتبة
يُسمى APK Expansion Zip Library الذي يقرأ بياناتك مباشرةً
من ملف 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 فقط.
- عندما يشغّل المستخدم تطبيقك، يجب أن يتحقّق تطبيقك مما إذا كانت ملفات البيانات الموسّعة
قد سبق حفظها على الجهاز.
- إذا كان الأمر كذلك، يعني ذلك أنّ تطبيقك جاهز للنشر.
- إذا لم يكن الأمر كذلك، يجب أن ينزِّل تطبيقك ملفات البيانات الموسّعة من Google Play عبر بروتوكول HTTP. يجب أن يُرسِل تطبيقك طلبًا إلى برنامج Google Play باستخدام خدمة ترخيص التطبيقات في Google Play، والتي تردّ عليه بالاسم وحجم الملف وعنوان URL لكل ملف بيانات موسّعة. باستخدام هذه المعلومات، يمكنك بعد ذلك تنزيل الملفات وحفظها في مكان التخزين المناسب.
تحذير: من المهم تضمين الرمز البرمجي اللازم لتنزيل ملفات البيانات الموسّعة من Google Play في حال عدم توفّر الملفات على الجهاز عند بدء تشغيل تطبيقك. كما هو موضّح في القسم التالي حول تنزيل ملفات البيانات الموسّعة، وفّرنا لك مكتبة تُبسّط هذه العملية بشكل كبير وتُجري عملية التنزيل من خدمة باستخدام الحد الأدنى من الرمز البرمجي.
قائمة التحقّق من التطوير
في ما يلي ملخّص للمهام التي يجب تنفيذها لاستخدام ملفات البيانات الموسّعة مع تطبيقك:
- حدِّد أولاً ما إذا كان حجم تنزيل تطبيقك المضغوط يجب أن يكون أكبر من 100 ميغابايت. إنّ المساحة قيّمة ويجب إبقاء إجمالي حجم التنزيل صغيرًا قدر الإمكان. إذا كان تطبيقك يستخدم أكثر من 100 ميغابايت لتوفير إصدارات متعددة من مواد عرض الرسومات لكثافات شاشة متعدّدة، ننصحك بدلاً من ذلك بنشر حِزم APK متعددة لا تحتوي كل حزمة APK سوى على مواد العرض المطلوبة للشاشات التي تستهدفها. للحصول على أفضل النتائج عند النشر على Google Play، حمِّل مجموعة حزمات تطبيق Android التي تضم كل الرموز البرمجية والموارد المجمّعة لتطبيقك، ولكن تُؤجل إنشاء حزمة APK وتوقيعها إلى Google Play.
- حدِّد موارد التطبيق التي تريد فصلها عن حِزمة APK وحزِّمها في ملف
لاستخدامه كملف البيانات الموسّعة الرئيسي.
في العادة، يجب عدم استخدام ملف البيانات الموسّعة الثاني إلّا عند إجراء تعديلات على ملف البيانات الموسّعة الأساسي. ومع ذلك، إذا تجاوزت مواردك الحد الأقصى الذي يبلغ 2 غيغابايت لملف التوسيع الرئيسي، يمكنك استخدام ملف التعديل لبقية مواد العرض.
- طوِّر تطبيقك بحيث يستخدم الموارد من ملفات البيانات الموسّعة في
موقع مساحة التخزين المشتركة على الجهاز.
يجب عدم حذف ملفات البيانات الموسّعة أو نقلها أو إعادة تسميتها.
إذا لم يكن تطبيقك يتطلّب تنسيقًا معيّنًا، ننصحك بإنشاء ملفات ZIP لملفّات البيانات الموسّعة، ثم قراءتها باستخدام مكتبة APK Expansion Zip Library.
- أضِف منطقًا إلى النشاط الرئيسي لتطبيقك للتحقّق مما إذا كانت ملفات البيانات الموسّعة
متوفّرة على الجهاز عند بدء التشغيل. إذا لم تكن الملفات متوفّرة على الجهاز، استخدِم خدمة ترخيص التطبيقات في Google Play لطلب عناوين URL
لملفّات البيانات الموسّعة، ثم نزِّلها واحفظها.
لتقليل كمية الرموز البرمجية التي يجب كتابتها بشكل كبير وضمان تجربة مستخدم جيدة أثناء التنزيل، ننصحك باستخدام مكتبة أداة التنزيل لتنفيذ سلوك التنزيل.
إذا أنشأت خدمة تنزيل خاصة بك بدلاً من استخدام المكتبة، يجب عدم تغيير اسم ملفات البيانات الموسّعة وحفظها في مكان التخزين المناسب.
بعد الانتهاء من تطوير تطبيقك، اتّبِع الدليل الخاص باختبار ملفات البيانات الموسّعة.
القواعد والقيود
تتوفّر ميزة إضافة ملفات البيانات الموسّعة لحِزم APK عند تحميل تطبيقك باستخدام Play Console. عند تحميل تطبيقك للمرة الأولى أو تعديل تطبيق يستخدم ملفات البيانات الموسّعة، يجب مراعاة القواعد والقيود التالية:
- يجب ألا يزيد حجم كل ملف بيانات موسّعة عن 2 غيغابايت.
- لتنزيل ملفات البيانات الموسّعة من Google Play، يجب أن يكون لدى المستخدم تطبيقك من Google Play. لن يقدّم Google Play عناوين URL لملفات البيانات الموسّعة إذا تم تثبيت التطبيق بوسائل أخرى.
- عند إجراء عملية التنزيل من داخل تطبيقك، يكون عنوان URL الذي يوفّره Google Play لكل ملف فريدًا لكل عملية تنزيل، وتنتهي صلاحيته بعد فترة قصيرة من تقديمه لتطبيقك.
- إذا عدّلت تطبيقك باستخدام حزمة 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 لملفاتك الموسّعة فريدًا لكل عملية تنزيل، وتنتهي صلاحيته بعد فترة قصيرة من منحه لتطبيقك.
إذا كان تطبيقك مجانيًا (وليس مدفوعًا)، من المحتمل أنّك لم تستخدم خدمة ترخيص التطبيقات. تم تصميم هذه الميزة primarily لمساعدتك في فرض سياسات الترخيص لتطبيقك والتأكّد من أنّ المستخدم يملك الحق في استخدام تطبيقك (لأنّه دفع ثمنه على Google Play). لتسهيل وظائفملف التمديد، تم تحسين خدمة الترخيص لتقديم استجابة لتطبيقك تتضمّن عنوان URL لملفات التمديد الخاصة بتطبيقك والتي يتم استضافتها على Google Play. وبالتالي، حتى إذا كان تطبيقك مجانيًا للمستخدمين، عليك تضمين مكتبة License Verification Library (LVL) لاستخدام ملفات بيانات APK الموسّعة. بالطبع، إذا كان تطبيقك مجانيًا، لن تحتاج إلى فرض التحقّق من الترخيص، ما عليك سوى استخدام مكتبة لتنفيذ الطلب الذي يعرض عنوان URL لملفات البيانات الموسّعة.
ملاحظة: سواء كان تطبيقك مجانيًا أم لا، لن يعرض Google Playعناوين URL لملفات البيانات الموسّعة إلا إذا حصل المستخدم على تطبيقك من Google Play.
بالإضافة إلى LVL، تحتاج إلى مجموعة من الرموز البرمجية التي تنزِّل ملفات البيانات الموسّعة من خلال اتصال HTTP وتحفظها في المكان المناسب على مساحة التخزين المشتركة للجهاز. أثناء إنشاء هذه العملية في تطبيقك، هناك عدة مشاكل يجب أخذها في الاعتبار:
- قد لا يتوفّر في الجهاز مساحة كافية لملفات البيانات الموسّعة، لذا عليك التحقّق من ذلك قبل بدء عملية التنزيل وتحذير المستخدم في حال عدم توفّر مساحة كافية.
- يجب أن تتم عمليات تنزيل الملفات في خدمة تعمل في الخلفية لتجنُّب حظر تفاعل المستخدِم مع التطبيق والسماح له بمغادرته أثناء اكتمال عملية التنزيل.
- قد تحدث مجموعة متنوعة من الأخطاء أثناء الطلب والتنزيل، ويجب التعامل معها بطريقة سلسة.
- يمكن أن يتغيّر الاتصال بالشبكة أثناء عملية التنزيل، لذا عليك التعامل مع هذه التغييرات وإذا انقطعت عملية التنزيل، عليك استئنافها متى أمكن.
- أثناء عملية التنزيل في الخلفية، يجب تقديم إشعار يشير إلى مستوى تقدّم التنزيل ويُعلم المستخدم عند اكتماله ويعيده إلى تطبيقك عند اختياره.
لتبسيط هذا العمل عليك، أنشأنا مكتبة أداة التنزيل التي تطلب عناوين URL لملفات البيانات الموسّعة من خلال خدمة الترخيص، وتنزّل ملفات البيانات الموسّعة، وتنفّذ جميع المهام المذكورة أعلاه، وتسمح أيضًا بتوقيف نشاطك مؤقتًا واستئناف عملية التنزيل. من خلال إضافة مكتبة Downloader وبعض عناصر الربط البرمجي إلى تطبيقك، يتم ترميز كل المهام تقريبًا المتعلّقة بتنزيل ملفات البيانات الموسّعة نيابةً عنك. وبالتالي، لتوفير أفضل تجربة للمستخدمين بأقل جهد ممكن، ننصحك باستخدام مكتبة أداة التنزيل لتحميلملفّات البيانات الموسّعة. توضّح المعلومات الواردة في الأقسام التالية كيفية دمج المكتبة في تطبيقك.
إذا كنت تفضّل تطوير حلّك الخاص لتنزيل ملفات البيانات الموسّعة باستخدام عناوين URL في Google
Play، عليك اتّباع مستندات ترخيص
التطبيقات لتقديم طلب ترخيص، ثم استرداد أسماء ملفات البيانات الموسّعة ومقاساتها وعناوين URL الخاصة بها من البيانات الإضافية في الاستجابة. يجب استخدام فئة APKExpansionPolicy
(المضمّنة في مكتبة التحقّق من الترخيص) كسياسة
الترخيص التي تسجّل أسماء ملفات البيانات الموسّعة وأحجامها وعناوين URL الخاصة بها من خدمة الترخيص.
لمحة عن مكتبة أداة التنزيل
لاستخدام ملفات توسيع APK مع تطبيقك وتقديم أفضل تجربة للمستخدمين بأقل جهد من جانبك، ننصحك باستخدام "مكتبة التنزيل" المضمّنة في حزمة "مكتبة توسيع حِزم APK من Google Play". تعمل هذه المكتبة على تنزيل ملفات البيانات الموسّعة في خدمة تعمل في الخلفية، وتعرض إشعارًا للمستخدم يتضمن حالة التنزيل، وتتعامل مع حالات فقدان اتصال بالشبكة، واستئناف التنزيل عندما يكون ذلك ممكنًا، وغير ذلك.
لتنفيذ عمليات تنزيل ملفات البيانات الموسّعة باستخدام مكتبة Downloader Library، ما عليك سوى اتّباع الخطوات التالية:
- يمكنك توسيع فئة فرعية خاصة من فئة
Service
وفئة فرعية خاصة من فئةBroadcastReceiver
لا تتطلّب كلّ منهما سوى بضع سطور من الرموز البرمجية. - أضِف بعض المنطق إلى نشاطك الرئيسي للتحقّق مما إذا سبق أن تم تنزيل ملفات البيانات الموسّعة، وإذا لم يكن الأمر كذلك، يمكنك بدء عملية التنزيل وعرض واجهة مستخدم تعرِض مستوى التقدّم.
- نفِّذ واجهة طلب معاودة الاتصال مع بضع طرق في نشاطك الرئيسي التي تتلقّى آخر المعلومات عن مستوى تقدّم التنزيل.
توضِّح الأقسام التالية كيفية إعداد تطبيقك باستخدام "مكتبة أداة التنزيل".
الاستعداد لاستخدام مكتبة أداة التنزيل
لاستخدام مكتبة Downloader، عليك تنزيل حِزمتَين من "مدير حِزم تطوير البرامج (SDK)" وإضافة المكتبات المناسبة إلى تطبيقك.
أولاً، افتح Android SDK Manager (الأدوات > أداة إدارة حِزم تطوير البرامج (SDK))، وضمن المظهر والسلوك > إعدادات النظام > حزمة تطوير البرامج (SDK) لنظام التشغيل Android، انقر على علامة التبويب أدوات حِزم تطوير البرامج (SDK) لاختيار ما يلي وتنزيله:
- حزمة "مكتبة الترخيص" في Google Play
- حزمة مكتبة "ملحقات حِزم APK" من Google Play
أنشئ وحدة مكتبة جديدة لكلّ من مكتبة التحقّق من الترخيص ومكتبة أداة التنزيل. لكل مكتبة:
- انقر على ملف > جديد > وحدة جديدة.
- في نافذة إنشاء وحدة جديدة، اختَر مكتبة Android، ثمّ انقر على التالي.
- حدِّد اسم التطبيق/المكتبة، مثل "مكتبة تراخيص Google Play" و "مكتبة أداة تنزيل Google Play"، ثم اختَر الحد الأدنى لمستوى حزمة SDK، ثم انقر على إنهاء.
- انقر على ملف > بنية المشروع.
- انقر على علامة التبويب الخصائص، وفي مكتبة
مستودع، أدخِل المكتبة من الدليل
<sdk>/extras/google/
(play_licensing/
لمكتبة التحقّق من الترخيص أوplay_apk_expansion/downloader_library/
لمكتبة أداة التنزيل). - انقر على حسنًا لإنشاء الوحدة الجديدة.
ملاحظة: تعتمد مكتبة Downloader Library على مكتبة License Verification Library. احرص على إضافة مكتبة التحقّق من الترخيص إلى سمات مشروع "مكتبة التنزيل".
أو يمكنك تعديل مشروعك من سطر الأوامر لتضمين المكتبات:
- غيِّر الأدلة إلى الدليل
<sdk>/tools/
. - نفِّذ
android update project
مع الخيار--library
لإضافة كل من LVL و"مكتبة أداة التنزيل" إلى مشروعك. مثلاً:android update project --path ~/Android/MyApp \ --library ~/android_sdk/extras/google/market_licensing \ --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
بعد إضافة كلّ من مكتبة التحقّق من الترخيص ومكتبة أداة التنزيل إلى تطبيقك، ستتمكّن من دمج إمكانية تنزيل ملفات البيانات الموسّعة من Google Play بسرعة. إنّ التنسيق الذي تختاره لملفات البيانات الموسّعة وطريقة قراءتها من مساحة التخزين المشتركة هي عملية تنفيذ منفصلة يجب مراعاتها استنادًا إلى احتياجات تطبيقك.
ملاحظة: تتضمّن حزمة Apk Expansion نموذجًا لتطبيق يوضّح كيفية استخدام مكتبة Downloader Library في أحد التطبيقات. يستخدم النموذج مكتبة ثالثة متوفّرة في حزمة Apk Expansion تُسمى APK Expansion Zip Library. إذا كان لديك نية استخدام ملفات ZIP لملفات البيانات الموسّعة، ننصحك أيضًا بإضافة مكتبة حِزم ZIP لتوسيع حِزم APK إلى تطبيقك. لمزيد من المعلومات، يُرجى الاطّلاع على القسم أدناه المتعلقباستخدام مكتبة حِزم ZIP لتوسيع حِزم APK.
الإفصاح عن أذونات المستخدمين
لتنزيل ملفات البيانات الموسّعة، تتطلّب مكتبة Downloader Library عدة أذونات يجب الإفصاح عنها في ملف بيان تطبيقك. وهي تشمل ما يلي:
<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 تلقائيًا مستوى 4 من واجهة برمجة التطبيقات، ولكن تتطلّب مكتبة APK Expansion Zip Library المستوى 5 من واجهة برمجة التطبيقات.
تنفيذ خدمة التنزيل
لتنفيذ عمليات التنزيل في الخلفية، توفّر مكتبة Downloader Library
فئتها الفرعية الخاصة بها من Service
والتي تُسمى DownloaderService
ويجب تمديدها. بالإضافة إلى تنزيل ملفات البيانات الموسّعة نيابةً عنك، تؤدي DownloaderService
أيضًا إلى ما يلي:
- يُسجِّل
BroadcastReceiver
الذي يستمع إلى التغييرات في الاتصال بالشبكة على الجهاز (بثCONNECTIVITY_ACTION
) من أجل إيقاف عملية التنزيل مؤقتًا عند الضرورة (مثلاً بسبب فقدان الاتصال) واستئناف عملية التنزيل عندما يكون ذلك ممكنًا (عند اكتمال الاتصال). - جدولة منبّه
RTC_WAKEUP
لإعادة محاولة التنزيل في الحالات التي يتم فيها إيقاف الخدمة - إنشاء
Notification
مخصّص يعرض مستوى تقدّم التنزيل وأي أخطاء أو تغييرات في الحالة - السماح لتطبيقك بتوقيف عملية التنزيل مؤقتًا واستئنافها يدويًا
- للتأكّد من أنّ مساحة التخزين المشتركة تم تركيبها وأصبحت متاحة، وأنّ الملفات غير متوفّرة، وأنّ هناك مساحة كافية، وكل ذلك قبل تنزيل ملفات البيانات الموسّعة. بعد ذلك، يتم إرسال إشعار إلى المستخدم إذا لم تكن أي من هذه الشروط صحيحة.
ما عليك سوى إنشاء فئة في تطبيقك تُنشئ فئة DownloaderService
وتلغي ثلاث طرق لتقديم تفاصيل معيّنة للتطبيق:
getPublicKey()
- يجب أن يعرض هذا الإجراء سلسلة تمثل المفتاح العام لتشفير RSA بترميز 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
للاتّصال بواجهة برمجة التطبيقات
من مكتبة أداة التنزيل التي تتحقّق من حالة التنزيل وتعيد بدؤه
إذا لزم الأمر.
ما عليك سوى إلغاء طريقة 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 Expansion الطريقة التالية في طريقة
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()
. يعرض هذا الإجراء رقم تعريف المورد لأحد السلاسل المضمّنة في مكتبة أداة التنزيل. على سبيل المثال، تتوافق السلسلة "تم إيقاف التنزيل مؤقتًا لأنّك في وضع التجوال" معSTATE_PAUSED_ROAMING
. onDownloadProgress(DownloadProgressInfo progress)
- تستدعي خدمة التنزيل هذا الإجراء لإرسال عنصر
DownloadProgressInfo
، الذي يصف معلومات مختلفة عن مستوى التقدّم في عملية التنزيل، بما في ذلك الوقت المتبقّي المقدَّر، والسرعة الحالية، والتقدّم العام، والإجمالي حتى تتمكّن من تعديل واجهة مستخدم مستوى التقدّم في عملية التنزيل.
ملاحظة: للحصول على أمثلة على هذه طلبات الاستدعاء التي تعدّل واجهة مستخدم ملف APK الموسّع الذي يعرض مستوى التقدّم في عملية التنزيل، اطّلِع على SampleDownloaderActivity
في نموذج التطبيق المقدَّم مع حزمة APK الموسّعة.
في ما يلي بعض الطرق العامة لواجهة 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
إذا قرّرت إنشاء خدمة تنزيل خاصة بك بدلاً من استخدام مكتبة التنزيل في 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
عندما تريد الوصول إلى ملف التوسيع.
لمزيد من المعلومات عن القراءة من مساحة التخزين المشتركة، يُرجى الاطّلاع على مستندات تخزين البيانات.
استخدام مكتبة حِزم APK المضغوطة
تتضمّن حزمة بيانات APK الموسّعة في Google Market مكتبة تُسمى "مكتبة حزمة APK الموسّعة بتنسيق ZIP" (تقع في <sdk>/extras/google/google_market_apk_expansion/zip_file/
). وهذه مكتبة اختيارية تساعدك في قراءة ملفات البيانات الموسّعة عند حفظها كملفات 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
عند استخدام مكتبة حزمة APK الموسّعة بتنسيق ZIP، تتطلّب قراءة ملف من حزمة 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 Library المضمّنة في حزمة APK Expansion إمكانية دمج ملف التصحيح مع ملف البيانات الموسّعة الرئيسي.
ملاحظة: حتى إذا كنت بحاجة إلى إجراء تغييرات على ملف التصحيح
التوسيع فقط، يجب تعديل حزمة APK حتى يُجري Google Play تحديثًا.
إذا لم تكن بحاجة إلى تغييرات في الرمز البرمجي للتطبيق، ما عليك سوى تعديل versionCode
فيملف
البيان.
طالما أنّك لم تغيّر ملف البيانات الموسّعة الرئيسي المرتبط بحزمة APK في Play Console، لن يقوم المستخدمون الذين ثبَّتوا تطبيقك سابقًا بتنزيل ملف البيانات الموسّعة الرئيسي. لا يتلقّى المستخدمون الحاليون سوى حزمة APK المعدَّلة وملف البيانات الموسّعة الجديد (مع الاحتفاظ بملف البيانات الموسّعة الرئيسي السابق).
في ما يلي بعض المشاكل التي يجب أخذها في الاعتبار بشأن تعديلات ملفات البيانات الموسّعة:
- لا يمكن أن يتضمّن تطبيقك سوى ملفي بيانات موسّعة في الوقت نفسه. ملف بيانات موسّعة أساسي واحد وملف بيانات موسّعة اختياري أثناء تحديث ملف، يحذف Google Play الإصدار السابق (ويجب أن يحذفه تطبيقك أيضًا عند إجراء تعديلات يدوية).
- عند إضافة ملف بيانات موسّعة لإصلاح، لا يُجري نظام Android تصحيحًا في التطبيق أو ملف البيانات الموسّعة الأساسي. يجب تصميم تطبيقك ليتوافق مع بيانات التصحيح. ومع ذلك، تتضمّن حزمة Apk Expansion مكتبة لاستخدام ملفات ZIP بصفتها ملفات بيانات موسّعة، ما يؤدي إلى دمج البيانات من ملف التصحيح في ملف البيانات الموسّعة الرئيسي كي تتمكّن من قراءة جميع بيانات ملف البيانات الموسّعة بسهولة.