فایل های گسترش APK

گوگل پلی الزام می‌کند که فایل APK فشرده‌ای که کاربران دانلود می‌کنند، نباید بیش از ۱۰۰ مگابایت باشد. برای اکثر برنامه‌ها، این فضا برای تمام کدها و فایل‌های برنامه کافی است. با این حال، برخی از برنامه‌ها به فضای بیشتری برای گرافیک‌های با کیفیت بالا، فایل‌های رسانه‌ای یا سایر فایل‌های بزرگ نیاز دارند. پیش از این، اگر حجم دانلود فشرده برنامه شما از ۱۰۰ مگابایت بیشتر می‌شد، مجبور بودید منابع اضافی را خودتان هنگام باز شدن برنامه توسط کاربر، میزبانی و دانلود کنید. میزبانی و ارائه فایل‌های اضافی می‌تواند پرهزینه باشد و تجربه کاربری اغلب ایده‌آل نیست. برای اینکه این فرآیند برای شما آسان‌تر و برای کاربران خوشایندتر باشد، گوگل پلی به شما امکان می‌دهد دو فایل الحاقی بزرگ را که مکمل APK شما هستند، پیوست کنید.

گوگل پلی فایل‌های توسعه برنامه شما را میزبانی می‌کند و آنها را بدون هیچ هزینه‌ای برای شما در اختیار دستگاه قرار می‌دهد. فایل‌های توسعه در محل ذخیره‌سازی مشترک دستگاه (کارت SD یا پارتیشن قابل نصب با USB؛ که به عنوان حافظه "خارجی" نیز شناخته می‌شود) ذخیره می‌شوند، جایی که برنامه شما می‌تواند به آنها دسترسی داشته باشد. در اکثر دستگاه‌ها، گوگل پلی همزمان با دانلود APK، فایل(های) توسعه را نیز دانلود می‌کند، بنابراین برنامه شما هر آنچه را که نیاز دارد، هنگام باز شدن توسط کاربر برای اولین بار، در اختیار دارد. با این حال، در برخی موارد، برنامه شما باید هنگام شروع برنامه، فایل‌ها را از گوگل پلی دانلود کند.

اگر می‌خواهید از استفاده از فایل‌های توسعه‌دهنده اجتناب کنید و حجم دانلود فشرده برنامه شما بیش از ۱۰۰ مگابایت است، باید برنامه خود را با استفاده از Android App Bundles آپلود کنید که امکان دانلود تا حجم فشرده ۵۰۰ مگابایت را فراهم می‌کند. علاوه بر این، از آنجا که استفاده از بسته‌های برنامه، تولید APK و امضای آن در Google Play را به تعویق می‌اندازد، کاربران APKهای بهینه‌شده را فقط با کد و منابعی که برای اجرای برنامه شما نیاز دارند، دانلود می‌کنند. شما نیازی به ساخت، امضا و مدیریت چندین APK یا فایل‌های توسعه‌دهنده ندارید و کاربران دانلودهای کوچک‌تر و بهینه‌تری دریافت می‌کنند.

نمای کلی

هر بار که یک فایل APK را با استفاده از کنسول گوگل پلی آپلود می‌کنید، می‌توانید یک یا دو فایل الحاقی به APK اضافه کنید. هر فایل می‌تواند تا ۲ گیگابایت حجم داشته باشد و می‌تواند هر فرمتی که شما انتخاب می‌کنید باشد، اما توصیه می‌کنیم برای صرفه‌جویی در پهنای باند در طول دانلود، از یک فایل فشرده استفاده کنید. از نظر مفهومی، هر فایل الحاقی نقش متفاوتی ایفا می‌کند:

  • فایل توسعه اصلی ، فایل توسعه اولیه برای منابع اضافی مورد نیاز برنامه شما است.
  • فایل الحاقی پچ اختیاری است و برای به‌روزرسانی‌های کوچک در فایل الحاقی اصلی در نظر گرفته شده است.

اگرچه می‌توانید از دو فایل الحاقی به هر روشی که مایلید استفاده کنید، توصیه می‌کنیم فایل الحاقی اصلی، دارایی‌های اولیه را ارائه دهد و به ندرت یا هرگز به‌روزرسانی شود؛ فایل الحاقی پچ باید کوچک‌تر باشد و به عنوان «حامل پچ» عمل کند و با هر انتشار اصلی یا در صورت لزوم به‌روزرسانی شود.

با این حال، حتی اگر به‌روزرسانی برنامه شما فقط به یک فایل توسعه پچ جدید نیاز داشته باشد، هنوز باید یک APK جدید با versionCode به‌روزرسانی‌شده در مانیفست آپلود کنید. (کنسول Play به شما اجازه نمی‌دهد یک فایل توسعه را در یک APK موجود آپلود کنید.)

نکته: فایل بسط پچ از نظر معنایی مشابه فایل بسط اصلی است - می‌توانید از هر فایل به هر شکلی که می‌خواهید استفاده کنید.

فرمت نام فایل

هر فایل توسعه‌ای که آپلود می‌کنید می‌تواند هر فرمتی که انتخاب می‌کنید (ZIP، PDF، MP4 و غیره) باشد. همچنین می‌توانید از ابزار JOBB برای کپسوله‌سازی و رمزگذاری مجموعه‌ای از فایل‌های منبع و پچ‌های بعدی برای آن مجموعه استفاده کنید. صرف نظر از نوع فایل، گوگل پلی آنها را به عنوان حباب‌های دودویی مات در نظر می‌گیرد و فایل‌ها را با استفاده از طرح زیر تغییر نام می‌دهد:

[main|patch].<expansion-version>.<package-name>.obb

سه جزء در این طرح وجود دارد:

main یا patch
مشخص می‌کند که آیا فایل، فایل اصلی است یا فایل الحاقی پچ. برای هر APK فقط یک فایل اصلی و یک فایل پچ می‌تواند وجود داشته باشد.
<expansion-version>
این یک عدد صحیح است که با کد نسخه APK که اولین بار افزونه به آن مرتبط شده است، مطابقت دارد (با مقدار android:versionCode برنامه مطابقت دارد).

«اولین» به این دلیل مورد تأکید قرار گرفته است که اگرچه کنسول Play به شما امکان می‌دهد از یک فایل توسعه آپلود شده با یک APK جدید دوباره استفاده کنید، نام فایل توسعه تغییر نمی‌کند - نسخه‌ای را که هنگام اولین آپلود فایل روی آن اعمال شده است، حفظ می‌کند.

