خانه » ادوبی ایر » بهینه سازی فلش اجرای غیرهمزمان حلقه

بهینه سازی فلش اجرای غیرهمزمان حلقه

بهینه سازی فلش اجرای غیرهمزمان حلقه

یکی از مهمترین مسائلی که توسعه دهندگان Flash و Air با آن سر و کار دارند، مسئله بهینه سازی پروژه به منظور افزایش کارایی اپلیکیشن می‌باشد. عدم توجه به مسئله بهینه سازی در توسعه یک اپلیکیشن منجر به افت شدید کارایی نرم افزار و بنابراین افزایش حافظه هدررفته سیستم می‌شود که این امر موجب کنار گذاشته شدن اپلیکیشن توسط کاربران آن خواهد شد.

برای مثال دو اپلیکیشن کاملا مشابه را درنظر بگیرید که توسط دو توسعه دهنده گسترش یافته اند به طوری که در یکی کدها بهینه شده اند ولی در دیگری تنها یک سری کد پشت سرهم نوشته شده است. در این حالت هر دو اپلیکیشن یک خروجی مشابه خواهند داشت ولی مطمئنا کاربران اپلیکیشنی را انتخاب خواهند کرد که کارایی بالاتری دارد و بهینه شده است. برای توسعه دهندگان اپلیکیشن، توجه به این نکته الزامی‌است که کاربران در هنگام پردازش عملیات‌های سنگین، مشاهده یک Loading متحرک را به هنگ کردن سیستمشان ترجیح خواهند داد، حتی اگر عملیات پردازش در حالت هنگ سریعتر انجام شود.

  • اصول ابتدایی اجرای کد در حالت Runtime

اولین قدم در بحث بهینه سازی کدنویسی در Actionscript و Air شناخت نحوه اجرا و پردازش کدها توسط Flash Player و Air Debugger می‌باشد. عملیات پردازش کدها در هر فریم به صورت متناوب و در یک حلقه صورت می‌پذیرد. طبق این تعریف، فریم عبارت است از مقطعی از زمان که طول آن توسط مقدار Frame per Second (تعداد فریم در یک ثانیه) تعریف می‌شود. برای مثال درصورتی که مقدار Frame per Second برابر با ۲۴ باشد، آنگاه طول هر فریم برابر با یک بیست و چهارم ثانیه (حدود ۴۲ میلی ثانیه) خواهد شد. مقدار Frame per Second در نرم افزار Flash به راحتی در پنل Timeline قابل دسترسی و تغییر است. همچنین به کمک کد زیر در کلاسه اصلی پروژه نیز می‌توان مقدار Frame per Second اولیه را تعیین نمود.

[SWF(frameRate="24")]

هر حلقه فریم شامل دو فاز می‌شود که عبارتند از فاز اجرای رویدادها (events) و فاز پردازش تصویر  (render) فریم. فاز اول را می‌توان متشکل از دو بخش دانست، بخش اول منحصرا رویداد مربوط به enterFrame است که همواره یکبار در ابتدای فریم ارسال (dispatch) می‌شود (حتی اگر Listener آن تعریف نشده باشد) و بخش دوم شامل تمامی‌رویدادهایی می‌شود که توسط کدنویس یک Listener برایشان تعریف شده است و باید در ابتدای فریم ارسال شوند (مانند رویداد مربوط به پاسخ شبکه بعد از بارگزاری اطلاعات و یا رویدادهای وردودی توسط کاربر). درصورتی که کدنویس هیچ رویدادی را (در بخش دوم) تعریف نکرده باشد، زمان پردازش فریم کوتاهتر نخواهد شد و Actionscript Virtual Machine یا AVM به صورت بیکار خواهد ماند تا زمان پردازش تصویر فرا برسد. AVM بیانگر بخشی از حافظه حفاظت شده سیستم است که عملیات پردازش کد (Actionscript 2.0 یا Actionscript 3.0) در Flash Player یا Air Debugger در آن صورت می‌گیرد. بعبارت دیگر Frame rate هیچگاه بعلت نبود عملیات برای اجرا، سریعتر نخواهد شد. بعد از ارسال تمامی‌رویدادها، عملیات پردازش تصویر (فاز دوم در حلقه فریم) آغاز می‌شود. در این فاز ابتدا حالت نمایش یا عدم نمایش یک شی (Visibility) بررسی می‌شود و سپس عملیات ترسیم اشیایی که مقدار visible آنها true است، انجام می‌شود. باید به این نکته توجه کرد، زمانی که مقدار Alpha برای یک شی صفر باشد با حالتی که مقدار visible آن شی false است، تفاوت دارد و عملیات ترسیم برای شی با alpha صفر هم انجام خواهد شد. پس هرگز به جای false کردن مشخصه visible یک شی، Alpha آن شی را صفر نکنید.

