PDA

View Full Version : آموزش : حل خطای Out of Memory و Force Close هنگام کار با عکس ها (Bitmap)



poorman
شنبه 19 بهمن 1392, 19:12 عصر
سلام دوستان

من بالاخره مشکلم رو حل کردم ، به همین مناسبت گفتم زکات علمم رو بدم و واسه کسایی که احیانا در آینده دچار مشکل من میشن ، توضیح بدم تا نخوان مثل من 2 روز کامل کل اینترنت رو بگردن

ابتدا بذارین یه توضیح بدم که خطای OOM یا همون Out Of Memory به چه دلیل رخ میده
سیستم عامل اندروید برای اجرای هر برنامه ، مقدار محدودی از حافظه رم رو در اختیار اون برنامه میذاره که متناسب با نوع دستگاه ، بین 16 تا 128 مگابایت هست
این حافظه کم ، وقتی که عکس های با اندازه بالا رو توی خودش لود میکنه ، به سرعت پر میشه ، به همین دلیل برنامه force close میده و اگر توی log سیستم ببینید ، هنگام کار با عکس ها یا setContentView می تونید خطای OOM رو ببینید
البته میدونید که force close دلایل متعددی داره ، و این تنها یکی از دلایلش هست

خب من برای رفع مشکلم چکار کردم ؟؟؟ اومدم چند تا حرکت زدم
حرکت اول اینکه توی هیچ فایل xml عکسی رو لود نکردم از resource
حرکت دوم ، توی متد onCreate هر اکتیویتی اومدم عکس ها رو لود کردم و قرار دادم
خب تا اینجای کار شما بازم ارور میگیرید ، پس عملا کاری نکردین ، بنابراین قسمتی که مهمه کوچیک کردن عکس های بزرگ هست
پس حرکت سوم اینه که شما با استفاده از دو تا تابع که سایت اندروید داده واسه کار کردن با bitmap های بزرگ ، عکس هاتون رو در اندازه مناسب کوچیک کنید
حالا چند تا ابهام هست ، اینکه گوشی های مختلف سایز های مختلف دارن ، و شما نمیدونید اندازه مناسب برای کوچیک کردن عکس چیه
من چون توی برنامه با عرض و طول صفحه زیاد کار دارم ، یک راه حل ساده دارم
توی اکتیویتی splash screen هنگام خروج میام عرض و طول layout رو توی یک کلاس عمومی ذخیره میکنم
حالا روی اینا مانور نمیدم چون طولانی میشه ...

خب اگر تعداد اکتیویتی هاتون زیاد هست ، این دو تا تابع رو توی یک کلاس جدا به اسم Module بنویسین که استفاده از اونها راحت تر باشه

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

public static Bitmap decodeSampledBitmapFromResource(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);
}

خب حالا با استفاده از تابع decodeSampledBitmapFromResource می تونید عکس ها رو کوچیک کنید
یک مثال میزنم برای ست کردن بک گراند یک layout

Bitmap b = module.decodeSampledBitmapFromResource(getResource s(), R.drawable.reading_page, 480, 800);
readingPage = (LinearLayout) findViewById(R.id.readingPage);
readingPage.setBackgroundDrawable((BitmapDrawable) new BitmapDrawable(b));

خب الان عکستون به اندازه 480*800 کوچیک شد و ست شد
اگر باز هم ارور گرفتید توی این قسمت ، باید کیفیت عکس رو پایینتر بیارید
برای این کار توی تابع calculateInSampleSize که بالا نوشتم ، مقدار اولیه inSampleSize رو یکی اضافه کنید ، اگر باز هم ارور گرفتید مقدار رو به 3 تغییر بدید تا کیفیت به حداقل برسه ( من با مقدار 1 ارور میگرفتم )

