مشخصات مقاله
سرویس ها در اندروید (Android Services)
سرویس های اندروید
سرویس چیست
Service یک کامپوننت نرم افزاری است که در پس زمینه اجرا شده و مستقیما با کاربر تعامل ندارد. از آنجایی که سرویس فاقد رابط کاربری است، طبیعتا به چرخه ی حیات یک activity نیز متصل نمی باشد.
سرویس ها اغلب برای انجام عملیات تکراری و طولانی مورد استفاده قرار می گیرند. از جمله ی این عملیات می توان به دانلود از اینترنت، بررسی و جستجو برای داده های جدید، پردازش اطلاعات، بروز آوری content provider ها و موارد مشابه اشاره کرد.
سرویس ها دارای اولویت سطح بالاتری نسبت به activity های غیرفعال/غیر قابل مشاهده در UI هستند و از این رو احتمال اینکه اندروید آن ها را به صورت خودکار خاتمه دهد بسیار پایین است.
اندروید به شما این امکان را می دهد تا سرویس ها را طوری تنظیم کنید که اگر به هر دلیلی مجبور به حذف این سرویس ها از حافظه شد، به مجرد قرار گرفتن منابع کافی در اختیار سیستم، قادر باشد آن ها را مجددا راه اندازی کند.
می توان به سرویس ها اولویت یکسان و برابر با ACTIVITY های حاضر در پیش زمینه (FORGROUND) اختصاص داد. در این سناریو لازم است یک notification قابل مشاهده و فعال در UI برای سرویس های مربوطه لحاظ نمایید. این روش بیشتر برای سرویس هایی بکار می رود که یک فایل ویدیویی یا موسیقی را پخش می کند.
سرویس ها و پردازش پس زمینه ای (background processing)
به صورت پیش فرض، سرویس در همان فرایندی اجرا می شود که thread اصلی اپلیکیشن در آن حال اجرا است. به همین جهت توسعه دهنده بایستی از پردازش ناهمزمان در سرویس استفاده نموده و task هایی که هزینه بر و سنگین هستند را در پس زمینه راه اندازی کند. یکی از الگوهایی که مکررا برای پیاده سازی سرویس بکار می رود، اجرای یک Thread جدید در سرویس جهت انجام پردازش در پس زمینه و خاتمه دادن سرویس به هنگام اتمام پردازش می باشد.
سرویس هایی که در بستر فرایند خود اپلیکیشن اجرا می شوند معمولا تحت عنوان service های محلی یا local شناخته می شوند.
سرویس های خود محیط اندروید (platform) و سرویس های اختصاصی
محیط اندروید سرویس های آماده و از پیش تعریف شده ای را درنظر گرفته و راه اندازی می کند که تمامی اپلیکیشن های اندرویدی، در صورت برخوردار بودن از مجوزهای لازم قادر به استفاده از آن ها استفاده می باشند. سرویس های سیستم را کلاسی به نام Manager در اختیار اپلیکیشن ها قرار می دهد.کافی است برای دسترسی به آن متد getSystemService() را فراخوانی نمایید.
کلاس Context تعدادی ثابت فراهم می کند که شما با استفاده از آن ها می توانید سرویس های نام برده را فراخوانی کنید.
اپلیکیشن اندروید می تواند علاوه بر سرویس های پیش فرض سیستم اندروید، سرویس های اختصاصی تعریف نموده و از آن ها در کنار سرویس های سیستم استفاده نماید.
توسعه دهنده قادر است با پیاده سازی سرویس های اختصاصی خود اپلیکیشن های پاسخگو و تعاملی (responsive) طراحی نماید. شما می توانید داده های اپلیکیشن را به وسیله های سرویس واکشی نموده و زمانی که اپلیکیشن راه اندازی شد، داده های جدید در اختیار کاربر قرار دهید.
راه اندازی و تعریف سرویس های اختصاصی
سرویس های اختصاصی اغلب توسط کامپوننت های دیگر راه اندازی می شوند، به عبارت دیگر سایر اجزا نرم افزاری اپلیکیشن های اندرویدی نظیر activity ها، broadcast receiver ها و سرویس های دیگر هستند که سرویس های اختصاصی را راه اندازی می کنند.
سرویس های پیش زمینه (foreground)
سرویس پیش زمینه سرویسی است که از نظر اولویت و اهمیت با یک activity فعال و قابل مشاهده در UI یکسان است و به همین جهت حتی اگر سیستم اندروید با کمبود حافظه مواجه باشد باز هم اجازه ی حذف از آن ها حافظه را ندارد. سرویس foreground می بایست در نوار نشان دهنده ی وضعیت کلی سیستم (status bar) یک اطلاعیه یا notification در زیر بخش عنوان "Ongoing" مختص به خود داشته باشد. این بدین معنی است که تا زمان حذف سرویس از foreground یا حافظه، notification قابل dismiss و حذف از status bar نخواهد بود.
1 2 3 4 5 6 7 8 | Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text), System.currentTimeMillis()); Intent notificationIntent = new Intent( this , ExampleActivity. class ); PendingIntent pendingIntent = PendingIntent.getActivity( this , 0 , notificationIntent, 0 ); notification.setLatestEventInfo( this , getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent); startForeground(ONGOING_NOTIFICATION_ID, notification); <button></button> |
تعریف سرویس های اختصاصی
پیاده سازی و اعلان
برای پیاده سازی یک سرویس اختصاصی، ابتدا لازم است آن را با استفاده از تگ service در لایه ی XML (فایل تنظیمات AndroidManifest.xml ) تعریف نموده و سپس در کدهای جاوا (کلاسی که سرویس را پیاده سازی می کند) از کلاس Service یا یکی از کلاس های مشتق آن ارث بری نمایید.
کد زیر نحوه ی تعریف سرویس در لایه ی xml و پیاده سازی آن در کلاس های جاوا را نمایش می دهد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | < service android:name = "MyService" android:icon = "@drawable/icon" android:label = "@string/service_name" > </ service > public class MyService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { //TODO do something useful return Service.START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { //TODO for communication return IBinder implementation return null; } } < button ></ button > |
راه اندازی یک سرویس
دیگر کامپوننت های نرم افزاری اندروید (service، receiver، activity) می توانند با فراخوانی تابع startService(intent) ، یک سرویس اندروید را راه اندازی کنند.
1 2 3 4 5 6 | // use this to start and trigger a service Intent i= new Intent(context, MyService. class ); // potentially add data to the intent i.putExtra( "KEY1" , "Value to be used by the service" ); context.startService(i); <button></button> |
روش دیگر راه اندازی سرویس، فراخوانی متد bindSerivce() می باشد. از این طریق شما می توانید مستقیما با service مورد نظر تعامل داشته باشید.
در آینده بیشتر درباره ی روش دوم توضیح خواهیم داد.
فرایند راه اندازی و اجرای سرویس
چنانچه پس از فراخوانی متد startService(intent)، سرویس همچنان راه اندازی نشده باشد، آنگاه آبجکت service ایجاد شده و متد onCreate() از کلاس سرویس فراخوانی می شود.
پس از اجرای سرویس، متد onStartCommand(intent) در سرویس فراخوانی می شود. سپس آبجکت Intent را به عنوان پارامتر از تابع فراخوانده شده ی startService(intent) دریافت می کند.
در واقع سیستم اندروید با فراخوانی متد startService()، تابع onStartCommand(intent) را زمانی صدا می زند که کامپوننت نرم افزاری دیگری همچون activity نیاز به اجرای سرویس مورد نظر داشته باشد. لازم به ذکر است که اگر برنامه نویس این متد را پیاده سازی کند، آنگاه مسئولیت متوقف نمودن سرویس را بایستی خود با صدا زدن توابع stopService()، stopSelf() بر عهده بگیرد.
اگر startService(intent) صدا خورده شود، درحالی که سرویس همچنان در حال اجر است، در آن صورت متد onStartCommand() نیز فراخوانی می شود. از این جهت سرویس بایستی این قابلیت و آمادگی را داشته باشد که متد onStartCommand() بارها صدا خورده شود.
در صورتی که این متد دوبار در کد فراخوانی شود، چه اتفاق رخ خواهد داد؟ آیا لازم است نگران همگام سازی فرخوانی های onStartCommand() باشید؟ در پاسخ باید گفت خیر. این متد را سیستم اندروید در thread اصلی (UI) صدا می زند، از این جهت امکان فراخوانی همزمان آن در دو thread متفاوت وجود ندارد.
سرویس تنها یکبار راه اندازی می شود، فارغ تعداد دفعاتی که متد startService() فراخوانی می شود.
رفتار بازآغازی و restart سرویس
سرویس داخل بدنه ی متد onStartCommand() خود یک مقدار int را برمی گرداند (return). این دستور رفتار سرویس را در صورتی که محیط اندروید (platform) مجبور به خاتمه دادن سرویس و حذف آن از حافظه شود را مشخص می نماید. برای این منظور می توانید از ثوابت زیر استفاده نمایید. این ثوابت به همراه شرح کاربرد در جدول زیر ذکر شده اند.
می توانید با فراخوانی متدIntent.getFlags() به همراه هر یک از پارامترهای (1. START_FLAG_REDELIVERY = در صورتی که سرویس با Service.START_REDELIVER_INTENT راه اندازی شده باشد 2. START_FLAG_RETRY = چنانچه سرویس با Service.START_REDELIVER_INTENT صدا خورده شده باشد 3. START_FLAG_RETRY = اگر سرویس با Service.START_STICKY فراخوانده شده باشد) بررسی کنید آیا سرویس مجددا راه اندازی شده است یا خیر.
متوقف کردن یک سرویس
برای متوقف کردن یک سرویس کافی است متد stopService() را صدا بزنید. فارغ از تعداد دفعاتی که متد startService() را برای راه اندازی خود سرویس فراخوانی کردید، یکبار صدا زدن تابع stopService() سرویس مورد نظر را کاملا متوقف می سازد.
یک سرویس می تواند خود را با فراخوانی متد stopSelf() متوقف کند. این تابع معمولا زمانی صدا خورده می شود که سرویس کارش کاملا تمام شده باشد.
استفاده از IntentServices (ارث بری از کلاس IntentServices)
می توانید از کلاس IntentService برای پیاده سازی سرویس استفاده نمایید.
IntentService برای اجرای تسک های خاصی در background بکار می رود. نمونه ی پیاده سازی شده از این کلاس پس از اتمام وظایفش، خود را به صورت اتوماتیک متوقف ساخته و از حافظه حذف می نماید. یکی از موارد استفاده ی این کلاس در بارگیری منابع و محتوای خاص از اینترنت است.
کلاس IntentService متد onHandleIntent() را ارائه می دهد. این متد را سیستم اندروید به صورت ناهمزمان (asynch) فراخوانی می نماید.
متصل کردن دوطرفه ی سرویس ها (service binding)
وصل شدن از activity ها به سرویس ها
چنانچه activity لازم دارد به طور مستقیم با service تبادل داده و تعامل داشته باشد، ابتدا متد bindService() را برای راه اندازی service صدا می زند. این متد یک آبجکت ServiceConnection به عنوان ورودی می گیرد که به هنگام آغاز سرویس و اتمام اجرای متد onBind() فراخوانی می شود. متد ذکر شده یک آبجکت IBinder به ServiceConnection برمی گرداند.
در واقع سیستم با فراخوانی bindService() متد onBind() را زمانی صدا می زند که کامپوننت دیگری درخواست اتصال به این سرویس را داشته باشد. در صورتی که توسعه دهنده این متد را پیاده سازی کرده باشد، آنگاه می بایست یک interface نیز پیاده سازی کرده و آبجکت IBinder را به عنوان خروجی برگرداند که کلاینت برای ارتباط با سرور از آن استفاده می کند. لازم به توضیح است که پیاده سازی این متد ضروری می باشد، اما چنانچه نمی خواهید اجازه ی اتصال دو طرفه (Bind) را بدهید، می توانید null را بازگردانی کنید.
Activity با استفاده از آبجکت IBinder با سرویس اطلاعات رد و بدل می کنند.
زمانی که فرایند اتصال به پایان می رسد، متد onStartCommand() در سرویس با آبجکت Intent فراخوانی شده که متدbindService() از آن استفاده می کند.
اتصال به سرویس های محلی
اگر سرویس در فرایندی یکسان با activity اجرا شود (سرویس و activity هر دو در یک فرایند اجرا شوند)، در آن صورت می توان سرویس را به activity برگرداند. این امر زمینه ای برای activity فراهم می آورد تا متدهای سرویس را مستقیما فراخوانی کند. این روش را به صورت کاربردی در <<exercise_bindlocalservice" /> می بینید که پیاده سازی شده است.
اتصال به سرویس با استفاده از IPC
اگر سرویس در فرایند ویژه ی خودش اجرا شود، آنگاه بایستی با استفاده از IPC با سرویس اطلاعات تبادل نمایید.
اجرای سرویس ها در فرایندهای مجزا
اجرای یک سرویس در فرایند مختص به خود
می توانید سرویس خود را طوری تنظیم نمایید که در فرایند جداگانه و مختص به خود اجرا شود. برای این منظور مقدار android:process را در داخل تگ service، برابر ":process_description" قرار می دهید.
1 2 3 4 5 6 | < service android:name = "WordService" android:process = ":my_process" android:icon = "@drawable/icon" android:label = "@string/service_name" > </ service > < button ></ button > |
علامت دونقطه رو به روی= : android:process به سیستم Android اعلان می کند که Service مورد نظر مختص اپلیکیشن میزبان و تعریف کننده ی آن است. در صورتی که از دو نقطه استفاده نشده باشد، آنگاه Service در بستر یک فرایند سراسری اجرا شده و سایر اپلیکیشن های اندرویدی به آن دسترسی خواهند داشت.
اجرای یک سرویس در فرایند خودش سبب مسدود شدن اپلیکیشن نمی شود، حتی اگر سرویس مورد نظر عملیات طولانی مدت در thread اصلی خود اجرا کند. اما از طرفی، از آنجایی که سرویس ها در فرایند خود اجرا می شوند، شما بایستی برای اتصال و تبادل داده با سرویس از سایر بخش ها از IPC بهره بگیرید.
حتی اگر سرویس در فرایند خود اجرا شود، برای دسترسی به اینترنت، برنامه نویس ملزم به استفاده از پردازش ناهمزمان خواهد بود چرا که اندروید تحت هیچ عنوانی اجازه ی دسترسی به اینترنت را در thread اصلی یک فرایند نمی دهد.
چه زمان می بایست یک سرویس را در فرایند مجزا اجرا کرد؟
اگر یک سرویس را در فرایند جداگانه و مختص به خود اجرا کنید، در آن صورت یک فضا با آدرس ویژه در حافظه به آن تخصیص یافته و garbage collector دستگاه مجازی (AVD) در این فرایند، در فرایند کلی اپلیکیشن دخالتی نخواهد داشت.
اپلیکیشن به ندرت نیاز پیدا می کند که سرویس را در فرایند ویژه و مجزا راه اندازی کند. اجرای سرویس ها در بستر فرایندهای مجزا، سبب می شود پیاده سازی ارتباط و تبادل داده بین سایر کامپوننت های نرم افزاری اندروید و سرویس به تبع دشوار شود.
اگر می خواهید دسترسی به یک سرویس را در اختیار اپلیکیشن دیگری قرار دهید، در آن صورت سرویس بایستی در فرایند مختص به خود اجرا شود.
تبادل داده و ارتباط با سرویس ها
روش های مختلف برای برقراری ارتباط با سرویس ها
راه های مختلفی برای تبادل داده و تعامل بین activity و سرویس وجود دارد. مطالب زیر روش های ممکن برای نیل به این هدف را نام برده و روش پیشنهادی خود را در اختیار شما قرار می دهد.
استفاده از داده های کپسوله شده در intent
در یک سناریوی ساده، نیازی به هیچ تعامل مستقیم (بین سرویس و activity) وجود ندارد. سرویس داده های کپسوله شده در intent را از کامپوننت آغاز کننده (فراخواننده ی سرویس) دریافت نموده و عملیات لازم را به انجام می رساند. لازم به ذکر است که برای این منظور notification ضروری نیست. در واقع در شرایطی که سرویس محتوای یک content provider را با داده های جدید بروز آوری می کند، خود کامپوننت نرم افزاری مزبور activity را از این رخداد با خبر کرده و هیچ اقدام یا مرحله ی دیگری در سرویس لازم نیست. این روش هم برای سرویس های محلی و هم برای سرویس هایی که در فرایند مختص به خود اجرا می شوند، قابل پیاده سازی و استفاده خواهد بود.
استفاده از receiver
می توان برای تعامل و ارتباط بین activity و سرویس ها از broadcast ها و receiver هایی که به این broadcast ها گوش می دهند، بهره گرفت. به عنوان مثال، activity شما می تواند یک broadcast receiver برای گوش دادن به event ای معین ثبت کند و سرویس مورد نظر اتفاق افتادن event های مربوطه را به بیرون (کاپوننت های دیگر) اعلان نماید. این روش بسیار معمول بوده و اغلب زمانی استفاده می شود که سرویس می بایست پس از به انجام رساندن پردازش به activity این اتفاق را اعلان کند.
این جریان ارتباطی در تصویر زیر به تصویر کشیده شده است.
روش نام برده برای سرویس های محلی و سرویس هایی که در فرایند میزبان و مختص خود اجرا می شوند، قابل استفاده می باشد.
اتصال activity به سرویس محلی
چنانچه activity و service هر دو در فرایندی یکسان اجرا شوند، در این شرایط activity قادر خواهد بود به سرویس مستقیما به صورت دو طرفه وصل شود. روش حاضر از میان گزینه هایی که تاکنون عنوان شده، بهینه ترین بوده و برای زمانی که activity نیاز دارد با سرویس با سرعت بالا تبادل داده داشته باشد بسیار مناسب می باشد.
ناگفته نماند که این روش تنها برای سرویس های محلی (سرویس هایی که) قابل پیاده سازی و استفاده می باشد.
Handler و ResultReceiver یا Messenger
چنانچه سرویس با activity تعامل دو طرفه داشته باشد (اطلاعاتی را به activity برگرداند)، در آن صورت می تواند از طریق داده های کپسوله شده در intent که از activity دریافت می کند، آبجکتی از جنس Messenger دریافت نماید. در صورتی که Messenger به Handler در activity متصل باشد، آنگاه service قادر خواهد بود آبجکت هایی از جنس Message را به activity ارسال نماید.
Messenger در واقع اینترفیس parcelable را پیاده سازی می کند، بدین معنی که می توان آن را به فرایند دیگر ارسال کرده و با استفاده از این آبجکت Message هایی را به Handler در activity ارسال نمود.
Messenger همچنین متدی به نام getBinder() را ارائه می دهد. این متد قابلیت ارسال آبجکتی از جنس Messenger به activity را فراهم می نماید. activity نیز متعاقبا قادر خواهد بود Message های (نمونه هایی از کلاس Message که حاوی توصیف و آبجکت های داده ای دلخواه می باشد) متعددی را به سرویس مورد نظر ارسال کند.
این روش برای سرویس های محلی که در فرایند خود اجرا می شوند، قابل استفاده می باشد.
اتصال به سرویس در فرایند دیگر با استفاده از AIDL
به منظور تبادل داده و اتصال (bind) به سرویسی که در فرایند دیگری در حال اجرا است، برنامه نویس بایستی از IPC (ارتباط میان پردازشی) کمک بگیرد. برای نیل به این هدف، ابتدا لازم است یک فایل AIDL ایجاد کند که تقریبا مشابه interface های جاوا می باشد با این تفاوت که پسوند آن .aidl بوده و تنها اجازه ی ارث بری و بسط دیگر فایل های AIDL را دارد.
توصیه می شود از این روش زمانی استفاده نمایید که لازم باشد به سرویسی که در فرایند دیگری در حال اجرا است متصل شوید. برای مثال زمانی که اپلیکیشن های دیگر درخواست استفاده از سرویس مورد نظر را داشته باشند باید از این رویکرد استفاده کرد.
تمرین: استفاده از Service ها و تبادل داده با سرویس
در مثال زیر یک activity با کنترل دکمه پیاده سازی می کنید که از سرویس برای دانلود فایل از اینترنت استفاده می کند. زمانی که کاربر بر روی دکمه در activity کلیک می کند، دانلود اطلاعات از اینترنت آغاز می گردد. پس از به پایان رسیدن دانلود، سرویس از طریق broadcast receiver به activity این اتفاق را اعلان می کند.
در مثال کاربردی حاضر از کلاس IntentService استفاده خواهید نمود چرا که این کلاس پردازش های پس زمینه ای را به صورت خودکار مدیریت می نماید.
یک پروژه و activity جدید به ترتیب به نام های com.vogella.android.service.receiver و MainActivity ایجاد نمایید.
سپس کلاس زیر را برای پیاده سازی سرویس تعریف نمایید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | package com.vogella.android.service.receiver; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import android.app.Activity; import android.app.IntentService; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Message; import android.os.Messenger; import android.util.Log; public class DownloadService extends IntentService { private int result = Activity.RESULT_CANCELED; public static final String URL = "urlpath" ; public static final String FILENAME = "filename" ; public static final String FILEPATH = "filepath" ; public static final String RESULT = "result" ; public static final String NOTIFICATION = "com.vogella.android.service.receiver" ; public DownloadService() { super ( "DownloadService" ); } // will be called asynchronously by Android @Override protected void onHandleIntent(Intent intent) { String urlPath = intent.getStringExtra(URL); String fileName = intent.getStringExtra(FILENAME); File output = new File(Environment.getExternalStorageDirectory(), fileName); if (output.exists()) { output.delete(); } InputStream stream = null ; FileOutputStream fos = null ; try { URL url = new URL(urlPath); stream = url.openConnection().getInputStream(); InputStreamReader reader = new InputStreamReader(stream); fos = new FileOutputStream(output.getPath()); int next = - 1 ; while ((next = reader.read()) != - 1 ) { fos.write(next); } // successfully finished result = Activity.RESULT_OK; } catch (Exception e) { e.printStackTrace(); } finally { if (stream != null ) { try { stream.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null ) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } publishResults(output.getAbsolutePath(), result); } private void publishResults(String outputPath, int result) { Intent intent = new Intent(NOTIFICATION); intent.putExtra(FILEPATH, outputPath); intent.putExtra(RESULT, result); sendBroadcast(intent); } } <button></button> |
این کلاس را در لایه ی XML، فایل تنظیمات اپلیکیشن AndroidManifest.xml اضافه نمایید. حال زمان آن رسیده تا مجوزهای لازم را برای درج داده (write) در حافظه ی خارجی و دسترسی به اینترنت را نیز اعطا نمایید. پس از وارد کردن تغییرات فوق، محتوای AndroidManifest.xml می بایست ظاهری مشابه زیر داشته باشد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <? xml version = "1.0" encoding = "utf-8" ?> package = "com.vogella.android.service.receiver" android:versioncode = "1" android:versionname = "1.0" > < uses-sdk android:minsdkversion = "17" android:targetsdkversion = "18" /> < uses-permission android:name = "android.permission.INTERNET" /> < uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE" /> < application android:allowbackup = "true" android:icon = "@drawable/ic_launcher" android:label = "@string/app_name" android:theme = "@style/AppTheme" > < activity android:name = "com.vogella.android.service.receiver.MainActivity" android:label = "@string/app_name" > < intent-filter > < action android:name = "android.intent.action.MAIN" /> < category android:name = "android.intent.category.LAUNCHER" /> </ intent-filter > </ activity > < service android:name = "com.vogella.android.service.receiver.DownloadService" > </ service > </ application > </ manifest > < button ></ button > |
فایل layout مربوط به activity خود را به صورت زیر ویرایش نمایید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <? xml version = "1.0" encoding = "utf-8" ?> android:layout_width = "match_parent" android:layout_height = "match_parent" android:orientation = "vertical" > < button android:id = "@+id/button1" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:onclick = "onClick" android:text = "Download" /> < linearlayout android:layout_width = "wrap_content" android:layout_height = "wrap_content" > < textview android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "Status: " /> < textview android:id = "@+id/status" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "Not started" /> </ linearlayout > </ linearlayout > < button ></ button > |
کلاس MainActivity خود را به صورت زیر ویرایش نمایید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package com.vogella.android.service.receiver; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private TextView textView; private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras(); if (bundle != null ) { String string = bundle.getString(DownloadService.FILEPATH); int resultCode = bundle.getInt(DownloadService.RESULT); if (resultCode == RESULT_OK) { Toast.makeText(MainActivity. this , "Download complete. Download URI: " + string, Toast.LENGTH_LONG).show(); textView.setText( "Download done" ); } else { Toast.makeText(MainActivity. this , "Download failed" , Toast.LENGTH_LONG).show(); textView.setText( "Download failed" ); } } } }; @Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.status); } @Override protected void onResume() { super .onResume(); registerReceiver(receiver, new IntentFilter( DownloadService.NOTIFICATION)); } @Override protected void onPause() { super .onPause(); unregisterReceiver(receiver); } public void onClick(View view) { Intent intent = new Intent( this , DownloadService. class ); // add infos for the service which file to download and where to store intent.putExtra(DownloadService.FILENAME, "index.html" ); intent.putExtra(DownloadService.URL, startService(intent); textView.setText( "Service started" ); } } <button></button> |
حال زمانی که برنامه ی فوق را اجرا کرده و بر روی دکمه کلیک می کنید، سرویس بلافاصله اقدام به بارگیری اطلاعات لازم از اینترنت می نماید. با اتمام دانلود، رابط کاربری بروز رسانی شده و یک پیغام Toast با اسم فایل به نمایش در می آید.
تنظیمات را طوری تغییر دهید که سرویس در فرایند ویژه ی خود اجرا شود. بار دیگر اپلیکیشن را تست کرده و اطمینان حاصل نمایید که همچنان به درستی عمل می کند چرا که با وارد نمودن تغییرات جدید، broadcast receiver ها از فرایندهای مختلف broadcast ها را دریافت می کنند.
تمرین: پیاده سازی و استفاده از سرویس محلی (local service)
تمرین حاضر نحوه ی اتصال از یک activity به سرویس محلی (سرویسی که با activity در یک فرایند اجرا می شود) را به صورت عملی نمایش می دهد.
پس از اینکه دستگاه بوت می شود، سرویس راه اندازی شده و داده هایی را در فواصل زمانی مشخص از اینترنت دانلود می نماید. حال Activity، خود را به سرویس متصل کرده تا به داده هایی که اخیرا از اینترنت بارگیری شده دسترسی داشته باشد.
یک پروژه و activity جدید به ترتیب به نام های de.vogella.android.ownservice.local و MainActivity تعریف نمایید.
کلاسی به نام LocalWordService با بدنه ی زیر ایجاد نمایید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package de.vogella.android.ownservice.local; import java.util.ArrayList; import java.util.List; import java.util.Random; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; public class LocalWordService extends Service { private final IBinder mBinder = new MyBinder(); private ArrayList< string > list = new ArrayList< string >(); @Override public int onStartCommand(Intent intent, int flags, int startId) { Random random = new Random(); if (random.nextBoolean()) { list.add("Linux"); } if (random.nextBoolean()) { list.add("Android"); } if (random.nextBoolean()) { list.add("iPhone"); } if (random.nextBoolean()) { list.add("Windows7"); } if (list.size() >= 20) { list.remove(0); } return Service.START_NOT_STICKY; } @Override public IBinder onBind(Intent arg0) { return mBinder; } public class MyBinder extends Binder { LocalWordService getService() { return LocalWordService.this; } } public List< string > getWordList() { return list; } } < button ></ button > |
دو کلاس با پیاده سازی زیر تعریف نمایید. این دو کلاس در لایه ی XML متناظر به عنوان BroadcastReceivers معرفی و ثبت خواهند شد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | package de.vogella.android.ownservice.local; import java.util.Calendar; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; public class MyScheduleReceiver extends BroadcastReceiver { // restart service every 30 seconds private static final long REPEAT_TIME = 1000 * 30 ; @Override public void onReceive(Context context, Intent intent) { AlarmManager service = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); Intent i = new Intent(context, MyStartServiceReceiver. class ); PendingIntent pending = PendingIntent.getBroadcast(context, 0 , i, PendingIntent.FLAG_CANCEL_CURRENT); Calendar cal = Calendar.getInstance(); // start 30 seconds after boot completed cal.add(Calendar.SECOND, 30 ); // fetch every 30 seconds // InexactRepeating allows Android to optimize the energy consumption service.setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), REPEAT_TIME, pending); // service.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), // REPEAT_TIME, pending); } } package de.vogella.android.ownservice.local; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; public class MyStartServiceReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent service = new Intent(context, LocalWordService. class ); context.startService(service); } } <button></button> |
تمامی کامپوننت های نرم افزاری را بایستی در فایل تنظیمات اپلیکیشن (AndroidManifest.xml) معرفی نمایید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <? xml version = "1.0" encoding = "utf-8" ?> package = "de.vogella.android.ownservice.local" android:versioncode = "1" android:versionname = "1.0" > < uses-sdk android:minsdkversion = "10" /> < uses-permission android:name = "android.permission.RECEIVE_BOOT_COMPLETED" /> < application android:icon = "@drawable/icon" android:label = "@string/app_name" > < activity android:name = ".MainActivity" android:label = "@string/app_name" > < intent-filter > < action android:name = "android.intent.action.MAIN" /> < category android:name = "android.intent.category.LAUNCHER" /> </ intent-filter > </ activity > < service android:name = ".LocalWordService" android:icon = "@drawable/icon" android:label = "@string/service_name" > </ service > < receiver android:name = "MyScheduleReceiver" > < intent-filter > < action android:name = "android.intent.action.BOOT_COMPLETED" /> </ intent-filter > </ receiver > < receiver android:name = "MyStartServiceReceiver" > </ receiver > </ application > </ manifest > < button ></ button > |
محتوای فایل layout مربوط به activity را به صورت زیر ویرایش نمایید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <? xml version = "1.0" encoding = "utf-8" ?> android:layout_width = "fill_parent" android:layout_height = "fill_parent" android:orientation = "vertical" > < textview android:layout_width = "fill_parent" android:layout_height = "wrap_content" android:text = "@string/hello" /> < button android:id = "@+id/button1" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:onclick = "onClick" android:text = "Update" > </ button > < listview android:id = "@id/android:list" android:layout_width = "match_parent" android:layout_height = "wrap_content" > </ listview > </ linearlayout > < button ></ button > |
کلاس activity خود را به صورت زیر ویرایش نمایید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | package de.vogella.android.ownservice.local; import java.util.ArrayList; import java.util.List; import android.app.ListActivity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Toast; public class MainActivity extends ListActivity { private LocalWordService s; @Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.main); wordList = new ArrayList<string>(); adapter = new ArrayAdapter<string>( this , android.R.layout.simple_list_item_1, android.R.id.text1, wordList); setListAdapter(adapter); } @Override protected void onResume() { super .onResume(); Intent intent= new Intent( this , LocalWordService. class ); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onPause() { super .onPause(); unbindService(mConnection); } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder binder) { LocalWordService.MyBinder b = (LocalWordService.MyBinder) binder; s = b.getService(); Toast.makeText(MainActivity. this , "Connected" , Toast.LENGTH_SHORT) .show(); } public void onServiceDisconnected(ComponentName className) { s = null ; } }; private ArrayAdapter<string> adapter; private List<string> wordList; public void onClick(View view) { if (s != null ) { Toast.makeText( this , "Number of elements" + s.getWordList().size(), Toast.LENGTH_SHORT).show(); wordList.clear(); wordList.addAll(s.getWordList()); adapter.notifyDataSetChanged(); } } } <button></button> |
اپلیکیشن را اجرا نمایید. پس از کلیک بر روی دکمه، داده ها بار دیگر از سرویس واکشی شده و لیست آیتم ها/ListView با داده های جدید بروز آوری می شوند.