رهایی از محدودیت ۶۵هزار متد در اندروید

ماهیت شی گرایی زبان جاوا و فراهم کردن ساده‌ی امکان code reuse در کنار محبوبیت و قدمت این زبان باعث شده که کتابخانه‌ها و نمونه کدهای خیلی زیادی برای تسریع روند توسعه با جاوا به وجود بیان. محبوبیت اندروید در بین توسعه دهنده‌ها هم باعث شده که کتابخانه‌های این پلتفرم انقدر زیاد و متنوع بشن که تقریبا برای هر کاری یک کتابخانه‌ی آماده وجود داشته باشه. با مهاجرت گوگل هم به سیستم gradle استفاده از کتابخانه‌ها به سادگی آب خوردن شده. فقط یک خط اسم کتابخونه و ورژنش رو توی dependency فایل گریدل اضافه می‌کنیم! این نیمه‌ی پر لیوان بود، معمولا هیچ منفعتی به صورت خالص به دست نمیاد و همیشه به اصطلاح trade-off ای هست.

بعد از کامپایل هر فایل java یک یا چند فایل ‎.class (بسته به تعداد کلاس‌های داخل اون فایل) تولید می‌شه؛ اگر تا به حال کنکجاو شده باشید و یک فایل apk رو با نرم افزارهای فشرده ساز باز کرده باشید احتمالا متوجه وجود فایلی به اسم classes.dex شدید؛ این فایل ترکیب تمام فایل‌های ‎.class ای هست که بعد از کامپایل کد برنامه‌مون تولید می‌شن. کدهای مربوط به کتابخانه‌های استفاده شده هم همراه کدهای خودمون همه داخل این فایل هستند. اندروید، یا بهتر بگم Dalvik، در حین نصب و اجرای یک اپ به تمام متدهای تمام کلاس‌های کد یک شماره اختصاص می‌ده و اطلاعاتی رو درباره‌ی متدها در یک حافظه‌ی میانگیر (buffer) به اسم LinearAlloc نگهداری می‌کنه. متأسفانه اندازه‌ی این بافر محدود به ۶۵۵۳۶ (۲۱۶) هست و اگر اپ از کتابخانه‌های زیادی استفاده کرده باشه یا خیلی ماژولار نوشته شده باشه و تعداد متدها زیاد باشه خیلی راحت ممکنه این محدودیت رو رد کنیم و برنامه کامپایل نشه. یا حتا بدتر از اون کامپایل بشه ولی به محض اجرا اکسپشن بخوره. اکسپشن Class Not Found، در صورتی که می‌بینید توی کد قبل کامپایل کلاسی که می‌گه نیست واقعا وجود داره! در ادامه‌ی مطلب راه حل‌های رفع این مشکل رو بررسی می‌کنیم.

۱. استفاده از Android Lint

اولین گام برای کاهش تعداد متدها استفاده از Android Lint برای پیدا کردن متدهای استفاده نشده هست. Android Lint تحلیل‌های خیلی متنوع و مفیدی رو روی کد انجام می‌ده و با این تحلیل‌ها مشکلات احتمالی رو پیش‌بینی می‌کنه و گاهی پیشنهاد راه حل پیشگیری از مشکل رو هم می‌ده. در اندروید استودیو/‎IntelliJ IDEA منوی ‎Analyze ‎> ‎Inspect Code برای اجرای ‎Android Lint روی کد تعبیه شده. برای رفع مشکل 65k‎ ما فقط به تحلیل ‎Unused Declaration نیاز داریم که متدهایی رو پیدا می‌کنه که جایی استفاده نشدند.

image

از منوی Analyze > Run Inspection By Name می‌تونید پنجره‌ی بالا رو باز کنید و تحلیل Unused Declaration رو اجرا کنید.

نکته‌ی نسبتا ظریفی که حائز اهمیته اینه که با یک بار اجرای این تحلیل همه‌ی متدهای اضافه ممکنه پیدا و حذف نشن. عمل اجرای Lint و حذف متدها و احتمالا کلاس‌هایی که تمام اعضاشون حذف شدند رو باید تا جایی ادامه داد که متد بدون استفاده ای باقی نمونه. دلیل این مسئله اینه که ممکنه متدی مثلا به اسم F فقط داخل متدهای A و B استفاده شده باشه اما A و B خودشون جایی استفاده نشده باشند. Unused Declaration در دور اول اجرا فقط متدهای A و B رو معرفی می‌کنه. چون F به مستقیماً بی استفاده نیست، هرچند بعد از حذف A و B چون F در جای دیگری استفاده نشده در اجرای بعدی تحلیل Unused Declaration به عنوان متد بی استفاده معرفی می‌شه.