خب تا اینجا شما عکس رو کوچیک کردید و ست کردین
برای احتیاط و اینکه هر وقت حافظه رو لازم نداشتین بتونین آزاد کنین ، انجام چند تا کار ضروری هست
اول اینکه متغیرهای Bitmap رو عمومی تعریف کنید
دوم در متد onStop هر اکتیویتی ، عکس تمام اشیاء و layout رو null کنید
فقط حواستون باشه که توی متد onRestart باید دوباره عکس اشیاء و layout رو مقدار بدین
سوم ، توی متد onStop تمام متغییر های Bitmap رو recycle کنید
b.Recycle();

کار تمومه ، دیگه خطای out of memory نخواهید گرفت

حالا موضوع دیگه اینکه ، شاید شما نخواین همیشه کیفیت و اندازه عکس رو بیارین پایین
راه حل خیلی ساده اینه که اول از راه معمولی عکس بک گراند یا هرچیزی رو ست کنید ، بعد اگر خطای OOM گرفتید با استفاده از try catch حالا مجبور هستید که کیفیت عکس رو بیارید پایین
بالاخره بهتر از کرش کردن برنامه هست
اینم یک نمونه کد برای این قسمت آخر
try {
readingPage.setBackgroundResource(R.drawable.readi ng_page);
} catch (OutOfMemoryError e) {
Bitmap b = module.decodeSampledBitmapFromResource(getResource s(), R.drawable.reading_page, 480, 800);
readingPage.setBackgroundDrawable((BitmapDrawable) new BitmapDrawable(b));
}

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

hosseinaryai
یک شنبه 18 خرداد 1393, 12:00 عصر
اوووممم خب حالا اگه بخوایم عکسی از توی sdcard بهش معرفی کنیم باید چیکار کنیم ؟

poorman
یک شنبه 18 خرداد 1393, 22:53 عصر
اوووممم خب حالا اگه بخوایم عکسی از توی sdcard بهش معرفی کنیم باید چیکار کنیم ؟

اوووومممم

عرضم به حضورتون توی تابع decodeSampleBitmapFrom... دو جا شما دارید از دستور bitmapFactory.decode استفاده میکنید

یک جا خط سوم تابع، و یک جا آخر تابع که bitmap رو return میکنید

این دو تا خط رو با این کد تعویض کنیم

BitmapFactory.decodeFile("pathName", options);

البته باید قبلش مسیر عکس رو بدید مسلما

hosseinaryai
دوشنبه 19 خرداد 1393, 07:55 صبح
اوووومممم

عرضم به حضورتون توی تابع decodeSampleBitmapFrom... دو جا شما دارید از دستور bitmapFactory.decode استفاده میکنید

یک جا خط سوم تابع، و یک جا آخر تابع که bitmap رو return میکنید

این دو تا خط رو با این کد تعویض کنیم

BitmapFactory.decodeFile("pathName", options);

البته باید قبلش مسیر عکس رو بدید مسلما

اوووممم :لبخند:
خب باید بگم خیلی ممنون .. کارمو راه انداخت

poorman
دوشنبه 19 خرداد 1393, 09:21 صبح
دوستان با توجه به اینکه این آموزش مربوط به 4 ماه پیش و وقتیه که اولین برنامم رو داشتم مینوشتم

نیاز به این داره که چند تا نکته رو اصلاح و اضافه کنم :

------------------------

برای تست برنامه روی شبیه ساز شما میتونید heap دستگاه رو توی AVD تعیین کنید، heap همون حافظه رم اختصاصی برای هر برنامه ست

خطای OOM معمولا وقتی پیش میاد که heap پایین تر از سطحی باشه که شما دارید عکس رو با اون سایز لود میکنید

مثلا یک عکس با کیفیت رو با اندازه 720*1200 یا بالاتر دارید توی heap با اندازه 32 یا پایین تر لود میکنید

پس خیلی راحت میشه نتیجه گرفت حتی با تغییر سایز عکس پس زمینه توی گوشی هایی که رزولوشن بالا دارن اما هیپ پایین دارن، خطا خواهیم گرفت

توی Genymotion اگر خواستید تست کنید، یک دستگاه با SDK ورژن 2.3 بسازید و رزولوشن 720*1200 و بالاتر

ورژن 2.3 توی geny دارای هیپ 32 هست، تست مناسبی میشه برای خطایابی پرشدن حافظه، اما قابل مقایسه با گوشی نیست

-----------------------

رم اختصاص داده شده به هر برنامه متغیر بین 16 تا 256 مگابایت هست، این مقدار بر اساس ورژن سیستم عامل و مشخصات دستگاه تعیین میشه

اطلاعات رم اختصاص داده شده به برنامه و کلاس هیپ رو با این کد به دست بیارید

Runtime rt = Runtime.getRuntime();
long maxMemory = rt.maxMemory();
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
Log.d("memory class", "max: " + maxMemory + ", class: " + memoryClass);

می تونید براساس یک آنالیز بین رزولوشن و اندازه هیپ، مقدار مناسب پایین اوردن کیفیت رو به دست بیارید

------------------------

توی یک قسمت من گفتم طول و عرض layout رو برای تغییر اندازه backGround میگیریم

این کار رو نمیشه توی متد onCreate انجام داد، چون توی این متد هنوز اشیاء ساخته نشدن و اگر شما بخواین طول و عرض رو بگیرید مقدار صفر برمیگردونه

اگر layout شما فول اسکرین و بدون اکشن بار و استاتوس بار هست میتونید با استفاده از متریک دستگاه اندازه طول و عرض کل صفحه نمایش رو به دست بیارید

DisplayMetrics metrics = this.getResources().getDisplayMetrics();
width = metrics.widthPixels;
height = metrics.heightPixels;

------------------------

اگر متغیرهای bitmap رو توی متد onStop پاک کنید ( recycle )، امکان داره وقتی دوباره به اکتیویتی برمیگردید با پیام خطا مواجه بشید

بهتره این کار توی متد onDestroy انجام بشه و قبلش شرط بذارید که اگر bitmap مساوی null نبود اون رو recycle کنه

------------------------

متغیر InSampleSize توی تابع CalculateInSampleSize بیانگر درجه پایین اوردن کیفیت عکس و از نوع int هست با مقدار اولیه 1

این مقدار بر اساس یک محاسبه بین اندازه اصلی عکس و اندازه خواسته شده، ممکنه در دو ضرب بشه

اگر شما عدد اولیه رو بذارید 2، ممکنه درجه کیفیت شما برابر 4 بشه

خب مسلما بین کیفیت 2 و 4 تفاوت خیلی زیاد میشه

بهتره شما با انجام یکسری تغییرات یک مقدار از نوع اعشاری رو قرار بدید و توی هر بار خطا گرفتن به اندازه 0.5 واحد بهش اضافه کنید که اگر ضرب در 2 شد، تفاوت بین کیفیت چشمگیر نباشه

مقدار اصلی که برگشت داده میشه حتما باید از نوع int باشه

roohola2434
شنبه 28 تیر 1393, 11:50 صبح
عرض سلام و خستع نباشید و تشکر از زحمات فراوان
من یک لیست ویو ساختم که بعد شش هفت بار بالا و پایین شدن oom میده
لیست ویو من کستومه و توش هم عکس داره هم متن
اولش خوبه کم کم سرعتش کم میشه تا این که از برنامه خارج میشه

roohola2434
شنبه 28 تیر 1393, 11:52 صبح
نمیشه به جای این کارا بیایم اضافه های حافظه رم رو پاک کنیم؟

poorman
شنبه 28 تیر 1393, 21:05 عصر
نمیشه به جای این کارا بیایم اضافه های حافظه رم رو پاک کنیم؟

سلام

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

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

-----------------

برای مشکل شما که میگید توی لیست ویو دارید نمایش میدید، احتمالا سایز عکس هاتون بزرگه

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

اگر مثلا میخواین بعدا اون عکس رو بزرگ نمایش بدید، عکسها رو در دو سایز کوچیک و بزرگ قرار بدید، توی لیست ویو عکس های کوچیک رو نمایش بدید و وقتی کلیک شد عکس بزرگ

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

roohola2434
دوشنبه 30 تیر 1393, 12:36 عصر
این هایی که میفرمایین همه درست اما مال من کل ایتم ها رو میسازه و مشکلی نداره اما بعد چند بار بالا و پایین کردن لیس ویو ارور out of memory میده
احساس میکنم با بالا و پایین کردن لیست ویو ایتم هاشو دوباره میسازه و روی هم جمع میکنه
نمیشه ایتم هایی که از صفحه خارج شدن رو حذف کرد؟

poorman
دوشنبه 30 تیر 1393, 13:12 عصر
ببینید شما وقتی مثلا 100 تا آیتم دارید و همشون عکس هستن، امکان داره همون اول کرش نکنه، چون 100 تا آیتم رو با هم نمیسازه، فقط همون 4-5 تای اول رو میسازه و برای هر اسکرول آیتم های بعدی که قراره نمایش داده بشن ساخته میشه

خود اندروید آیتم هایی که از صفحه خارج میشن رو حذف میکنه و تا نیاز نباشه آیتمی رو نمایش بده اون رو نمیسازه توی لیست ویو

کار ساخت توی getView آداپتر انجام میشه، اما کار حذف دست ما نیست و نمیدونم آیا از حافظه هم پاک میکنه یا نه

یعنی اگر دقت کنید هر بار که اسکرول میکنید ( بالا یا پایین ) متد getView برای آیتم هایی که میان توی صفحه اجرا میشه

من همین الان یک جستجو زدم و با این لایبری آشنا شدم Android Universal Image Loader

https://github.com/nostra13/Android-Universal-Image-Loader

هنوز باهاش کار نکردم، اما ظاهرا که خیلی کارایی داره، میتونید نمونه کدهاش رو بگیرید و طریقه استفادش رو یاد بگیرید

کار خاصی نداره، یک فایل jar رو میذارید توی libs و دو تا پرمیشن باید اضافه کنید و بعد از لایبری استفاده کنید

ho3ein.3ven
دوشنبه 30 تیر 1393, 13:46 عصر
عرض سلام و خستع نباشید و تشکر از زحمات فراوان
من یک لیست ویو ساختم که بعد شش هفت بار بالا و پایین شدن oom میده
لیست ویو من کستومه و توش هم عکس داره هم متن
اولش خوبه کم کم سرعتش کم میشه تا این که از برنامه خارج میشه


سلام منم چند وقت پیش همین مشکل رو داشتم .

مشکل از getView آداپترت هست .

یک سرچ بزن سورس یه custom listview درست رو بگیر.

roohola2434
دوشنبه 30 تیر 1393, 14:21 عصر
اره فک کنم حق با شماست
من از روی اموزش http://androidcode.ir/post/custom-list ساختم
فک کنم اموزشش مبدتیه و مشکل oom داره
یه مثال دیگه دیدم تو اینترنت ولی سردرنیاوردم
اگه راهنماییم کنید ممنون میشم

ho3ein.3ven
دوشنبه 30 تیر 1393, 15:23 عصر
از این سورس استفاده کن :

http://androidexample.com/How_To_Create_A_Custom_Listview_-_Android_Example/index.php?view=article_discription&aid=67&aaid=92

roohola2434
دوشنبه 30 تیر 1393, 17:35 عصر
ممنون نگاش کردم کلی روش فک کردم ولب متوجه نشدم
فک کنم هنوز زوده برام
فقط اگه میشه بگین چی بخونم میتونم اینا رو بفهمم؟

ho3ein.3ven
دوشنبه 30 تیر 1393, 22:37 عصر
سورسش رو دانلود کن کاملا معلومه

HDsharp
چهارشنبه 12 اسفند 1394, 03:56 صبح
ممنون نگاش کردم کلی روش فک کردم ولب متوجه نشدم
فک کنم هنوز زوده برام
فقط اگه میشه بگین چی بخونم میتونم اینا رو بفهمم؟

سلام دوستان
داخل سایت زیر مطلب مفیدی درباره با این خطا و روش پیداکردن و برطرف کردنش نوشته
http://androidgate.ir/165/fixing-memory-leaks-in-android-outofmemoryerror/