<package-name>
نام بسته‌ی برنامه‌ی شما به سبک جاوا.

برای مثال، فرض کنید نسخه APK شما ۳۱۴۱۵۹ و نام بسته شما com.example.app است. اگر یک فایل توسعه اصلی آپلود کنید، نام فایل به صورت زیر تغییر می‌کند:

main.314159.com.example.app.obb

محل نگهداری

وقتی گوگل پلی فایل‌های افزونه شما را روی دستگاهی دانلود می‌کند، آنها را در محل ذخیره‌سازی مشترک سیستم ذخیره می‌کند. برای اطمینان از عملکرد صحیح، نباید فایل‌های افزونه را حذف، جابجا یا تغییر نام دهید. در صورتی که برنامه شما مجبور به دانلود از خود گوگل پلی باشد، باید فایل‌ها را دقیقاً در همان محل ذخیره کنید.

متد getObbDir() ‎ مکان خاص فایل‌های توسعه شما را به شکل زیر برمی‌گرداند:

<shared-storage>/Android/obb/<package-name>/
  • <shared-storage> مسیر فضای ذخیره‌سازی مشترک است که از طریق getExternalStorageDirectory() قابل دسترسی است.
  • <package-name> نام بسته برنامه شما به سبک جاوا است که از getPackageName() قابل دسترسی است.

برای هر برنامه، هرگز بیش از دو فایل توسعه در این دایرکتوری وجود ندارد. یکی فایل توسعه اصلی و دیگری فایل توسعه پچ (در صورت لزوم) است. نسخه‌های قبلی هنگام به‌روزرسانی برنامه با فایل‌های توسعه جدید، رونویسی می‌شوند. از اندروید ۴.۴ (سطح API ۱۹)، برنامه‌ها می‌توانند فایل‌های توسعه OBB را بدون اجازه ذخیره‌سازی خارجی بخوانند. با این حال، برخی از پیاده‌سازی‌های اندروید ۶.۰ (سطح API ۲۳) و بالاتر هنوز به مجوز نیاز دارند، بنابراین باید مجوز READ_EXTERNAL_STORAGE را در مانیفست برنامه اعلام کنید و در زمان اجرا به شرح زیر درخواست مجوز کنید:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

برای اندروید نسخه ۶ و بالاتر، مجوز ذخیره‌سازی خارجی باید در زمان اجرا درخواست شود. با این حال، برخی از پیاده‌سازی‌های اندروید نیازی به مجوز خواندن فایل‌های OBB ندارند. قطعه کد زیر نحوه بررسی دسترسی خواندن قبل از درخواست مجوز ذخیره‌سازی خارجی را نشان می‌دهد:

کاتلین

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

جاوا

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

فرآیند دانلود

اغلب اوقات، گوگل پلی همزمان با دانلود فایل APK روی دستگاه، فایل‌های توسعه شما را نیز دانلود و ذخیره می‌کند. با این حال، در برخی موارد، گوگل پلی نمی‌تواند فایل‌های توسعه را دانلود کند یا ممکن است کاربر فایل‌های توسعه‌ای که قبلاً دانلود کرده است را حذف کرده باشد. برای مدیریت این موقعیت‌ها، برنامه شما باید بتواند هنگام شروع فعالیت اصلی، با استفاده از URL ارائه شده توسط گوگل پلی، خود فایل‌ها را دانلود کند.

فرآیند دانلود از سطح بالا به این شکل است:

  1. کاربر انتخاب می‌کند که برنامه شما را از گوگل پلی نصب کند.
  2. اگر گوگل پلی بتواند فایل‌های الحاقی را دانلود کند (که در اکثر دستگاه‌ها این اتفاق می‌افتد)، آنها را همراه با فایل APK دانلود می‌کند.

    اگر گوگل پلی نتواند فایل‌های الحاقی را دانلود کند، فقط فایل APK را دانلود می‌کند.

  3. وقتی کاربر برنامه شما را اجرا می‌کند، برنامه شما باید بررسی کند که آیا فایل‌های توسعه از قبل روی دستگاه ذخیره شده‌اند یا خیر.
    1. اگر بله، برنامه شما آماده استفاده است.
    2. اگر خیر، برنامه شما باید فایل‌های توسعه را از طریق HTTP از گوگل پلی دانلود کند. برنامه شما باید با استفاده از سرویس صدور مجوز برنامه گوگل پلی، درخواستی را به کلاینت گوگل پلی ارسال کند که در پاسخ، نام، اندازه فایل و URL هر فایل توسعه را ارائه می‌دهد. با این اطلاعات، شما فایل‌ها را دانلود کرده و آنها را در محل ذخیره‌سازی مناسب ذخیره می‌کنید.

احتیاط: بسیار مهم است که کد لازم برای دانلود فایل‌های توسعه از گوگل پلی را در صورتی که فایل‌ها هنگام شروع برنامه شما از قبل روی دستگاه شما نیستند، وارد کنید. همانطور که در بخش بعدی در مورد دانلود فایل‌های توسعه بحث شد، ما کتابخانه‌ای را در دسترس شما قرار داده‌ایم که این فرآیند را بسیار ساده کرده و دانلود را از یک سرویس با حداقل کد از طرف شما انجام می‌دهد.

چک لیست توسعه

در اینجا خلاصه‌ای از کارهایی که باید برای استفاده از فایل‌های توسعه با برنامه خود انجام دهید، آورده شده است:

  1. ابتدا مشخص کنید که آیا حجم دانلود فشرده برنامه شما باید بیش از ۱۰۰ مگابایت باشد یا خیر. فضا بسیار ارزشمند است و شما باید حجم کل دانلود خود را تا حد امکان کم نگه دارید. اگر برنامه شما برای ارائه چندین نسخه از فایل‌های گرافیکی برای تراکم صفحه نمایش‌های مختلف، بیش از ۱۰۰ مگابایت فضا اشغال می‌کند، به جای آن انتشار چندین APK را در نظر بگیرید که در آن هر APK فقط شامل فایل‌های مورد نیاز برای صفحه نمایش‌هایی باشد که هدف قرار می‌دهد. برای بهترین نتیجه هنگام انتشار در Google Play، یک Android App Bundle آپلود کنید که شامل تمام کدها و منابع کامپایل شده برنامه شما باشد، اما تولید APK و امضای آن در Google Play را به تعویق بیندازد.
  2. مشخص کنید که کدام منابع برنامه را از APK خود جدا کنید و آنها را در یک فایل بسته‌بندی کنید تا به عنوان فایل توسعه اصلی استفاده شود.

    معمولاً هنگام انجام به‌روزرسانی‌ها در فایل توسعه اصلی، فقط باید از فایل توسعه پچ دوم استفاده کنید. با این حال، اگر منابع شما از حد مجاز ۲ گیگابایت برای فایل توسعه اصلی بیشتر شد، می‌توانید از فایل پچ برای بقیه فایل‌های خود استفاده کنید.

  3. برنامه خود را طوری توسعه دهید که از منابع فایل‌های توسعه شما در محل ذخیره‌سازی مشترک دستگاه استفاده کند.

    به یاد داشته باشید که نباید فایل‌های توسعه را حذف، جابجا یا تغییر نام دهید.

    اگر برنامه شما فرمت خاصی را درخواست نمی‌کند، پیشنهاد می‌کنیم برای فایل‌های توسعه خود فایل‌های ZIP ایجاد کنید، سپس آنها را با استفاده از APK Expansion Zip Library بخوانید.

  4. منطقی را به فعالیت اصلی برنامه خود اضافه کنید که بررسی کند آیا فایل‌های توسعه در هنگام راه‌اندازی روی دستگاه هستند یا خیر. اگر فایل‌ها روی دستگاه نیستند، از سرویس صدور مجوز برنامه Google Play برای درخواست URL برای فایل‌های توسعه استفاده کنید، سپس آنها را دانلود و ذخیره کنید.

    برای کاهش قابل توجه حجم کدی که باید بنویسید و تضمین یک تجربه کاربری خوب در طول دانلود، توصیه می‌کنیم از کتابخانه دانلودر برای پیاده‌سازی رفتار دانلود خود استفاده کنید.

    اگر به جای استفاده از کتابخانه، سرویس دانلود خودتان را می‌سازید، توجه داشته باشید که نباید نام فایل‌های توسعه را تغییر دهید و باید آنها را در محل ذخیره‌سازی مناسب ذخیره کنید.

پس از اتمام توسعه برنامه، راهنمای تست فایل‌های توسعه خود را دنبال کنید.

قوانین و محدودیت‌ها

افزودن فایل‌های الحاقی APK یکی از ویژگی‌هایی است که هنگام آپلود برنامه خود با استفاده از کنسول Play در دسترس است. هنگام آپلود برنامه برای اولین بار یا به‌روزرسانی برنامه‌ای که از فایل‌های الحاقی استفاده می‌کند، باید از قوانین و محدودیت‌های زیر آگاه باشید:

  1. هر فایل توسعه نمی‌تواند بیش از ۲ گیگابایت باشد.
  2. برای دانلود فایل‌های الحاقی شما از گوگل پلی، کاربر باید برنامه شما را از گوگل پلی دریافت کرده باشد . اگر برنامه از طریق دیگری نصب شده باشد، گوگل پلی آدرس‌های اینترنتی فایل‌های الحاقی شما را ارائه نمی‌دهد.
  3. هنگام انجام دانلود از داخل برنامه، URL ای که گوگل پلی برای هر فایل ارائه می‌دهد، برای هر دانلود منحصر به فرد است و هر دانلود مدت کوتاهی پس از دریافت، منقضی می‌شود.
  4. اگر برنامه خود را با یک APK جدید به‌روزرسانی کنید یا چندین APK برای یک برنامه آپلود کنید، می‌توانید فایل‌های توسعه‌ای را که برای APK قبلی آپلود کرده‌اید، انتخاب کنید. نام فایل توسعه‌ای تغییر نمی‌کند - نسخه‌ای که توسط APK دریافت شده و فایل در ابتدا به آن مرتبط بوده است، حفظ می‌شود.
  5. اگر از فایل‌های توسعه در ترکیب با چندین APK استفاده می‌کنید تا فایل‌های توسعه متفاوتی برای دستگاه‌های مختلف ارائه دهید، همچنان باید برای هر دستگاه APKهای جداگانه‌ای آپلود کنید تا یک مقدار versionCode منحصر به فرد ارائه دهید و فیلترهای مختلفی را برای هر APK تعریف کنید.
  6. شما نمی‌توانید تنها با تغییر فایل‌های توسعه، برنامه خود را به‌روزرسانی کنید - برای به‌روزرسانی برنامه خود باید یک APK جدید بارگذاری کنید . اگر تغییرات شما فقط مربوط به فایل‌های توسعه شما باشد، می‌توانید APK خود را به سادگی با تغییر versionCode (و شاید همچنین versionName ) به‌روزرسانی کنید.
  7. داده‌های دیگر را در دایرکتوری obb/ خود ذخیره نکنید . اگر باید برخی داده‌ها را از حالت فشرده خارج کنید، آن‌ها را در مکانی که توسط getExternalFilesDir() مشخص شده است، ذخیره کنید.
  8. فایل الحاقی .obb را حذف یا تغییر نام ندهید (مگر اینکه در حال انجام به‌روزرسانی باشید). انجام این کار باعث می‌شود گوگل پلی (یا خود برنامه شما) بارها و بارها فایل الحاقی را دانلود کند.
  9. هنگام به‌روزرسانی دستی یک فایل توسعه، باید فایل توسعه قبلی را حذف کنید.

دانلود فایل‌های توسعه

در بیشتر موارد، گوگل پلی همزمان با نصب یا به‌روزرسانی APK، فایل‌های افزونه شما را دانلود و در دستگاه ذخیره می‌کند. به این ترتیب، فایل‌های افزونه هنگام اولین اجرای برنامه در دسترس هستند. با این حال، در برخی موارد، برنامه شما باید فایل‌های افزونه را خودش با درخواست آنها از URL ارائه شده به شما در پاسخ از سرویس صدور مجوز برنامه گوگل پلی دانلود کند.