۲. استفاده از proguard

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

android {
    buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

اگر خوش‌شانس باشید با بهبودهایی که proguard میده اپ قابل استفاده می‌شه.

۳. پیدا کردن و جایگزینی کتابخانه‌های سنگین

راه بعدی اینه که بریم ریشه‌ی کار رو درست کنیم (چرا از اول نرفتیم سراغ این کار؟ :دی) Jake Wharton یه شِل اسکریپت داره که می‌تونه تعداد متدهایی که هر کتابخانه به کد اضافه کرده رو بشمره. با وجود پیاده‌سازی‌های متعدد یک کاربرد برای اندروید پیدا کردن نسخه‌های سبک‌تر و ساده‌تر کتابخانه‌های پیچیده‌ای که برامون دردسر ساز شدن نباید کار سختی باشه. برای این منظور سایت Android Arsenal گزینه‌ی خوبیه که کتابخونه‌های مختلف اندروید رو به صورت موضوعی دسته بندی و معرفی کرده.

مهم‌ترین کتابخانه‌ای که تعداد زیادی متد اضافه می‌کنه Google Play Services هست. این کتابخانه امکانات خیلی خوبی رو مثل Push Notification، Google Maps و حتا تشخیص حالت چهره برای برنامه فراهم می‌کنه، این امکانات نزدیک به ۳۰ هزار متد مصرف می‌کنند (تقریبا نصف تعداد متدهای قابل استفاده). در اکثر مواقع ما به تعداد محدودی از این امکانات نیاز داریم. خوشبختانه گوگل از نسخه‌ی ۶.۵ به بعد این امکان رو گذاشته که از اجزای مختلف این کتابخانه به صورت مجزا استفاده کنیم. مثلا برای اپی که از Google Analytics, Google Cloud Messaging و Google Maps استفاده می‌کنه کافیه بجای این خط:

compile 'com.google.android.gms:play-services:8.1.0'

این خط‌ها در dependencyهای پروژه باشند:

compile 'com.google.android.gms:play-services-analytics:8.1.0'
compile 'com.google.android.gms:play-services-maps:8.1.0'
compile 'com.google.android.gms:play-services-gcm:8.1.0'

برای مشاهده‌ی لیست کامل کتابخانه‌های جزئی Google Play Services کلیک کنید.

۴. استفاده از چند فایل Dex

راه حل نهایی استفاده از بیش از یک فایل classes.dex در کد برنامه‌هست. در این پست کد به سه بخش کد اصلی، کتابخانه و Interface ارتباط با کتابخانه تقسیم شده و فایل‌های dex مربوط به کتابخانه در پوشه‌ی Assets داخل فایل apk قرار می‌گیرن و بعداً در زمان اجرا با استفاده از Class Loader وارد حافظه‌ی گوشی می‌شن و ازشون استفاده می‌شه.

گوگل با کتابخانه‌ی multidex این کار رو خیلی ساده کرده. پلاگین gradle ای هم برای multidex نوشته و همراه با sdk نصب می‌شه که عمل تقسیم کدها به چند فایل dex رو به طور خودکار در زمان کامپایل انجام می‌ده. برای استفاده از این امکان باید:

۱. کتابخانه‌ی multidex به dependency ها اضافه بشه، multidex روی اپ فعال شه و گریدل یک بار Sync بشه.

defaultConfig {
    ...
    multiDexEnabled true
}

dependencies {
    ...
    compile 'com.android.support:multidex:1.0.0'
}

۲. کلاس اصلی برنامه (Application) بجای android.os.applicationاز android.support.multidex.MultiDexApplication ارث‌بری کنه یا اینکه کد زیر به این کلاس اضافه شه:

@Overrid
protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    MultiDex.install(this);
}

اگر کلاس Application ندارید میتونید مستقیما از کلاس android.support.multidex.MultiDexApplication به عنوان کلاس اصلی استفاده کنید.

Manifest.xml:
<application android:name="android.support.multidex.MultiDexApplication" ...>

این روش همیشه کار می‌کنه اما به دو دلیل از اول معرفی نشد، ۱. این روش زمان کامپایل رو به طور قابل توجهی زیاد می‌کنه. ۲. حذف dependencyها و کدهای اضافه راه حل بهتری نسبت به multidex شدن هستند و ۳. به بهانه‌ی معرفی روش‌های مختلف ابزارها و سایت‌ها و… جدیدی معرفی شن.

موفق باشین

Mohammad Jafar Mashhadi

Mohammad Jafar Mashhadi

Your average genius.

comments powered by Disqus
rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora