مشخصات مقاله
-
1838
-
0.0
-
4242
-
0
-
0
بالا بردن کارایی و بهینه سازی حافظه در اندروید
آموزش حاضر به شرح نحوه ی استفاده ی بهینه از حافظه و افزایش کارایی اپلیکیشن می پردازد. کدهای این بخش داخل محیط برنامه نویسی Android Studio تست و اجرا شده است.
رهنمودهایی جهت طراحی و ارائه ی اپلیکیشن های کارامد
چرا باید در استفاده از منابع اندروید مراقب بود؟
دستگاه های اندروید نسبت به کامپیوترهای رومیزی یا لپ تاپ قدرت و توانایی کمتری دارند. به همین دلیل بایستی در استفاده از حافظه دقت بیشتری داشته باشید. به ویژه از ویرایش 5.0 اندروید قبل تر، توسعه دهنده باید مراقب باشد تا حد امکان garbage collector دستگاه مجازی جاوا (JVM) را درگیر نکند چرا که این امر سبب freeze و هنگ کردن runtime اندروید به مدت 200 میلی ثانیه می شود. در چنین شرایطی چنانچه کاربر، برای مثال، با نوار اسکرول در حال پیمایش به پایین لیست باشد، با توجه به تاخیر قابل توجهی که garbage collector سبب می شود، تجربه ی کاربری به شدت پایین می آید.
اجتناب از تخصیص و ایجاد آبجکت غیر ضروری
سعی کنید تا حد امکان از ایجاد آبجکت های غیر ضروری، به خصوص در جاهای هزینه بر خودداری نمایید. توصیه می کنیم آبجکت های موجود را بازیافت نموده و مجددا از آن ها استفاده کنید. ایجاد آبجکت های اضافی و غیر ضروری سبب راه اندازی مکرر garbage collector می شود. می توان با اجتناب از ساخت آبجکت های غیر ضروری به راحتی از این رخداد جلوگیری نمود.
برای مثال بهتر است از ایجاد آبجکت در حلقه ها یا در بدنه ی متد onDraw() داخل آبجکت view اختصاصی خود خودداری نمایید.
استفاده از data structure های کارامد
اندروید چندین نمونه با پیاده سازی های مختلف از کلاس های Sparse*Array ارائه می دهد. به کد زیر توجه کنید.
Map<integer, string> map = new HashMap<integer, string>();
استفاده از این کد سبب ساخت آبجکت های غیر ضروری از نوع Integer می شود.
محیط اندروید جهت نگاشت مقادیر به دیگر آبجکت ها، data structure های کارمدتری را در اختیار توسعه دهنده قرار می دهد. در صورت امکان توصیه ی ما بر این است که از این ساختارها استفاده نمایید چرا که این ساختارها از ساخت آبجکت اضافی جلویگری می کنند. از جمله می توان به HashMap اشاره کرد.
همان طور که در بالا نیز گفته شد، ساخت آبجکت عملیات سنگین و هزینه بری بوده و تا حد امکان می بایست از رخداد غیر ضروری آن جلوگیری نمود. زیرا به دنبال کاهش تعداد دفعات ایجاد آبجکت، garbage collector نیز دفعات کمتری جهت مدیریت حافظه فراخوانده می شود.
جدول زیر مثال هایی از کاربرد SparseArrays را نمایش می دهد.
The table give examples for SparseArrays.
به منظور بهینه سازی مثال کاربردی فوق، بهتر است از data structure زیر استفاده نمایید.
SparseArray<string> map = new SparseArray<string>(); map.put(1, "Hello");
مدیریت bitmap
Bitmap ها (عکس های پیکسلی) ، چنانچه در اندازه ی کامل و حقیقی بارگذاری شوند، مقدار زیادی از حافظه را اشغال خواهند کرد. توصیه می شود bitmap ها را فقط در اندازه ی مورد نیاز بارگذاری کنید.
فرض کنید اپلیکیشنی طراحی کردید که تصویری در ابعاد 100x100 dp را نمایش می دهد، برای این اپلیکیشن می بایست تصویر مربوطه را دقیقا در همین اندازه داخل حافظه بارگذاری نمایید.
یک روش معمول این است که با ارسال یک flag به BitmapFactory، bitmap دلخواه را بدون اینکه در حافظه بارگذاری شود، اول اندازه گیری نمایید.
// instruct BitmapFactory to only the bounds and type of the image BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); // get width and height int imageHeight = options.outHeight; int imageWidth = options.outWidth; // type of the image String imageType = options.outMimeType;
پس از آن می توانید نسخه ی درست اندازه بندی شده ی تصویر مورد نظر را در حافظه بارگذاری نمایید. اندروید تصاویر را به توان 2 می رساند. می توانید با استفاده از متد زیر scale factor (ضریب مقیاس بندی) را بر مبنای 2 محاسبه نمایید.
public static Bitmap decodeBitmapWithGiveSizeFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;}}
return inSampleSize;}
با استفاده از این متد می توان تصویر مورد نظر را همان طور که در مثال زیر نمایش داده شده، به view تخصیص داد.
viewWidth = imageView.getWidth();
viewHeight = imageView.getHeight();
imageView.
imageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, viewWidth, viewHeight));
استفاده از Cache
استفاده از حافظه ی نهان
همان طور که می دانید فرایند ساخت آبجکت بسیار سنگین و هزینه بر است. Cache این امکان را فراهم می کند تا آبجکت هایی که قبلا در حافظه موجود هستند را بازیافت نموده (مجددا مورد استفاده قرار داد) و بدین وسیله از تخصیص یافتن مجدد منابع به ساخت آبجکت (جدید) خودداری گردد. به عبارتی، زمانی که شما آبجکتی را در حافظه بارگذاری می کنید، می توانید این حافظه را به عنوان cache و حافظه ی موقت برای میزبانی آبجکت ساخته شده در نظر بگیرید. به عنوان مثال زمانی را در نظر بگیرید که تعدادی فایل تصویری را از اینترنت بارگیری می کنید. در این لحظه، جهت نمایش فایل های ذکر شده در یک لیست، می بایست آن ها را به طور موقت در حافظه نگه دارید. امر مانع از این می شود که عکس ها بارها از اینترنت دانلود شوند.
شما به ناگذیر بایستی برخی از آبجکت های مستقر در حافظه را بازیافت نمایید چرا که در غیر این صورت با کمبود حافظه مواجه خواهید شد. یک روش پیشنهادی این است که آبجکت هایی که طولانی مدت در اپلیکیشن مورد استفاده قرار نگرفته اند را بازیافت نمایید.
محیط (platform) اندروید از API 12 (ورژن 12 کتابخانه های اندروید) کلاسی به نام LruCache را ارائه می نماید (لازم به ذکر است که این قابلیت در کتابخانه های پشتیبانی از ورژن های قبلی اندورید support-v4 library نیز قابل دسترسی می باشد). کلاس مزبور امکان پیاده سازی یک cache جهت نگهداری آبجکت هایی که مدت طولانی مورد استفاده قرار نگرفته اند را فراهم می سازد. به عبارت دیگر این کلاس یک LRU cache پیاده سازی می کند. در این cache تمامی اعضایی که مدت ها از آخرین بار استفاده از آن ها می گذرد، قرار می گیرند ( آبجکت هایی که کمترین استفاده ی اخیر را داشته اند در این حافظه ی موقت جای می گیرند). این cache ظرفیت مشخصی دارد و هنگامی که این ظرفیت پر می شود، تمامی آیتم هایی که طولانی مدت مورد استفاده قرار نگرفته اند بر حسب نیاز از این cache حذف می شوند. این قابلیت تصویر زیر نمایش داده شده است.
مثال زیر یک نمونه از پیاده سازی کلاس LruCache را برای ذخیره ی موقتی فایل های تصویری فراهم می سازد.
public class ImageCache extends LruCache<string, bitmap> {
public ImageCache( int maxSize ) {
super( maxSize );
}
@Override
protected int sizeOf( String key, Bitmap value ) {
return value.getByteCount();
}
@Override
protected void entryRemoved( boolean evicted, String key, Bitmap oldValue, Bitmap newValue ) {
oldValue.recycle();
}
}
همان طور که در نمونه کد زیر مشاهده می کنید، استفاده از آن بسیار ساده می باشد.
LruCache<string, bitmap> bitmapCache = new LruCache<string, bitmap>();
جهت اطلاع از ظرفیت حقیقی cache، کافی است اندازه و ظرفیت کل حافظه ی قابل استفاده در دستگاه میزبان را بررسی نمایید. برای این منظور می توانید از کلاس MemoryClass استفاده نمایید. کد زیر استفاده از آن را به نمایش می گذارد.
int memClass = ( ( ActivityManager) activity.getSystemService( Context.ACTIVITY_SERVICE ) ).getMemoryClass(); int cacheSize = 1024 * 1024 * memClass / 8; LruCache cache = new LruCache<string, bitmap>( cacheSize );
پاک سازی cache
از API 14 این امکان برای شما وجود دارد که متد onTrimMemory() را در کامپوننت های نرم افزاری اندورید بازنویسی (override) نمایید. این متد را اندروید فراخوانی نموده و به شما این اجازه را می دهد تا زمانی که سیستم منابعی را جهت پردازش های پیش زمینه ای (foreground process) لازم داشت، محتوای حافظه را پاک سازی و آزاد نمایید.