توجه: در رویدادهایی که شامل مشخصه updateAfterEvent هستند، عملیات پردازش تصویر را می‌توان مجبور به اجرا قبل از شروع فاز دوم کرد. این امر می‌تواند منجر به کاهش کارایی پروژه شود، بنابراین از استفاده بی دلیل updateAfterEvent جدا پرهیز نمایید.

در یک نگاه ساده انگارانه می‌توان فرض کرد که فاز اول و دوم در یک حلقه فریم در زمان‌های مشابه انجام می‌شوند (برای مثال در Frame per Second برابر با ۲۴ مقدار طول هر فاز حدود ۲۱ میلی ثانیه می‌شود). اما در واقعیت زمان لازم برای اجرای هر فاز با یکدیگر متفاوت هستند. گاهی زمان لازم برای فاز اول افزایش می‌یابد که منجر به کاهش سهم فاز دوم (پردازش تصویر) می‌شود. همچنین در برخی حالات بویژه حالاتی که از filter‌ها و blend mode‌ها استفاده می‌شود، عملیات پردازش تصویر طولانی می‌شود و سهمی‌بیشتر از نصف طول فریم برای فاز دوم نیاز خواهد بود. درنتیجه در شرایطی که هر دو فاز طولانی هستند، runtime قادر به نگهداری از Frame per Second نخواهد بود و بنابراین طول هر فریم از مقدار مجاز آن بیشتر خواهد شد که منجر به بروز تاخیر در اجرای فریم بعدی می‌شود. در بدترین حالت، بروز این اتفاق می‌تواند منجر به فریز شدن اپلیکیشن و هنگ کردن سیستم شود. در شکل ۱، نحوه تقسیم بندی یک فریم به فازهای مختلف دیده می‌شود.

general_frame_division

شکل ۱- تقسیم فریم به دو فاز پردازش کد و تصویر. در عمل یک سری زمان‌های بیکاری بعد از هر پردازش کد وجود دارد. [منبع: senocular.com]

گاهی اوقات، زمان پردازش فریم آن قدر طولانی می‌شود که Flash Player یا Air Debugger از کار می‌افتند و پیام خطای Timeout مشاهده خواهد شد (شکل ۲). برای رفع این مشکل لازم است که عملیات‌های طولانی شکسته شوند (یعنی در چند حلقه فریم) انجام شوند.

timeout_dialog

شکل ۲- پیام خطای Timeout. [منبع: senocular.com]

  • تقسیم عملیات‌های طولانی حلقه به چند قسمت کوچکتر (Chunk)

در این قسمت با ذکر یک مثال، روش اجرای غیرهمزمان حلقه آموزش داده می‌شود. یک حلقه for طولانی (برای مثال شامل ۱۰۰۰ مرحله) را درنظر بگیرید به طوری که هر مرحله شامل یک عملیات پردازش زمانبر (مثل اضافه کردن اشیا به stage) باشد.

for (var i:int = 0; i < 1000; i++) {
    trace(i);
}

در این حالت فاز اول زمانی خاتمه می‌یابد که تمامی‌مراحل حلقه for اجرا شده باشد. سپس در فاز دوم، عملیات trace (و یا عملیات اضافه کردن اشیا به stage و پردازش تصویر) اجرا می‌شود. بدیهی که اجرای این حلقه for طولانی در حالت عادی منجر به هنگ کردن موقت اپلیکیشن خواهد شد. برای رفع این مشکل باید حلقه را به صورت غیرهمزمان اجرا کنیم. مثال زیر را در نظر بگیرید:

import flash.events.Event;
import flash.text.TextField;

var savedIndex = 0;
var breakSetp = 20;

addEventListener(Event.ENTER_FRAME, onEnterFrame);

function onEnterFrame(e:Event) {

    var i:int;
    var max:int = 1000;
    for (i=savedIndex; i < max; i++){

        process(i);

        if (needToExit(i)){
            savedIndex = i;
            if (savedIndex == Math.floor((max - 1) / breakSetp) * breakSetp) {
                removeEventListener(Event.ENTER_FRAME, onEnterFrame);
            } else {
                break;
            }
        }

    }

    onResetFrameLoop();
}

function process(_input:int):void {
    trace(_input);
}

function needToExit(_input:int):Boolean {
    if (_input - savedIndex == breakSetp) {
        return true;
    } else {
        return false;
    }
}

function onResetFrameLoop():void {
    if (hasEventListener(Event.ENTER_FRAME)) {
        trace("New Chunk");
    } else {
        trace("Finished.");
    }
}

متغیر breakSetp: در این مثال نیز یک حلقه طولانی for با ۱۰۰۰ مرحله (متغیر max) وجود دارد. اما کد این حلقه به صورتی نوشته شده است که در یک حلقه فریم به صورت کامل اجرا نشود تا از هنگ کردن اپلیکیشن جلوگیری شود. بدین منظور یک متغیر به نام breakSetp تعریف شده است که مشخص کننده تعداد مراحل در هر حلقه فریم است. برای تعیین دقیق breakSetp روش مشخصی وجود ندارد و می‌توان آن را به روش سعی و خطا به دست آورد. یعنی باید زمان تقریبی عملیات‌هایی که در هر مرحله انجام می‌شود را محاسبه کرد و سپس با یک ضریب اطمینان، مقدار breakSetp  را مشخص نمود.

متغیر savedIndex: از این متغیر برای تعیین مقدار شروع کننده حلقه for در فاز اول حلقه frame استفاده می‌شود. مقدار آن زمانی که خروجی تابع needToExit برابر با true باشد، تعیین می‌شود. در تابع needToExit مشخص می‌شود که آیا تعداد مراحل هر حلقه فریم (برابر با متغیر breakSetp) طی شده است یا خیر.

Listener مربوط به enterFrame: همانطور که گفته شد رویداد enterFrame همواره در فاز اول ارسال می‌شود. بنابراین یک Listener برای آن تعریف شده است تا بعد از ارسال آن و با شروع حلقه فریم جدید، حلقه for ادامه پیدا کند. در این حلقه for یک سری عملیات می‌تواند انجام شود که توسط تابع process مشخص می‌شود. سپس وضعیت تکرار حلقه for توسط تابع needToExit چک می‌شود تا درصورت نیاز و با اجرای دستور break، حلقه for متوقف شود. بعد از توقف حلقه for تابع onResetFrameLoop فراخوانی می‌شود که بعد از اجرای آن، فاز اول حلقه فریم خاتمه یافته و فاز دوم شروع می‌شود. بعد از اتمام فاز دوم، مجددا حلقه فریم تکرار می‌شود و تا وقتی که دستور removeEventListener اجرا نشده است، تابع onEnterFrame با ارسال رویداد enterFrame فراخوانی خواهد شد. دستور removeEventListener  زمانی اجرا می‌شود که مطمئن شویم دیگر نیازی به فراخوانی تابع onEnterFrame به منظور اتمام مراحل حلقه for نداریم. برای تعیین این زمان از شرطی که برای savedIndex نوشته شده است، استفاده کرده ایم.

تابع onResetFrameLoop: به وسیله این تابع از زمان اتمام تمامی‌مراحل حلقه for و همچنین زمانهای هر break مطلع می‌شویم. همانطور که گفته شد، بعد از اطمینان از اتمام مراحل حلقه for با دستور removeEventListener، فراخوانی تابع onEnterFrame متوقف می‌شود، بنابراین در این حالت مقدار خروجی hasEventListener، برابر با false خواهد بود.

  • حل مشکل حلقه‌های for each in

در روشی که اشاره شد، مشکل هنگ کردن سیستم تنها برای حلقه‌های for دارای اندیس (index) قابل حل می‌باشد. اما در حلقه‌های for each in که اندیس شمارنده (مثلا i) وجود ندارد، مشکل هنگ کردن سیستم کماکان وجود خواهد داشت. راه حل پیشنهادی در این موارد تبدیل این حلقه‌ها به یک حلقه اندیس دار با استفاده از یک آرایه است. به مثال زیر توجه کنید:

var indexedList:Array = new Array();

for each (_value in _object) {
    indexedList.push(value);
}

for (var i:int = 0; i < indexedList.length; i++) {
    //...
}

در این مثال ابتدا تمامی‌مقادیر مورد نیاز در یک آرایه قرار می‌گیرد. در ادامه می‌توان مشابه مثال قبل یک عملیات طولانی را در یک حلقه اندیس دار انجام داد. به هرحال در مرحله اول که عملیات تبدیل حلقه انجام می‌شود نیز با هنگ موقت سیستم مواجه خواهیم بود، اما زمان این هنگ بسیار کوتاهتر از حالتی است که یک عملیات زمانبر سنگین در همان ابتدا و در حلقه for each in انجام شود.

  • جمع بندی

۱- به جای false کردن مشخصه visible اشیا، هرگز alpha آنها را صفر نکنید (ممکن نتیجه نهایی مشابه باشد اما عملیات پردازش متفاوت خواهد بود).

۲- از updateAfterEvent، filter و Blend mode تنها در مواقع لزوم استفاده کنید، زیرا منجر به کاهش کارایی اپلیکیشن می‌شوند.

۳- حلقه‌های for طولانی را به صورت غیرهمزمان اجرا کنید و آنها را به قطعات کوچکتر تبدیل کنید.

۴- حتی المقدور سعی کنید از حلقه for each in طولانی استفاده نکنید. درصورت نیاز آنها را به حلقه‌های اندیس دار تبدیل کنید.

۵- سعی کنید در صورت امکان از مقادیر کمتر برای Frame per Second استفاده کنید. در LCD‌های معمولی حتما کمتر از ۶۰ fps را انتخاب کنید.

 

Print Friendly
تگ ها :
پیوند مطلب قبلی
پیوند مطلب بعدی

درباره بهروز پولادرگ

متولد : ۵ مرداد سال ۱۳۶۷ ، حرفه : ادوبی فلش ، برنامه نویسی و طراحی وب ، طراحی گرافیک ، مالتی مدیا ، نرم افزار های تحت وب

