کاهش سایز apk هنگام استفاده از کتابخانه های بومی(Native)


اگه تا حالا سعی کرده باشین که حجم apk ‌رو کاهش بدین حتما به تفکیک apk بر اساس تراکم صفحه نمایش و واسط باینری اپلیکیشن (Application binary interface به اختصار ABI ) فکر کردین.

در این پست یاد می گیریم که ABI چیه ‍‍‍‍‍‍‍‍‍٬ تفکیک ABI چه مشکلاتی داره و راه‌های جایگزین برای جلوگیری از این مشکلات چیا هستن.

اگه اولین باره که اسم تفکیک ABI رو می‌شنوین ابتدا این پست رو بخونین (حواستون باشه سایتش فیلتره).

یک ABI ‌چیست و چگونه کار می‌کند؟

در واقع ABI کاملا دقیق و شفاف مشخص می‌کنه که چطور کد ماشین یک اپلیکیشن بتونه در زمان اجرا (runtime) با سیستم تعامل کنه. در نتیجه باید برای معماری هر پردازنده ای که اپلیکیشن باید باهاش کار کنه یک ABI مشخص کنیم.

برای نمونه اندروید از ABI های زیر پشتیبانی می‌کنه :

mips, mips64, X86, X86–64, arm64-v8a, armeabi, armeabi-v7a

به طور راحت تر اینکه : دستگاه های اندرویدی چیست و پردازنده و cpu های مختلفی دارن. اپلیکیشن ها بر اساس معماری cpu دستگاهی که روش نصب شدن از ورژن های مختلف کتابخونه‌ها استفاده می‌کنن.به عنوان مثال پیکسل گوگل از ورژن arm64-v8a کتابخونه ها و نکسوس 5 از ورژن armeabi-v7a کتابخونه ها استفاده می‌کنه.

درون هر کتابخونه ای که به اپلیکیشن‌تون اضافه می‌کنین یک فایل با پسوند so. وجود داره که از همه معماری های cpu پشتیبانی می‌کنه.این فایل باعث میشه که به صورت چشمگیری سایز apk ‌ زیاد بشه ٬ در حالی که دستگاه مورد نظر (گوشی ٬ ساعت و.. اندرویدی) فقط یک ورژن از کتابخونه رو لازم داره.


یک مثال برای فهمیدن ABI

یک اپ ساده شامل یک اکتیویتی و آیکون ها ایجاد کردیم.

آنالیز apk
آنالیز apk

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

این بحث مختص کتابخونه Realm نیست و همه کتابخونه های نیتیو اندروید اینطوری رفتار می‌کنند.
جزییات apk  بعد از اضافه کردن کتابخونه Realm
جزییات apk بعد از اضافه کردن کتابخونه Realm

بهله! سایز apk از 74KB رسید به 5MB !

ببینیم چی اتفاقی افتاده!

توزیع Realm برای معماری های مختلف CPU
توزیع Realm برای معماری های مختلف CPU


در واقع کتابخونه Realm فایل so. برای پشتیبانی از 5 معماری پردازنده اضافه کرده که شامل این ها هست :

mips, X86, X86–64, armeabi-v7a, and arm64-v8a

این باعث شده که سایز apk به 4.8MB برسه . حدود 130KB بقیه به سایز dex اضافه شده.

اگه این اپ رو تو مارکت منتشر کنین پیکسل گوگل این apk با حجم 5MB رو نصب می‌کنه در صورتی که فقط به یک ورژن از کتابخونه Realm (arm64-v8a) نیاز داره که 947KB حجمشه! هزینه‌ی (سرریز) زیادیه! همچنین ممکنه که با ورژن V7 کتابخونه Realm هم به خوبی کار کنه که تنها 771KB حجم داره(این رو به زودی می‌فهمیم).

تفکیک بر اساس ABI چگونه از مشکل جلوگیری می‌کنه؟

اساسا با تفکیک ABI به بیلد سیستم گریدل می‌گیم که برای هر معماری متفاوت cpu یک apk جداگونه بسازه که داخلش ورژن کتابخونه مربوط به همون معماری باشه.

خروجی فایل build.gradle مربوط به ماژول اپ با تفکیک ABI :

android {
    splits {
        // Configures multiple APKs based on ABI.
        abi {
            // Enables building multiple APKs per ABI.
            enable true
            // By default all ABIs are included, so use reset() and include
            // to specify that we only want APKs for x86, armeabi-v7a, and mips.

            // Resets the list of ABIs that Gradle should create APKs for to none.
            reset()
            // Specifies a list of ABIs that Gradle should create APKs for.
            include &quotmips&quot, &quotx86&quot, &quotx86_64&quot, &quotarm64-v8a&quot, &quotarmeabi-v7a&quot
            // Specifies that we do not want to generate a universal APK
            // that includes all ABIs.
            universalApk false
        }
    }
}

اگه این تنظیمات رو به اپ اضافه کنید ورژن‌های مختلفی از apk ‌رو برای معماری های متفاوت خروجی می‌گیره:

خروجی های مختلف apk  ‌بعد از فعال کردن تفکیک کننده ABI
خروجی های مختلف apk ‌بعد از فعال کردن تفکیک کننده ABI

عالیه! از 74KB رسیدیم به 5MB و دوباره برگشتیم به 1.3MB اما...

با اینکه الان یک ورژن مناسب از apk (که فقط شامل فایل های ضروری است) داریم ٬ اما این 5 تا apk همه خروجی یک اپلیکیشن هستن و هنوز بر اساس تراکم صفحه نمایش تفکیک نشدن.(خود تراکم بسته به کانفیگ دستگاه ٬ apk های مختلفی برای xhdpi ٬ xxhdpi و xxxhdpi تولید می‌کنه در حالی که این نوع تفکیک کردن خیلی بهتر از بهینه کردن کتابخونه های بومیه).

معمولا از فایل apk حداقل سه نسخه‌ی hdpi و xhdpi و xxhdpi خروجی می‌گیریم. در نتیجه الان 15 نسخه از apk برای انتشار در مارکت داریم. فرض کنیم هرماه یک اپدیت برای اپ منتشر میشه بنابراین باید 15 apk رو نگهداری کنیم و تصور کنین که مشتری/شرکت به شما بگه قبل از انتشار از درست کار کردن همه apk ها مطمئن بشین و همه رو تست کنین!!!