منطق اساسی که برای دانلود فایل‌های توسعه خود نیاز دارید به شرح زیر است:

  1. وقتی برنامه شما اجرا شد، به دنبال فایل‌های افزونه در محل ذخیره‌سازی مشترک (در دایرکتوری Android/obb/<package-name>/ ) بگردید.
    1. اگر فایل‌های توسعه وجود داشته باشند، همه چیز آماده است و برنامه شما می‌تواند ادامه یابد.
    2. اگر فایل‌های توسعه وجود ندارند :
      1. با استفاده از مجوز برنامه گوگل پلی، درخواستی را برای دریافت نام، اندازه و آدرس‌های اینترنتی فایل‌های الحاقی برنامه خود انجام دهید.
      2. از آدرس‌های اینترنتی ارائه شده توسط گوگل پلی برای دانلود فایل‌های الحاقی و ذخیره آنها استفاده کنید. شما باید فایل‌ها را در محل ذخیره‌سازی مشترک ( Android/obb/<package-name>/ ) ذخیره کنید و دقیقاً از نام فایل ارائه شده توسط پاسخ گوگل پلی استفاده کنید.

        توجه: آدرس اینترنتی (URL) که گوگل پلی برای فایل‌های افزونه شما ارائه می‌دهد، برای هر دانلود منحصر به فرد است و هر کدام اندکی پس از ارائه به برنامه شما منقضی می‌شوند.

اگر برنامه شما رایگان است (نه یک برنامه پولی)، احتمالاً از سرویس صدور مجوز برنامه استفاده نکرده‌اید. این سرویس در درجه اول برای شما طراحی شده است تا سیاست‌های صدور مجوز را برای برنامه خود اعمال کنید و اطمینان حاصل کنید که کاربر حق استفاده از برنامه شما را دارد (آنها به درستی برای آن در Google Play هزینه پرداخت کرده‌اند). به منظور تسهیل عملکرد فایل توسعه، سرویس صدور مجوز بهبود یافته است تا پاسخی به برنامه شما ارائه دهد که شامل URL فایل‌های توسعه برنامه شما است که در Google Play میزبانی می‌شوند. بنابراین، حتی اگر برنامه شما برای کاربران رایگان باشد، برای استفاده از فایل‌های توسعه APK باید کتابخانه تأیید مجوز (LVL) را نیز اضافه کنید. البته، اگر برنامه شما رایگان است، نیازی به اجرای تأیید مجوز ندارید - شما به سادگی به کتابخانه نیاز دارید تا درخواستی را که URL فایل‌های توسعه شما را برمی‌گرداند، انجام دهد.

توجه: چه برنامه شما رایگان باشد و چه نباشد، گوگل پلی فقط در صورتی آدرس‌های اینترنتی فایل‌های الحاقی را برمی‌گرداند که کاربر برنامه شما را از گوگل پلی تهیه کرده باشد.

علاوه بر LVL، به مجموعه‌ای از کدها نیاز دارید که فایل‌های توسعه را از طریق اتصال HTTP دانلود کرده و آنها را در مکان مناسب در حافظه مشترک دستگاه ذخیره کند. هنگام پیاده‌سازی این رویه در برنامه خود، باید چندین نکته را در نظر بگیرید:

  • ممکن است دستگاه فضای کافی برای فایل‌های افزونه نداشته باشد، بنابراین قبل از شروع دانلود باید بررسی کنید و در صورت عدم وجود فضای کافی، به کاربر هشدار دهید.
  • دانلود فایل‌ها باید در یک سرویس پس‌زمینه انجام شود تا از مسدود شدن تعامل کاربر جلوگیری شود و به کاربر اجازه داده شود تا در حین اتمام دانلود، برنامه شما را ترک کند.
  • ممکن است در طول درخواست و دانلود، خطاهای مختلفی رخ دهد که باید به درستی آنها را مدیریت کنید.
  • اتصال شبکه می‌تواند در طول دانلود تغییر کند، بنابراین باید چنین تغییراتی را مدیریت کنید و در صورت قطع شدن، در صورت امکان دانلود را از سر بگیرید.
  • در حالی که دانلود در پس‌زمینه انجام می‌شود، باید اعلانی ارائه دهید که پیشرفت دانلود را نشان دهد، پس از اتمام دانلود به کاربر اطلاع دهد و در صورت انتخاب، کاربر را به برنامه شما بازگرداند.

برای ساده‌سازی این کار برای شما، ما کتابخانه دانلودر (Downloader Library ) را ساخته‌ایم که از طریق سرویس صدور مجوز، آدرس‌های اینترنتی فایل‌های توسعه را درخواست می‌کند، فایل‌های توسعه را دانلود می‌کند، تمام وظایف ذکر شده در بالا را انجام می‌دهد و حتی به فعالیت شما اجازه می‌دهد تا دانلود را متوقف کرده و از سر بگیرد. با افزودن کتابخانه دانلودر و چند قلاب کد به برنامه‌تان، تقریباً تمام کارهای مربوط به دانلود فایل‌های توسعه از قبل برای شما کدگذاری شده است. به همین دلیل، برای ارائه بهترین تجربه کاربری با حداقل تلاش از طرف شما، توصیه می‌کنیم از کتابخانه دانلودر برای دانلود فایل‌های توسعه خود استفاده کنید. اطلاعات موجود در بخش‌های بعدی نحوه ادغام کتابخانه در برنامه شما را توضیح می‌دهد.

اگر ترجیح می‌دهید راه‌حل خودتان را برای دانلود فایل‌های افزونه با استفاده از URLهای گوگل پلی توسعه دهید، باید مستندات مجوز برنامه را برای انجام درخواست مجوز دنبال کنید، سپس نام‌ها، اندازه‌ها و URLهای فایل‌های افزونه را از موارد اضافی پاسخ بازیابی کنید. شما باید از کلاس APKExpansionPolicy (موجود در کتابخانه تأیید مجوز) به عنوان سیاست صدور مجوز خود استفاده کنید که نام‌ها، اندازه‌ها و URLهای فایل‌های افزونه را از سرویس صدور مجوز دریافت می‌کند.

درباره کتابخانه دانلودر

برای استفاده از فایل‌های توسعه APK در برنامه خود و ارائه بهترین تجربه کاربری با حداقل تلاش از طرف شما، توصیه می‌کنیم از کتابخانه دانلودر که در بسته کتابخانه توسعه APK گوگل پلی موجود است، استفاده کنید. این کتابخانه فایل‌های توسعه شما را در یک سرویس پس‌زمینه دانلود می‌کند، یک اعلان کاربر را با وضعیت دانلود نشان می‌دهد، قطع اتصال شبکه را مدیریت می‌کند، در صورت امکان دانلود را از سر می‌گیرد و موارد دیگر.