26 نظر

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

  2. سلام دوست عزیز . به نظر میرسه شما از کد نویسی چیزایی می دونید.
    می تونید منو راهنمایی کنید که چطور برای یک عکس امکان کلیک بذارم.یعنی وقتی روش کلیک می کنم بره یه وبلاگ مشخص.
    کدhtml چجوریه؟

    • سلام ، البته سوال شما به مطلب ربطی ندارد
      ولی برای لینک می بایست از تگ A در HTML استفاده کنید که به صورت زیر هست
      link
      به جای متن link اگر می خواهید تصویر را لینک کنید تگ IMG مربوطه به تصویر را می بایست قرار دهید
      این کار را در فلش و اکشن اسکریپت ۳ نیز در بخش مقالات آموزش داده ام.
      موفق و پیروز باشید

  3. با سلام
    ممنون از مطالب خوبی که قرار دادید

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

    ۱. آموزشگاهی سراغ دارید که اکشن اسکریپت۳ رو به طور کامل آموزش بده ؟

    ۲. راهی برای برداشتن قفل فایل های فلش (که با SWF Protection و یا amyta encrypt کد گذاری شدن ) هست ؟

    با سپاس

    • سلام
      ۱ – تا آنجایی که بنده اطلاع دارم خیر.
      ۲ – به زودی در همین سایت سرویسی برای برداشتن قفل های فلش ها و مهندسی معکوس آنها راه اندازی می شود ، البته طبیعا” برای انجام این کار هزینه ای دریافت خواهد شد
      موفق و پیروز باشید

  4. سلام
    جالب بود.
    بنده معمولا سعی میکنم که در پروژه های فلش کاملا غیر همگام و asynchrone عمل کرده و در صورت لزوم کلاسهایی را به عنوان worker برای پردازشهای سنگین تعریف کنم.

  5. سلام خسته نباشین میتونم بپرسم چرا سوالائی که مطرح می کنم رو پاک میکنید؟

  6. سلام و خسته نباشید
    آقای پولادرگ میدونم که سوالم ربطی به موضوع نداره اما لطفا اینو جواب بدین
    من یه فایل ساختم که توش تکستی هست که کاربر باید جواب رو تو اون بنویسه بعد با زدن دکمه نتیجه ی کار براش به نمایش در میاد
    مشکل اینجاست که از فریم دوم به بعد دیگه نتیجه رو نشون نمیده
    آدرس فایلو براتون میزارم لطفا یه نگاهی بکنین
    http://uplod.ir/21wakldxqa78/JADVALEZARB.fla.htm

    • سلام
      می بایست نتایج را درون یک یا چند متغیر ذخیره کنید این کار را در تایم لاین انجام دهید ویا فقط تعریف متغیرها را در تایم لاین انجام دهید و مقدار دهی را از جایی دیگر که مورد نظر شماست. البته بهتر این هست که هر دو در تایم لاین باشد.
      موفق و پیروز باشید

  7. سلام و عرض ادب
    من ۲ تا سوال دارم ، ممنون میشم جواب بدید

    ۱ _ میخوام با ادوبی ایر ، یه اپلیکیشن برای تلفن هایی که سیستم عامل اندروید دارن بسازم، حساب کردم حجمش حدودا ۵۰ یا ۶۰ مگابایت میشه البته بطور بهینه هم با as3 برنامه نویسیش میکنم. سوالم اینه که فلش cs6 میتونه این خروجی سنگین رو به من بده ؟؟؟ و اگه میده ،وقتی نرم افزار رو روی موبایل نصب کنم ، موبایل به اصطلاح ” هنگ ” نمیکنه ؟؟؟
    ( همه فایلها داخل نرم افزاره ، و قرار نیست چیزی از بیرون لود بشه )

    ۲_ SDK3.2 ورژن چند به بعد از اندروید و IOS هست ؟؟؟ SDK 14 مربوط به ورژن چندمه ؟؟؟؟

    واقا به کمکتون احتیاج دارم.
    با تشکر فراوان

    • سلام
      در مورد سوال اول :
      البته می بایست در صورتی که می خواهید کاربر بدون داشتن ادوبی ایر بر روی دستگاه اندرویدی خود برنامه را اجرا کند ۸-۱۰ مگابایت برای این مورد محاسبه کنید. در کل فلش مشکلی با این بخش ندارد شما بهتر هست فایل ها را از خارج از فلش بارگزاری کنید و آنها در در بخش انتشار برنامه در لیست مربوطه قرار دهید. فایل ها که قرار نیست یکجا بارگزاری شود طبیعتا هر فایل در بخش مربوطه بارگزاری و نمایش داده می شود
      اگر قرار باشد همه فایل ها داخل فلش باشد شاید به مشکل بر بخورید ولی اگر فایل ها خارجی باشد ولی آنها را پیوست برنامه اندرویدی خود کنید مشکلی نخواهید داشت.

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

      Android™ ۲.۲, ۲.۳, ۳.۰, ۳.۱, and 3.2
      iPod touch (3rd generation) 32 GB and 64 GB model, iPod touch 4, iPhone 3GS, iPhone 4, iPad, iPad 2
      iOS 4 and above

      موفق و پیروز باشید

  8. سلام مجدد
    یه سوال درمورد ایر دارم که کلی من رو کلافه کرده،
    ممنون میشم که بازهم کمکم کنید
    براتون ایمیلش کردم ، ممنون میشم جواب بدید

  9. سلام
    قبل از هر چیز تشکر جانانه از شما استاد گل
    ممنون میشم دوباره راهنماییم کنید
    من با فلش cs6 یه فایل برای اندروید ساختم(از ایر ۳.۲ استفاده کردم) که یه فیلد متنی TLF داره روی حالت Editable هم قرارش دادم وقتی فایل apk نهایی رو روی گوشیم نصب میکنم( گلکسی اس ۳ دارم(اصلیه) که اندروید ۴ روشه ) مشکل اینه که وقتی نمیتونم از تو گوشیم ، چیزی توی فیلد متنی بنویسم
    اینجوری میشه که وقتی روی فیلد ضربه میزنم ، نشانه گر چشمکزن متن توی فیلد میاد ولی کیبرد گوشی نمیاد بالا که بتونم چیزی تایپ کنم
    درواقع مشکل اینه که کیبرد گوشی بالا نمیاد
    چه باید کرد استاد ؟؟؟؟؟

    • سلام دوست عزیز

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

      راه دوم دکمه ای قرار دهید که با tap بر روی آن بر روی فیلد شما فکوس کند تا مشکل رفع شود

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

      نمونه را هم برای برسی ارسال کنید

      موفق و پیروز باشید

      موفق و پیروز باشید

  10. سلام

    کارت درسته

    راستی اگه یه فایل apk رو که با as3 از فلش c6 ساخته شده باشه ، میشه کدش رو استخراج کرد ؟؟ اگه آره ، با چه برنامه ای ؟؟؟

    ممنون و متشکرم

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

  11. سلام
    باز هم ممنون

  12. سلام
    یه سوال درمورد مرورگرها و as3 داشتم که چون ندونستم که کدام بخش مطرحش کنم ، اینجا میگمش، ممنون میشم که جواب بدید
    چطور میشه میزان طول و عرض اون مقداری رو که مرورگرها نمایش میدن رو اندازه گرفت ؟؟؟ ( بخصوص وقتی که اندازه مرورگرها رو خودمون با موس تغییر میدیم )
    البته فرض کنید که فرمت خروجی html باشه
    البته میزان رزلوشن دکستاپ رو میشه اندازه گرفت ولی میزان اسکرین مرورگرها رو نمیدونم چطور باید بدست بیارم
    ممنون سپاسگزارم

    • سلام
      اگر فلش شما در مرورگر به صورت تمام صفحه و ۱۰۰% نمایش داده می شود و همه محیط آن را در بر گرفته با همان stage.stageWidth می توانید درسترسی به عرض داشته باشید و همین طور طول ولی اگر می خواهید اطلاعات مرورگر را دریافت کنید می توانید این کار را به راحتی در جاوا اسکریپت انجام دهید سپس متغیری که داده ها در آن هست یا تابعی که برای آن تعریف کرده اید را از درون فلش فراخوانی کنید و از اطلاعات استفاده کنید.
      از بخش مقالات مقاله مربوطه به ارتباط با جاوا اسکریپت را مطالعه کنید.

      این کد نیز یک کد نمونه در جاوا اسکریپت برای دریافت طول وعرض مرورگر :

      function getWindowSize(){
       var d= document, root= d.documentElement, body= d.body;
       var wid= window.innerWidth || root.clientWidth || body.clientWidth, 
       hi= window.innerHeight || root.clientHeight || body.clientHeight ;
       return [wid,hi]
      }
      

      موفق و پیروز باشید

  13. سلام
    متشکرم

    راستی یه سوال داشتم که توضیحش کمی سخته ولی در کل میشه این رو گفت که چطور میشه عکس و فایل ها ی swf رو از سایت های مختلف آپلود کرد( با فرمت html از طریق as3 )
    راستی فایل سورس همین رو براتون به وسیله ایمیل ارسال کردم
    باز هم ممنون.

    • سلام
      سوال شما کمی نامفهوم هست
      برای آپلود فایل توسط فلش می بایست از یک زبان برنامه نویسی سمت سرور نیز کمک بگیریم مثلا php که کار ذخیره اطلاعات ارسالی فلش را روی سرور انجام دهد
      یک نمونه ساده در بخش مقالات هست می توانید در وب نیز در این مورد جستجو نمایید
      موفق و پیروز باشید

ارسال جواب

آدرس رایانامه شما منتشر نخواهد شد.فیلد های اجباری علامت گزاری شده اند *

*

رفتن به بالای صفحه