هنوز یک مشکل بزرگتر از این نگهداری طاقت فرسا وجود داره :(

در بازارهای نوظهور مثل هند (ایران هم کاملا صدق میکنه) که اینترنت همراه یا وای فای مثل کشورهای توسعه یافته ارزون نیست ٬ کاربران اپ رو به وسیله بلوتوث یا برنامه های shareit , xender و ... برای هم میفرستن و به صورت دستی نصب میکنن.

بحث درمورد اشتراک اپ ها
بحث درمورد اشتراک اپ ها


حالا اگه تفکیک ABI انجام داده باشین یک کاربر که گوشیش با معماری ARM کار می‌کنه اپ رو به یک کاربر دیگه با گوشی معماری x86 بفرسته به طور معمولی برنامه کرش می‌کنه. هیچکس نمیخواد که چپ و راست برنامش کرش کنه.

وقتشه که راه حل رو پیدا کنیم

یه راه وجود داره که میتونیم از مشکل بالا جلوگیری کنیم و اون استفاده از NDK abiFilters است.

تنظیمات مربوط به تفکیک ABI ‌رو پاک کنید و کد زیر رو به فایل build.gradle ‌سطح اپ اضافه کنید :

android {   
 defaultConfig {    
        applicationId &quotcom.android.abi.myapplication&quot      
         minSdkVersion 17  
            targetSdkVersion 25  
                                   ndk {
                                                 abiFilters &quotarmeabi-v7a&quot, &quotx86&quot   
                                            }   
                             }   
                 }

کاری که این گزینه انجام میده اینه که فقط کتابخونه‌ی معماری های مختلف رو که توی abiFilters نام برده شده رو نگه میداره. حالا فایل 5مگابایتی ما رسید به 2MB (اصلا بد نیست) با این کار میتونیم از از ساختن 5 apk یک مگابایتی یا یک apk پنج مگابایتی جلوگیری کنیم.

فایل apk با کتابخونه های 'x86' و 'armeabi-v7a'
فایل apk با کتابخونه های 'x86' و 'armeabi-v7a'


چی شد؟!! من تازه گفتم که هرگونه تغییری در ABI ممکنه در سمت کاربر باعث نتایجی بشه که انتظار نداریم اما الان پیشنهاد میکنم که کاملا کورکورانه ABI هارو پاک کنید!
برای فهم این موضوع باید وارد جزییات هر معماری بشیم.



معماری ها

جزییات مربوط به معماری های cpu
جزییات مربوط به معماری های cpu

اگه هنوز در مورد پاک کردن این کتتابخونه های مطمئن نیستین بذارین یه مثال بزنم که جرأت انجام این کارو رو میده. یه کنکاشی توی apk یونیورسال گوگل مپ v9.55.1 میندازیم :

MinSDK: Android 4.3 (Jelly Bean MR2, API 18)

با اینکه گوگل مپ بر اساس ABI و تراکم صفحه نمایش تفکیک میشه (گوگل مپ برای هر انتشار 17 apk مختلف داره) هنوز apk ‌یونیورسالش فقط از معماری های arm64-v8a و armeabi-v7a و X86 و X86–64 پشتیبانی می‌کنه.

بنابراین باید تصمیم بگیرید که یا از کتابخونه های 64 بیتی پشتیبانی کنین که حجم برنامه زیاد میشه یا معیارتون کارآمد وبهینه بودن (performance) برنامتون باشه که در این صورت بدون هیچ درنگی باید mipsو mips-64 و armeabi رو پاک کنید.




جزییات مربوط به هر معماری

armeabi, armeabi-v7a, and arm64-v8a


معماری armeabi :

یک معماری قدیمی مبتنی بر معماری ARM است. از اندروید 4.4 CDD (تعیین سازگاری) اکیدا توصیه کرده که از ARMv7 استفاده بشه. ( خوندن مستندات CDD )

اما CDD ویژگی هایی هستن که گوگل برای سازندگان دستگاه های اندرویدی (هر نسخه ای) مشخص کرده که هر دستگاه اندرویدی برای انتشار با مجوز گوگل باید حداقل نیازمندی هایی رو داشته باشه .
بیشتر تولیدکنندگان دستگاه های اندرویدی قبل از اندروید 4.4 به معماری armV7 مهاجرت کردن. بنابراین الان با خیال راحت می‌تونیم از armeabi ‌چشم‌پوشی کنیم.


معماری armeabi-v7a :

  • این معماری ٬ رایج ترین معماری در درسترس است و بیشترین پشتیبانی رو برای همه اپلیکیشن ها دارد.
  • 32 بیتی و پشتیبانی از :ARM Cortex-A5, ARM Cortex-A7, ARM Cortex-A8, ARM Cortex-A9, ARM Cortex-A12, ARM Cortex-A15, ARM Cortex-A17

معماری arm64-v8a :

  • نسل بعدی معماری 64 بیتی ARM ‌است و تقریبا همه گوشی های پرچم‌دار از این معماری استفاده می‌کنند.از نکسوس 5x تا نکسوس 6p و پیکسل گوگل از چیپست های 64 بیتی ARM استفاده می‌کنند.
  • 64 بیتی و پیشتیبانی از :ARM Cortex-A35, ARM Cortex-A53, ARM Cortex-A57, ARM Cortex-A72, ARM Cortex-A73
  • استفاده از این ABI ‌اختیاری است چون دستگاه‌های اندرویدی به جاش میتونن از armeabi-v7a استفاده کنند.استفاده از ورژن 32 بیتی کتابخونه های بومی روی پردازنده 64 بیتی مقدار ناچیزی در کاهش کارایی تاثیر داره اما این موضوع رو باید درنظر بگیریم که با انجام این کار می‌تونیم حدود 1MB از سایز apk کم کنیم.
  • اگه کتابخونه ای که استفاده می‌کنید جزو بخش اصلی برنامتون باشه و میخواین کاربر در گوشی های پرچمدار بهترین تجربه استفاده رو داشته باشه در این صورت بهتره که از این ABI (arm64-v8a ) استفاده کنید اما در بیشتر اپلیکیشن ها ٬ این ABI می‌تونه حذف بشه( تا وقتی که متوجه بشیم بیشتر کاربرا گوشی گوشی پرچمدار دارن).

معماری x86 :
دستگاه های مبتنی بر پردازنده های اینتل ٬ asus zenphone2 معروف ترین تلفن به همراه تعدادی زیادی از تلفن های لنوو از این معماری پشتیبانی می‌کنند.

معماری x86-64 :

هیچ دستگاه اندرویدی با پردازنده های 64بیتی اینتل در بازار نیست حتی اگه تعداد کمی هم وجود داشته باشن به خوبی از ورژن 32بیتی کتابخونه ها پشتیبانی می‌کنند پس میشه به حساب نیاورد.

معماری های mips and mips64 :

همچنین هیچ دستگاه اندرویدی با معماری MIPS در بازار وجود نداره. فکر میکنم یک دستگاه در سال 2016 قرار بود ساخته بشه اما هیچ اطلاعات دیگه ای جز این مقاله و این رشته پیدا نکردم.(حواستون به فیلتر باشه)



اگه تصمیم گرفتین که از این روش ها و فیلتر های ABI استفاده کنین موقع انتشار اپلیکیشن در کنسول گوگل پلی (کافه بازار یا مارکت های اندرویدی رو نمیدونم ) مثل عکس زیر به هشدار برمی‌خورین که جای نگرانی نیست فقط اطلاعاتی درباره تغییرات ایجاد شده است.
کنسول گوگل پلی هنگام انتشار اپلیکیشن دارای فیلتر های ABI
کنسول گوگل پلی هنگام انتشار اپلیکیشن دارای فیلتر های ABI

خب! منتظر چی هستین؟ امتحان کنین :)

این مطلب ترجمه مقاله ای در مدیوم هست که در مستندات رسمی Realm به این مقاله ارجاع داده شده است.

در پایان اگه نظر یا پیشنهادی دارین خوشحال میشم در زیر این پست بخونم :)


برروزرسانی :

همونطور که آقای نجفی در کامنت ها گفتن :
از آگوست 2019 ، گوگل پلی پشتیبانی از arm64-v8a رو اجباری کرده. این هم لینک توضیحات مربوطه:
https://developer.android.com/distribute/best-practices/develop/64-bit

ضمنا متاسفانه کافه بازار از همچین چیزی پشتیبانی نمی کنه. البته چون در کافه بازار arm64-v8a اجباری نیست، میشه فقط دو معماری armeabi-v7a و x86 رو قرار داد. پس حجم اپ خیلی زیاد نخواهد بود.

پس حواستون باشه :)