برای پیاده‌سازی دانلود فایل‌های توسعه با استفاده از کتابخانه دانلودر، تنها کاری که باید انجام دهید این است:

  • یک زیرکلاس ویژه Service و یک زیرکلاس ویژه BroadcastReceiver را بسط دهید که هر کدام فقط به چند خط کد از شما نیاز دارند.
  • به activity اصلی خود منطقی اضافه کنید که بررسی کند آیا فایل‌های توسعه قبلاً دانلود شده‌اند یا خیر، و اگر دانلود نشده باشد، فرآیند دانلود را فراخوانی کرده و رابط کاربری پیشرفت را نمایش دهد.
  • یک رابط فراخوانی با چند متد در activity اصلی خود پیاده‌سازی کنید که به‌روزرسانی‌هایی در مورد پیشرفت دانلود دریافت کند.

بخش‌های زیر نحوه‌ی راه‌اندازی برنامه‌ی شما با استفاده از کتابخانه‌ی دانلودر را توضیح می‌دهند.

آماده‌سازی برای استفاده از کتابخانه دانلودر

برای استفاده از کتابخانه دانلودر، باید دو بسته را از SDK Manager دانلود کنید و کتابخانه‌های مناسب را به برنامه خود اضافه کنید.

ابتدا، Android SDK Manager ( Tools > SDK Manager ) را باز کنید و در قسمت Appearance & Behavior > System Settings > Android SDK ، تب SDK Tools را برای انتخاب و دانلود انتخاب کنید:

  • بسته کتابخانه صدور مجوز گوگل پلی
  • بسته کتابخانه توسعه APK گوگل پلی

یک ماژول کتابخانه جدید برای کتابخانه تأیید مجوز و کتابخانه دانلودکننده ایجاد کنید. برای هر کتابخانه:

  1. فایل > جدید > ماژول جدید را انتخاب کنید.
  2. در پنجره‌ی «ایجاد ماژول جدید» ، «کتابخانه‌ی اندروید» را انتخاب کنید و سپس «بعدی» را بزنید.
  3. نام برنامه/کتابخانه مانند «کتابخانه مجوز گوگل پلی» و «کتابخانه دانلود گوگل پلی» را مشخص کنید، حداقل سطح SDK را انتخاب کنید، سپس روی «پایان» کلیک کنید.
  4. فایل > ساختار پروژه را انتخاب کنید.
  5. برگه Properties را انتخاب کنید و در Library Repository ، کتابخانه را از دایرکتوری <sdk>/extras/google/ وارد کنید (برای کتابخانه تأیید مجوز، play_licensing/ یا برای کتابخانه دانلود play_apk_expansion/downloader_library/ ).
  6. برای ایجاد ماژول جدید، روی تأیید کلیک کنید.

نکته: کتابخانه دانلودر به کتابخانه تأیید مجوز (License Verification Library) وابسته است. حتماً کتابخانه تأیید مجوز (License Verification Library) را به ویژگی‌های پروژه کتابخانه دانلودر اضافه کنید.

یا از طریق خط فرمان، پروژه خود را به‌روزرسانی کنید تا کتابخانه‌ها را شامل شود:

  1. دایرکتوری‌ها را به دایرکتوری <sdk>/tools/ تغییر دهید.
  2. 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
    

با افزودن هر دو کتابخانه تأیید مجوز و کتابخانه دانلودر به برنامه‌تان، می‌توانید به سرعت قابلیت دانلود فایل‌های توسعه از گوگل پلی را ادغام کنید. فرمتی که برای فایل‌های توسعه انتخاب می‌کنید و نحوه خواندن آنها از فضای ذخیره‌سازی مشترک، پیاده‌سازی جداگانه‌ای است که باید بر اساس نیازهای برنامه‌تان در نظر بگیرید.

نکته: بسته Apk Expansion شامل یک برنامه نمونه است که نحوه استفاده از کتابخانه دانلودر را در یک برنامه نشان می‌دهد. این نمونه از یک کتابخانه سوم موجود در بسته Apk Expansion به نام APK Expansion Zip Library استفاده می‌کند. اگر قصد دارید از فایل‌های ZIP برای فایل‌های توسعه خود استفاده کنید، پیشنهاد می‌کنیم APK Expansion Zip Library را نیز به برنامه خود اضافه کنید. برای اطلاعات بیشتر، به بخش زیر در مورد استفاده از APK Expansion Zip 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>

توجه: به طور پیش‌فرض، کتابخانه دانلودر به API سطح ۴ نیاز دارد، اما کتابخانه زیپ افزونه APK به API سطح ۵ نیاز دارد.

پیاده‌سازی سرویس دانلودر

برای انجام دانلودها در پس‌زمینه، کتابخانه‌ی دانلودر، زیرکلاس Service مخصوص به خود به نام DownloaderService را ارائه می‌دهد که باید آن را توسعه دهید. DownloaderService علاوه بر دانلود فایل‌های توسعه برای شما، موارد زیر را نیز انجام می‌دهد:

  • یک BroadcastReceiver ثبت می‌کند که به تغییرات در اتصال شبکه دستگاه (پخش CONNECTIVITY_ACTION ) گوش می‌دهد تا در صورت لزوم (مثلاً به دلیل قطع اتصال) دانلود را متوقف کند و در صورت امکان (در صورت برقراری اتصال) دانلود را از سر بگیرد.
  • یک هشدار RTC_WAKEUP را برای تلاش مجدد دانلود در مواردی که سرویس از کار می‌افتد، زمان‌بندی می‌کند.
  • یک Notification سفارشی ایجاد می‌کند که پیشرفت دانلود و هرگونه خطا یا تغییر وضعیت را نمایش می‌دهد.
  • به برنامه شما اجازه می‌دهد تا دانلود را به صورت دستی متوقف کرده و از سر بگیرد.
  • قبل از دانلود فایل‌های توسعه، تأیید می‌کند که فضای ذخیره‌سازی مشترک نصب و در دسترس است، فایل‌ها از قبل وجود ندارند و فضای کافی وجود دارد. سپس در صورت صحت هر یک از این موارد، به کاربر اطلاع می‌دهد.

تنها کاری که باید انجام دهید این است که یک کلاس در برنامه خود ایجاد کنید که کلاس DownloaderService را ارث بری کند و سه متد را برای ارائه جزئیات خاص برنامه، بازنویسی (override) کند:

getPublicKey()
این باید رشته‌ای را برگرداند که کلید عمومی RSA با کدگذاری Base64 برای حساب ناشر شما باشد و از صفحه نمایه در کنسول Play قابل دسترسی باشد ( به تنظیمات مجوز مراجعه کنید).
getSALT()
این باید آرایه‌ای از بایت‌های تصادفی را برگرداند که Policy صدور مجوز برای ایجاد یک Obfuscator از آن استفاده می‌کند. Salt تضمین می‌کند که فایل SharedPreferences مبهم‌سازی شده شما که داده‌های صدور مجوز شما در آن ذخیره می‌شود، منحصر به فرد و غیرقابل کشف خواهد بود.
getAlarmReceiverClassName()
این باید نام کلاس BroadcastReceiver را در برنامه شما برگرداند که باید هشداری مبنی بر شروع مجدد دانلود دریافت کند (که ممکن است در صورت توقف غیرمنتظره سرویس دانلود رخ دهد).

برای مثال، در اینجا یک پیاده‌سازی کامل از DownloaderService آورده شده است:

کاتلین

// 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
}

جاوا

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 را به عنوان کلید عمومی متعلق به حساب ناشر خود به‌روزرسانی کنید. می‌توانید این کلید را در کنسول توسعه‌دهندگان، زیر اطلاعات پروفایل خود پیدا کنید. این کار حتی هنگام آزمایش دانلودهایتان نیز ضروری است.

به یاد داشته باشید که سرویس را در فایل مانیفست خود تعریف کنید:

<app ...>
    <service android:name=".SampleDownloaderService" />
    ...
</app>

پیاده‌سازی گیرنده هشدار

برای نظارت بر پیشرفت دانلود فایل و در صورت لزوم، DownloaderService یک هشدار RTC_WAKEUP را زمان‌بندی می‌کند که یک Intent را به BroadcastReceiver در برنامه شما ارسال می‌کند. شما باید BroadcastReceiver را طوری تعریف کنید که یک API از کتابخانه Downloader را فراخوانی کند که وضعیت دانلود را بررسی کرده و در صورت لزوم آن را مجدداً راه‌اندازی کند.

شما به سادگی باید متد onReceive() را برای فراخوانی DownloaderClientMarshaller.startDownloadServiceIfRequired() بازنویسی کنید.

برای مثال:

کاتلین

class SampleAlarmReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    context,
                    intent,
                    SampleDownloaderService::class.java
            )
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }
}

جاوا

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>

شروع دانلود

فعالیت اصلی در برنامه شما (فعالیتی که با آیکون لانچر شما آغاز می‌شود) مسئول تأیید وجود فایل‌های توسعه در دستگاه و شروع دانلود در صورت عدم وجود آنهاست.

شروع دانلود با استفاده از کتابخانه دانلودر مستلزم طی مراحل زیر است:

  1. بررسی کنید که آیا فایل‌ها دانلود شده‌اند یا خیر.

    کتابخانه دانلودر شامل برخی APIها در کلاس Helper است تا به این فرآیند کمک کند:

    • getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
    • doesFileExist(Context c, String fileName, long fileSize)

    برای مثال، برنامه‌ی نمونه‌ی ارائه شده در پکیج Apk Expansion، متد زیر را در متد onCreate() مربوط به activity فراخوانی می‌کند تا بررسی کند که آیا فایل‌های بسط از قبل روی دستگاه وجود دارند یا خیر:

    کاتلین

    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
    }

    جاوا

    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 برنامه نمونه مراجعه کنید.)

    اگر این متد مقدار false را برگرداند، برنامه باید دانلود را شروع کند.

  2. دانلود را با فراخوانی متد استاتیک DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass) آغاز کنید.

    این متد پارامترهای زیر را دریافت می‌کند:

    • context : Context برنامه شما.
    • notificationClient : یک PendingIntent برای شروع activity اصلی شما. این در Notification که DownloaderService برای نمایش پیشرفت دانلود ایجاد می‌کند، استفاده می‌شود. وقتی کاربر notification را انتخاب می‌کند، سیستم PendingIntent که شما اینجا ارائه می‌دهید را فراخوانی می‌کند و باید activity ای را که پیشرفت دانلود را نشان می‌دهد (معمولاً همان activity ای که دانلود را شروع کرده است) باز کند.
    • serviceClass : شیء Class برای پیاده‌سازی DownloaderService که برای شروع سرویس و در صورت لزوم شروع دانلود مورد نیاز است.

    این متد یک عدد صحیح برمی‌گرداند که نشان می‌دهد آیا دانلود مورد نیاز است یا خیر. مقادیر ممکن عبارتند از:

    • NO_DOWNLOAD_REQUIRED : اگر فایل‌ها از قبل وجود داشته باشند یا دانلودی در حال انجام باشد، مقدار را برمی‌گرداند.
    • LVL_CHECK_REQUIRED : در صورتی که برای دریافت URLهای فایل‌های توسعه، تأیید مجوز لازم باشد، بازگردانده می‌شود.
    • DOWNLOAD_REQUIRED : اگر آدرس‌های اینترنتی فایل‌های افزونه از قبل شناخته شده باشند، اما دانلود نشده باشند، برگردانده می‌شود.

    رفتار LVL_CHECK_REQUIRED و DOWNLOAD_REQUIRED اساساً یکسان است و معمولاً نیازی به نگرانی در مورد آنها ندارید. در activity اصلی خود که startDownloadServiceIfRequired() را فراخوانی می‌کند، می‌توانید به سادگی بررسی کنید که آیا پاسخ NO_DOWNLOAD_REQUIRED است یا خیر. اگر پاسخ چیزی غیر از NO_DOWNLOAD_REQUIRED باشد، کتابخانه Downloader دانلود را شروع می‌کند و شما باید رابط کاربری activity خود را به‌روزرسانی کنید تا پیشرفت دانلود را نمایش دهد (به مرحله بعدی مراجعه کنید). اگر پاسخ NO_DOWNLOAD_REQUIRED باشد ، فایل‌ها در دسترس هستند و برنامه شما می‌تواند شروع شود.

    برای مثال:

    کاتلین

    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
    }

    جاوا

    @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
    }
  3. وقتی متد startDownloadServiceIfRequired() هر چیزی غیر از NO_DOWNLOAD_REQUIRED را برمی‌گرداند، با فراخوانی DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService) یک نمونه از IStub ایجاد کنید. IStub یک اتصال بین activity شما و سرویس دانلود کننده ایجاد می‌کند به طوری که activity شما در مورد پیشرفت دانلود، callback دریافت می‌کند.

    برای نمونه‌سازی IStub خود با فراخوانی CreateStub() ، باید پیاده‌سازی رابط IDownloaderClient و پیاده‌سازی DownloaderService خود را به آن منتقل کنید. بخش بعدی در مورد دریافت پیشرفت دانلود ، رابط IDownloaderClient را مورد بحث قرار می‌دهد که معمولاً باید در کلاس Activity خود پیاده‌سازی کنید تا بتوانید رابط کاربری activity را هنگام تغییر وضعیت دانلود به‌روزرسانی کنید.

    توصیه می‌کنیم که پس از شروع دانلود توسط startDownloadServiceIfRequired() ، در طول متد onCreate() مربوط به activity خود، برای نمونه‌سازی IStub ، از CreateStub() استفاده کنید.

    برای مثال، در نمونه کد قبلی برای onCreate() ، می‌توانید به نتیجه startDownloadServiceIfRequired() به این صورت پاسخ دهید:

    کاتلین

            // 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
            }

    جاوا

            // 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() اکتیویتی خود فراخوانی کنید.

    کاتلین

    override fun onResume() {
        downloaderClientStub?.connect(this)
        super.onResume()
    }
    
    override fun onStop() {
        downloaderClientStub?.disconnect(this)
        super.onStop()
    }

    جاوا

    @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 را پیاده‌سازی کنید. معمولاً، فعالیتی که برای شروع دانلود استفاده می‌کنید باید این رابط را پیاده‌سازی کند تا پیشرفت دانلود را نمایش دهد و درخواست‌ها را به سرویس ارسال کند.

متدهای رابط مورد نیاز برای IDownloaderClient عبارتند از:

onServiceConnected(Messenger m)
پس از اینکه IStub در activity خود نمونه‌سازی کردید، فراخوانی به این متد دریافت خواهید کرد که یک شیء Messenger متصل به نمونه DownloaderService شما را ارسال می‌کند. برای ارسال درخواست به سرویس، مانند مکث و از سرگیری دانلودها، باید DownloaderServiceMarshaller.CreateProxy() را برای دریافت رابط IDownloaderService متصل به سرویس فراخوانی کنید.

یک پیاده‌سازی پیشنهادی به این شکل است:

کاتلین

private var remoteService: IDownloaderService? = null
...

override fun onServiceConnected(m: Messenger) {
    remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
        downloaderClientStub?.messenger?.also { messenger ->
            onClientUpdated(messenger)
        }
    }
}

جاوا

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 ارائه دهد که اطلاعات مختلفی در مورد پیشرفت دانلود، از جمله زمان تخمینی باقی مانده، سرعت فعلی، پیشرفت کلی و مجموع را شرح می‌دهد تا بتوانید رابط کاربری پیشرفت دانلود را به‌روزرسانی کنید.

نکته: برای نمونه‌هایی از این فراخوانی‌های برگشتی که رابط کاربری پیشرفت دانلود را به‌روزرسانی می‌کنند، به SampleDownloaderActivity در برنامه نمونه ارائه شده با بسته Apk Expansion مراجعه کنید.

برخی از متدهای عمومی برای رابط IDownloaderService که ممکن است مفید باشند عبارتند از:

requestPauseDownload()
دانلود را متوقف می‌کند.
requestContinueDownload()
دانلود متوقف شده را از سر می‌گیرد.
setDownloadFlags(int flags)
تنظیمات برگزیده کاربر را برای انواع شبکه‌ای که دانلود فایل‌ها در آنها مجاز است، تنظیم می‌کند. پیاده‌سازی فعلی از یک پرچم، FLAGS_DOWNLOAD_OVER_CELLULAR ، پشتیبانی می‌کند، اما می‌توانید پرچم‌های دیگری نیز اضافه کنید. به طور پیش‌فرض، این پرچم فعال نیست ، بنابراین کاربر برای دانلود فایل‌های توسعه باید به Wi-Fi متصل باشد. ممکن است بخواهید یک تنظیمات برگزیده کاربر برای فعال کردن دانلودها از طریق شبکه تلفن همراه ارائه دهید. در این صورت، می‌توانید فراخوانی کنید:

کاتلین

remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
    ...
    setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR)
}

جاوا

remoteService
    .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);

استفاده از APKExpansionPolicy

If you decide to build your own downloader service instead of using the Google Play Downloader Library , you should still use the APKExpansionPolicy that's provided in the License Verification Library. The APKExpansionPolicy class is nearly identical to ServerManagedPolicy (available in the Google Play License Verification Library) but includes additional handling for the APK expansion file response extras.

Note: If you do use the Downloader Library as discussed in the previous section, the library performs all interaction with the APKExpansionPolicy so you don't have to use this class directly.

The class includes methods to help you get the necessary information about the available expansion files:

  • getExpansionURLCount()
  • getExpansionURL(int index)
  • getExpansionFileName(int index)
  • getExpansionFileSize(int index)

For more information about how to use the APKExpansionPolicy when you're not using the Downloader Library , see the documentation for Adding Licensing to Your App , which explains how to implement a license policy such as this one.

Reading the Expansion File

Once your APK expansion files are saved on the device, how you read your files depends on the type of file you've used. As discussed in the overview , your expansion files can be any kind of file you want, but are renamed using a particular file name format and are saved to <shared-storage>/Android/obb/<package-name>/ .

Regardless of how you read your files, you should always first check that the external storage is available for reading. There's a chance that the user has the storage mounted to a computer over USB or has actually removed the SD card.

Note: When your app starts, you should always check whether the external storage space is available and readable by calling getExternalStorageState() . This returns one of several possible strings that represent the state of the external storage. In order for it to be readable by your app, the return value must be MEDIA_MOUNTED .

Getting the file names

As described in the overview , your APK expansion files are saved using a specific file name format:

[main|patch].<expansion-version>.<package-name>.obb

To get the location and names of your expansion files, you should use the getExternalStorageDirectory() and getPackageName() methods to construct the path to your files.

Here's a method you can use in your app to get an array containing the complete path to both your expansion files:

کاتلین

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

جاوا

// 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;
}

You can call this method by passing it your app Context and the desired expansion file's version.

There are many ways you could determine the expansion file version number. One simple way is to save the version in a SharedPreferences file when the download begins, by querying the expansion file name with the APKExpansionPolicy class's getExpansionFileName(int index) method. You can then get the version code by reading the SharedPreferences file when you want to access the expansion file.

For more information about reading from the shared storage, see the Data Storage documentation.

Using the APK Expansion Zip Library

The Google Market Apk Expansion package includes a library called the APK Expansion Zip Library (located in <sdk>/extras/google/google_market_apk_expansion/zip_file/ ). This is an optional library that helps you read your expansion files when they're saved as ZIP files. Using this library allows you to easily read resources from your ZIP expansion files as a virtual file system.

The APK Expansion Zip Library includes the following classes and APIs:

APKExpansionSupport
Provides some methods to access expansion file names and ZIP files:
getAPKExpansionFiles()
The same method shown above that returns the complete file path to both expansion files.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
Returns a ZipResourceFile representing the sum of both the main file and patch file. That is, if you specify both the mainVersion and the patchVersion , this returns a ZipResourceFile that provides read access to all the data, with the patch file's data merged on top of the main file.
ZipResourceFile
Represents a ZIP file on the shared storage and performs all the work to provide a virtual file system based on your ZIP files. You can get an instance using APKExpansionSupport.getAPKExpansionZipFile() or with the ZipResourceFile by passing it the path to your expansion file. This class includes a variety of useful methods, but you generally don't need to access most of them. A couple of important methods are:
getInputStream(String assetPath)
Provides an InputStream to read a file within the ZIP file. The assetPath must be the path to the desired file, relative to the root of the ZIP file contents.
getAssetFileDescriptor(String assetPath)
Provides an AssetFileDescriptor for a file within the ZIP file. The assetPath must be the path to the desired file, relative to the root of the ZIP file contents. This is useful for certain Android APIs that require an AssetFileDescriptor , such as some MediaPlayer APIs.
APEZProvider
Most apps don't need to use this class. This class defines a ContentProvider that marshals the data from the ZIP files through a content provider Uri in order to provide file access for certain Android APIs that expect Uri access to media files. For example, this is useful if you want to play a video with VideoView.setVideoURI() .

Skipping ZIP compression of media files

If you're using your expansion files to store media files, a ZIP file still allows you to use Android media playback calls that provide offset and length controls (such as MediaPlayer.setDataSource() and SoundPool.load() ). In order for this to work, you must not perform additional compression on the media files when creating the ZIP packages. For example, when using the zip tool, you should use the -n option to specify the file suffixes that should not be compressed:

zip -n .mp4;.ogg main_expansion media_files

Reading from a ZIP file

When using the APK Expansion Zip Library, reading a file from your ZIP usually requires the following:

کاتلین

// 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 {
    ...
}

جاوا

// 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);

The above code provides access to any file that exists in either your main expansion file or patch expansion file, by reading from a merged map of all the files from both files. All you need to provide the getAPKExpansionFile() method is your app android.content.Context and the version number for both the main expansion file and patch expansion file.

If you'd rather read from a specific expansion file, you can use the ZipResourceFile constructor with the path to the desired expansion file:

کاتلین

// 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 {
    ...
}

جاوا

// 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);

For more information about using this library for your expansion files, look at the sample app's SampleDownloaderActivity class, which includes additional code to verify the downloaded files using CRC. Beware that if you use this sample as the basis for your own implementation, it requires that you declare the byte size of your expansion files in the xAPKS array.

Testing Your Expansion Files

Before publishing your app, there are two things you should test: Reading the expansion files and downloading the files.

Testing file reads

Before you upload your app to Google Play, you should test your app's ability to read the files from the shared storage. All you need to do is add the files to the appropriate location on the device shared storage and launch your app:

  1. On your device, create the appropriate directory on the shared storage where Google Play will save your files.

    For example, if your package name is com.example.android , you need to create the directory Android/obb/com.example.android/ on the shared storage space. (Plug in your test device to your computer to mount the shared storage and manually create this directory.)

  2. Manually add the expansion files to that directory. Be sure that you rename your files to match the file name format that Google Play will use.

    For example, regardless of the file type, the main expansion file for the com.example.android app should be main.0300110.com.example.android.obb . The version code can be whatever value you want. Just remember:

    • The main expansion file always starts with main and the patch file starts with patch .
    • The package name always matches that of the APK to which the file is attached on Google Play.
  3. Now that the expansion file(s) are on the device, you can install and run your app to test your expansion file(s).

Here are some reminders about handling the expansion files:

  • Do not delete or rename the .obb expansion files (even if you unpack the data to a different location). Doing so will cause Google Play (or your app itself) to repeatedly download the expansion file.
  • Do not save other data into your obb/ directory . If you must unpack some data, save it into the location specified by getExternalFilesDir() .

Testing file downloads

Because your app must sometimes manually download the expansion files when it first opens, it's important that you test this process to be sure your app can successfully query for the URLs, download the files, and save them to the device.

To test your app's implementation of the manual download procedure, you can publish it to the internal test track, so it's only available to authorized testers. If everything works as expected, your app should begin downloading the expansion files as soon as the main activity starts.

Note: Previously you could test an app by uploading an unpublished "draft" version. This functionality is no longer supported. Instead, you must publish it to an internal, closed, or open testing track. For more information, see Draft Apps are No Longer Supported .

Updating Your app

One of the great benefits to using expansion files on Google Play is the ability to update your app without re-downloading all of the original assets. Because Google Play allows you to provide two expansion files with each APK, you can use the second file as a "patch" that provides updates and new assets. Doing so avoids the need to re-download the main expansion file which could be large and expensive for users.

The patch expansion file is technically the same as the main expansion file and neither the Android system nor Google Play perform actual patching between your main and patch expansion files. Your app code must perform any necessary patches itself.

If you use ZIP files as your expansion files, the APK Expansion Zip Library that's included with the Apk Expansion package includes the ability to merge your patch file with the main expansion file.

Note: Even if you only need to make changes to the patch expansion file, you must still update the APK in order for Google Play to perform an update. If you don't require code changes in the app, you should simply update the versionCode in the manifest.

As long as you don't change the main expansion file that's associated with the APK in the Play Console, users who previously installed your app will not download the main expansion file. Existing users receive only the updated APK and the new patch expansion file (retaining the previous main expansion file).

Here are a few issues to keep in mind regarding updates to expansion files:

  • There can be only two expansion files for your app at a time. One main expansion file and one patch expansion file. During an update to a file, Google Play deletes the previous version (and so must your app when performing manual updates).
  • When adding a patch expansion file, the Android system does not actually patch your app or main expansion file. You must design your app to support the patch data. However, the Apk Expansion package includes a library for using ZIP files as expansion files, which merges the data from the patch file into the main expansion file so you can easily read all the expansion file data.