سرویس های اندروید Android Services و Multi Threading در اندروید
بسم الله الرحمن الرحیم
سرویس های اندروید Android Services و Multi Threading در اندروید
فصل یازدهم
شما در پایان این فصل با موارد زیر آشنا می شوید:
چگونه یک Service را ایجاد کنید که در background اجرا می شود
چگونه یک کار را با زمان های اجرای طولانی در یک Thread جداگانه اجرا کنید.
چگونه کارهای (task) جداگانه ای را در Service اجرا کنید
چگونه یک Activity با سرویس (service)ارتباط بر قرار می شود.
سرویس یک برنامه اندروید است که در background اجرا می شود بدو ن نیاز به اینکه با کاربر تعامل داشته باشد. به عنوان مثال زمانی که شما از این برنامه استفاده می کنید می خواهید یک موزیک در زمان مشخص اجرا شود. در این موارد موزیک در background اجرا می شود ونیازی به تعامل به کاربر نمی باشد. در نتیجه میتواند در یک سرویس اجرا شود. سرویس ها بسیار مناسب و ایدال برای زمان های هستند که شما نمی خواهید به کاربر UI را نمایش دهید. یک مثال خوب این است که مکان های جغرافیای دستگاه ثبت شود. در این موارد شما یک سرویس برای این کار استفاده می کنید که در background استفاده می شود. شما در این فصل یاد می گیرید که چگونه یک سرویس را ایجاد کنید و کار ها را در background به طور همزمانی اجرا کنید.
ایجاد سرویس خودتان
بهترین را ه برای فهمیدن نحوه ای کار سرویس ها این است که یک سرویس را راه اندازی کنید. در ادامه شرح می دهیم که چگونه یک سرویس ساده را راه اندازی کنید.
شما یاد میگیرید که چگونه یک سرویس را start(راه اندازی)و stop (متوقف)کنید.
پروژه جدیدی به نامServices. ایجاد کنید
کلاس جاوای به نامMyService. ایجاد کنید و دستورات زیر را در آن وارد نمایید.
package com.MehrdadJavidi.services;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class MyService extends Service {
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();
}
}
فایلAndroidManifest.xml به صورت زیر تغییر دهید.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.MehrdadJavidi.services"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService" />
</application>
</manifest>
قایلmain.xml به صورت زیر تغییر دهید.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="@+id/btnStartService"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="startService"
android:text="Start Service" />
<Button
android:id="@+id/btnStopService"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="stopService"
android:text="Stop Service" />
</LinearLayout>
دستورات MAINaCTIITY.javaزیر رادر قایل وارد نمایید.
package com.MehrdadJavidi.services;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void startService(View view) {
startService(new Intent(getBaseContext(), MyService.class));
}
public void stopService(View view) {
stopService(new Intent(getBaseContext(), MyService.class));
}
}
برنامه را برازدن F11 اجرا کنید بر روی دکمهStart Service کلیک کنید تا سرویس اجرا شود. بر روی دکمهStopService کلیک کنید تا سرویس متوقف شود
توضیحات
این مثال مشخص می کند که چگونه میتواندی به سادگی یک سرویس را ایجاد کنید
اول شما یک کلاس تعریف کردید که از کلاس Service مشتق شده است
در کلاس MyService شما 3 متد به کار گرفته اید کردند
@Override
public IBinder onBind(Intent arg0) { ... }
@Override
public int onStartCommand(Intent intent, int flags, int startId) { ... }
@Override
public void onDestroy() { ... }
متد onBind() شما را قادر می سازد تا یک activity را بر سرویس bind کنید. این متد شما قادر می سازد که شما دسترسی مستقیم به اعضا (member)و متد های سرویس دسترسی داشته باشید. در اینجا برای سادگی شما مقدار null رابرای این متد برگرداندید. در قسمت های بعدی شما یاد می گیرید که چگونه این Bind را انجام دهید.
متدonStartCommand() صدا زده می شود زمانی که بخواهید سرویس اجرا شود. به طور خلاصه شده ازstartService() استفاده می شود. این متد سرویس را اجرا می کند. و کدی که می حواهید عملیاتی انجام دهید در آن قرار می گیرد. دراین متد شما ثابت START_STICKY را بازگردانید بنابراین این سرویس ادامه می یابد تا زمانی که آن را به طور صریح Stop کنید.
متدonDestroy()زمانی که بخو.اهیم سرویس متوقف شود فراخوانی می شود. به طور خلاصه از stopService() اسفتاده می شود
تمام سرویس های که شما ایجاد کردید باید در فایل AndroidManifest.xml تعریف کنید
<service android:name=".MyService" />
اگر شما بخواهید سرویس توسط برنامه های دیگر در دسترس باشد شما می توانید یک intent filter شبیه زیر استفاده کنید.
<service android:name=”.MyService”>
<intent-filter>
<action android:name=”com.MehrdadJavidi.MyService” />
</intent-filter>
</service>
برای اجرای سرویس شما از startService() استفاده کردید
startService(new Intent(getBaseContext(), MyService.class));
برای اجرای سرویس توسط برنامه های دیگر شما باید به صورت زیر استفاده کنید.
stopService(new Intent(getBaseContext(), MyService.class));
برای متوقف کردن برنماه شما ازstopService() استفاده می کنید.
stopService(new Intent(getBaseContext(), MyService.class));
اجرای کار ها با زمان های طولانی با استفاده از سرویس ها
سرویسی که شما در قسمت قبل ساختید کار مفیدی را انجام نمی دهد. در این قسمت شما تغییراتی میدهیم تا یک کار را انجام دهید.. در زیر سرویس یک فایل را به صورت شبیه سازی شده از اینترنت دانلود میکند.
به پروژه قبلی بروید و فایل MainActivity.java به صورت زیر تغییر دهید.
package com.mehrdadjavidi.Services;
import java.net.MalformedURLException;
import java.net.URL;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class MyService extends Service {
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
// Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
try {
int result = DownloadFile(new URL(
"http://www.amazon.com/somefile.pdf"));
Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes",
Toast.LENGTH_LONG).show();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return START_STICKY;
}
private int DownloadFile(URL url) {
try {
// ---simulate taking some time to download a file---
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// ---return an arbitrary number representing
// the size of the file downloaded---
return 100;
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();
}
}
برنامه را اجراکنید بر رویService button کلیک کنید تا برنامه اجرا شود نکته این است که برنامه برای چند لحظه به حالت می هنگ شدن می رودتا زمانی که Toast پیام نمایشDownloaded 100 bytes دهد
توضیحات:
در اینجا شما سرویس متد DownloadFile() را برای دانلود فایل به طور شبیه سازی فراخوانی کرد این متد تعداد بایت های که دانلود کرده است را نمایش میدهید. برای شبیه سازی مدت زمانی که طول می کشد فایل دانلود شما از Thread.Sleep() اسستفاده می کنید
که ای متد زمانی که میخواهید به حالت pause برورد را دریافت می کند(برحسب میلی ثانیه).
بعد از اجرای سرویس توجه کنید که برنامه برای 5 ثانیه به حالت suspendedمی رود که این مدت زمان شبیه سازی شده برای دریافت فایل از اینترنت می باشد. باید توجه داشته در این مدت هیچ پاسخی از activity نداریم تا زمانی که این کار تمام نشود نمی توانیم هیچ کاری با Acitivity داشته باشیم و این بسیار مهم می باشد که سرویس همزمان با Thread مربوط به activity اجرا می شود. و به همین دلیل سرویس ِacitivity را به حات suspended می برد.
از این رو برای سرویس های با زمان طولانی این بسیار مهم است که کار ها بازمان های طولانی در یک Thread جدا گانه اجرا شوند. که د ر ادامه شرح می دهیم که چگونه این کار را انجام دهیم.
فایل MyService.java به صورت زیر تغییر دهید.
package com.MehrdadJavidi.services;
import java.net.MalformedURLException;
import java.net.URL;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class MyService extends Service {
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
// Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
try {
new DoBackgroundTask().execute(new URL(
"http://www.amazon.com/somefiles.pdf"), new URL(
"http://www.wrox.com/somefiles.pdf"), new URL(
"http://www.google.com/somefiles.pdf"), new URL(
"http://www.learn2develop.net/somefiles.pdf"));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return START_STICKY;
}
private int DownloadFile(URL url) {
try {
// ---simulate taking some time to download a file---
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// ---return an arbitrary number representing
// the size of the file downloaded---
return 100;
}
private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalBytesDownloaded = 0;
for (int i = 0; i < count; i++) {
totalBytesDownloaded += DownloadFile(urls[i]);
// ---calculate percentage downloaded and
// report its progress---
publishProgress((int) (((i + 1) / (float) count) * 100));
}
return totalBytesDownloaded;
}
protected void onProgressUpdate(Integer... progress) {
Log.d("Downloading files", String.valueOf(progress[0])
+ "% downloaded");
Toast.makeText(getBaseContext(),
String.valueOf(progress[0]) + "% downloaded",
Toast.LENGTH_LONG).show();
}
protected void onPostExecute(Long result) {
Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes",
Toast.LENGTH_LONG).show();
stopSelf();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();
}
}
برنامه را با F11 اجرا کنید. با کلیک بر روی Service button. کلاسToast پیام های را که شامل نمایش درصد تکمیل شدن دانلود فایل می باشند را نمیش میدهد. شما them: 25%, 50%, 75%, and 100%. مشاهده می کنید
همچنین شما می توانید در پنجره LogCat می توانید مشاهده کنید
12-06 01:58:24.967: D/Downloading files(6020): 25% downloaded
12-06 01:58:30.019: D/Downloading files(6020): 50% downloaded
12-06 01:58:35.078: D/Downloading files(6020): 75% downloaded
12-06 01:58:40.096: D/Downloading files(6020): 100% downloaded
توضیحات
این مثال به شما نشان میدهد که چگونه شما می توانید یک task را به طور همزمانی اجرا کنید شما با ساخت یک کلاس داخلی که از مشتقAsyncTask شده است این کلاس به شما امکان میدهد عملیات خود رادر background بدون نیاز به کنترل Thread ها اجرا کنید.
کلاسDoBackgroundTask از AsyncTask مشتق شده است که سه نوع Generic تعیین کرده ایم.
private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> {
در اینجا شما نوع داده های تعیین URL, Integer and Long. کرداید. این نوع داده های برای متد های که در کلاس AsyncTask به کار گرفته شده تعین کرد اید.
متد doInBackground() یک آرایه از نوع داد های generic نوع اول تعیین شده است. را دریافت می کند که در اینجا URL. میباشد این متد در یک Thread در background اجرا می شوند و جای است که شما کد های با زمان اجرای طولانی را قرار میدهید. برای گزارش از پیشرفت کار شما متد publishProgress() فراخوانی می کنید که متدonProgressUpdate(), را اجرا می کنید مقدار برگشتی این متد نوع داده generic سوم را دریافت می کند که در اینجا Longمیباشد.
onProgressUpdate()- این متد thread مربوط به uiرا زمانی که publishProgress() فراخوانی می کند اجرا میکند این متد یک آرایه از نوع generic دوم دریافت می کند که در اینجا Integer. می باشد
onPostExecute()- این متد thread مربوط به UI را زمانی که متد doInBackground() به طور کامل اجرا شد اجرای میکند ای متد یکی از 3 نوع داد های generic را دریافت می کنند که در اینجاLong. می باشد. برای دانلود چندین فایل به طور همزمان شما یک نمونه از DoBackgroundTask را ایجاد و سپس execute() و فراخوانی کردید و سپس آرایه ای از URL را به آن ارسال کرد هایم
try {
new DoBackgroundTask().execute(new URL(
"http://www.amazon.com/somefiles.pdf"), new URL(
"http://www.wrox.com/somefiles.pdf"), new URL(
"http://www.google.com/somefiles.pdf"), new URL(
"http://www.learn2develop.net/somefiles.pdf"));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
کد قبل در سرویس فایل ها را در background دانلود می کند و پیشرفت گزارش درصد دانلود فایل را نمایش میدهد. نکته مهم اینجا این است که ما در حالی که فایل در حال دانلود شدن می باشد می توانیم با UI کار کنیم. دلیل این میباشد که عملیات در Thread های جداگانه در حال اجرا می باشند.
نکته دیگر این میباشد که thread در background در حال اجرا میباشد زمانی که به پایان رسید شما می توانید به طور دستی با متد stopSelf() سرویس را متوفق کنید
protected void onPostExecute(Long result) {
Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes",
Toast.LENGTH_LONG).show();
stopSelf();
}
متدstopSelf() برابر با stopService() برای متوقف کردن میباشد
اجرای کار های تکرار شدنی در سرویس
علاوه بر کار های زمان بر در سرویس ها. شما ممکن است بعضی از کار های که تکرار می شوند را اجرا کنید ممکن است شما بخواهید یکalarm clock را اجرا کنید که به طور پیوسته در background اجرا می شود. یادر مواردی ممکن است سرویس شما بخواهد به طور پیوسته آب و هوا چک کند و در شرایط خواصی یک Alarm به کاربر بدهد. برای چنین کار های شما از کلاس Timerدر سرویس استفاده می کنید .که در زیر آن را شرح می دهیم. که در ادامه آن را شرح میدهیم.
به پروژه قبلی رفته و در فایلMyService.java وارد نمایید
package com.MehrdadJavidi.services;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class MyService extends Service {
int counter = 0;
static final int UPDATE_INTERVAL = 1000;
private Timer timer = new Timer();
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
// Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
doSomethingRepeatedly();
try {
new DoBackgroundTask().execute(new URL(
"http://www.amazon.com/somefiles.pdf"), new URL(
"http://www.wrox.com/somefiles.pdf"), new URL(
"http://www.google.com/somefiles.pdf"), new URL(
"http://www.learn2develop.net/somefiles.pdf"));
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return START_STICKY;
}
private void doSomethingRepeatedly() {
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
Log.d("MyService", String.valueOf(++counter));
}
}, 0, UPDATE_INTERVAL);
}
private int DownloadFile(URL url) {
try {
// ---simulate taking some time to download a file---
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// ---return an arbitrary number representing
// the size of the file downloaded---
return 100;
}
private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalBytesDownloaded = 0;
for (int i = 0; i < count; i++) {
totalBytesDownloaded += DownloadFile(urls[i]);
// ---calculate percentage downloaded and
// report its progress---
publishProgress((int) (((i + 1) / (float) count) * 100));
}
return totalBytesDownloaded;
}
protected void onProgressUpdate(Integer... progress) {
Log.d("Downloading files", String.valueOf(progress[0])
+ "% downloaded");
Toast.makeText(getBaseContext(),
String.valueOf(progress[0]) + "% downloaded",
Toast.LENGTH_LONG).show();
}
protected void onPostExecute(Long result) {
Toast.makeText(getBaseContext(), "Downloaded " + result + " bytes",
Toast.LENGTH_LONG).show();
stopSelf();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (timer != null) {
timer.cancel();
}
Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();
}
}
برنامه را اجرا کنید
بر روی دکمهStart Service کلیک کنید
پنجره LogCat را مشاهده کنید
12-06 02:37:54.118: D/MyService(7752): 1
12-06 02:37:55.109: D/MyService(7752): 2
12-06 02:37:56.120: D/MyService(7752): 3
12-06 02:37:57.111: D/MyService(7752): 4
12-06 02:37:58.125: D/MyService(7752): 5
12-06 02:37:59.137: D/MyService(7752): 6
توضیحات
در اینجا شما یک شیTimer ایجاد کردید و سپس متدscheduleAtFixedRate() آن را در متدdoSomethingRepeatedly() فراخوانی کردید
private void doSomethingRepeatedly() {
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
Log.d("MyService", String.valueOf(++counter));
}
}, 0, UPDATE_INTERVAL);
}
شما یک نمونه از کلاسTimerTask را به scheduleAtFixedRate()ارسال کردید بلاکی از دستورات را درون run() به صورت تکرار شدنی اجرا کنید دومین پارامتر scheduleAtFixedRate() زمان را بر جسب میلی ثانیه تعیین می کند. سومین پارامتر زمان را بر حسب میلی ثانیه مشخص می کند بر ای اجرای های تکرار شدنی.
در کد قبل شما تعیین کردید که که مقدار counter را هر یک ثانیه (1000میلی ثانیه) در خروجی چاپ کنید
@Override
public void onDestroy() {
super.onDestroy();
if (timer != null) {
timer.cancel();
}
Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();
}
متد scheduleAtFixedRate() , کد شما را در بازه های زمانی مشخص و ثابت اجرا می کند ......... به عنوان مثلا کد درون متد run() دو ثانیه طول می کشد که کامل شود . کد دوم شما بلافاصله بعد از پایان کار اول اجرا می شود. به طور مشابه شما تعیین کردهاید که برنامه در فاصله زمانی 3 ثانیه تکرار شود. و کد شما 2 ثانیه طول می کشد تا تمام شود. کار دوم باید منتظر بماند تا این 1 ثانیه تمام شود. تا اجرا شود.
همچنین شما متدdoSomethingRepeatedly() را درonStartCommand() قرار دادید. بدون نیاز به اینکه زیر کلاس AsyncTask تعریف کنید به دلیل این که کلاس TimerTask واسط (interface) Runnable را به کار گرفته است که اجازه می دهد کار ها در Thread ها متفاوت انجام شود.
اجرای همزمای کار ها در Thread های متفاوت با استفاده از IntentService
در قسمت های قبلی این فصل شما یاد گرفتید که چگونه میتوانید یک یک سرویس را با استفاده از متد startService()اجرا و با متدstopService() متوقف کنید هم چنین شما یاد گرفتید که چگونه میتوانید کار های با زمان های طولانی را در Thread های جداگانه اجرا کنید . نکته ای بسیار مهم این است که زمانی که اجرای Task به پایان می رسید سرویس به پایان می رسید. شما این امکان را داشتید که خودتان سرویس را زودتر متوقف کنید تا منابع استفاده شده آزاد شوند . هدر نروند.برا ی این مار شما از متدstopSelf() استفاده می کنید. متاسفانه بسیار ی از توسعه دهندگان فراموش می کنند زمانی که کار به طور کامل اجرا شد سرویس را متوقف کنند
به آسانی شما می توانید سرویس را ایجاد کنید که کار ها را به طور همزمان اجرا کند و بعد از اینکه کار را انجام داد خود را متوفق کند که این کار را شما IntentServiceبا انجام می دهید
IntentService یک کلاس پایه برای Service می باشد که درخواست های همزمان را کنترل می کند. این دقیقا شبیه سرویس ها می باشد و کارها را در ون Thread ها اجرا می کند و زمانی که کار کامل شد خود را متوقف می کند در ادامه شر ح میدهیم که چگونه می توانید از IntentServiceاستفاده کنید
به پروژه Services رفته و کلاس جدیدی به نام MyIntentService.javaایجاد کنید
دستورات زیر را درآن وارد نمایید.
package com.MehrdadJavidi.services;
import java.net.MalformedURLException;
import java.net.URL;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentServiceName");
}
@Override
protected void onHandleIntent(Intent intent) {
try {
int result = DownloadFile(new URL(
"http://www.amazon.com/somefile.pdf"));
Log.d("IntentService", "Downloaded " + result + " bytes");
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
private int DownloadFile(URL url) {
try {
// ---simulate taking some time to download a file---
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}
}
فایلAndroidManifest.xml به صورت زیر تغییر دهید
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.MehrdadJavidi.services"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="15" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService" >
<intent-filter>
<action android:name="com.MehrdadJavidi.MyService" />
</intent-filter>
</service>
<service android:name=".MyIntentService" />
</application>
</manifest>
دستورات زیر را در فایل mainActivity.java وارد نمایید.
public void startService(View view) {
// startService(new Intent(getBaseContext(), MyService.class));
// OR
startService(new Intent(getBaseContext(), MyIntentService.class));
}
برنامه را با F11 اجرا کنید
بر روی دکمه Start Service کلیک کنید
بعد از 5 ثانیه نتیجه را در پنجره LogCat مشاهده می کنید
12-06 13:35:32.181: D/IntentService(861): Downloaded 100 bytes
توضیحات
اول شما کلاس MyIntentService تعریف کردید که از IntentService مشتق شده است
MyIntentService extends IntentService {
شما نیاز دارید که سازنده کلاس را تعریف کنید و درآن superclass را با نام فراخوانی کنید
public MyIntentService() {
super("MyIntentServiceName");
}
سپس شما متد onHandleIntent() را به کار گرفته اید که کار را در یک Thread اجرا میکند.
@Override
protected void onHandleIntent(Intent intent) {
try {
int result = DownloadFile(new URL(
"http://www.amazon.com/somefile.pdf"));
Log.d("IntentService", "Downloaded " + result + " bytes");
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
متد onHandleIntent() جایی است که شما دستورا ت خود را که می خواهید در Thread جداگانه اجرا شود در آن قرار میدهید. به عنوان مثال دانلود فایل از اینترنت. زمانی که اجرای کار به پایان رسید thread متوقف می شود سرویس را به صورت اتوماتیک متوقف میکند.
ایجاد ارتباط بین سرویس و activity
اغلب سرویس ها در thread مربوط به خودشان اجرا می شوند. ومستقل از activity ها صدا زده می شوند. هیچ مشکلی ایجاد نمی کنند اگر بخواهید کار های در سرویس ها اجرا کنید و Activity نیاز به دانستن وضعیت سرویس ها ندارد. به عنوان مثال ممکن است سرویس مکان جغرافیای دستگا ها رادر دیتابیس ثبت نمایید. در این مورد نیازی نیست که سرویس با acivity ارتباط داشته باشد. زیرا هدف اصلی ذخیره اطلاعت در دیتایس میباشد. زمان های وجود دارد که شما می خواهید که سرویس با acitivty ارتباط داشته باشد که در ادامه نحوه ای انجام این کار را را شرح میدهیم.
درزیر به شما شرح میدهیم که چگونه یک سرویس می تواند باBroadcastReceiver. به یک acitivity ارتباط داشته باشد
پروژهServices استفاده می کنیم دستورات مشخص شده را در فایل MyIntentService.javaوارد نمایید.
package com.MehrdadJavidi.Services;
import java.net.MalformedURLException;
import java.net.URL;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentServiceName");
}
@Override
protected void onHandleIntent(Intent intent) {
try {
int result =
DownloadFile(new URL("http://www.amazon.com/somefile.pdf"));
Log.d("IntentService", "Downloaded " + result + " bytes");
//---send a broadcast to inform the activity
// that the file has been downloaded---
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("FILE_DOWNLOADED_ACTION");
getBaseContext().sendBroadcast(broadcastIntent);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
private int DownloadFile(URL url) {
try {
//---simulate taking some time to download a file---
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return 100;
}
}
دستورات در فایل mainActiviy.java وارد نمایید
package com.MehrdadJavidi.services;
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.Toast;
public class MainActivity extends Activity {
IntentFilter intentFilter;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public void onResume() {
super.onResume();
// ---intent to filter for file downloaded intent---
intentFilter = new IntentFilter();
intentFilter.addAction("FILE_DOWNLOADED_ACTION");
// ---register the receiver---
registerReceiver(intentReceiver, intentFilter);
}
@Override
public void onPause() {
super.onPause();
// ---unregister the receiver---
unregisterReceiver(intentReceiver);
}
public void startService(View view) {
// startService(new Intent(getBaseContext(), MyService.class));
// OR
// startService(new Intent("net.learn2develop.MyService"));
startService(new Intent(getBaseContext(), MyIntentService.class));
}
public void stopService(View view) {
stopService(new Intent(getBaseContext(), MyService.class));
}
private BroadcastReceiver intentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(getBaseContext(), "File downloaded!",
Toast.LENGTH_LONG).show();
}
};
}
برنامه را با F11 اجرا کنید
بر روی Start Serviceکلیک کنید بعد از 5 ثانیه پیامی نمایش داده می شود که تعیین میکند فایل دانلدو شده است.
توضیحات
برای پیغام دادن به Activiyt زمانی که سرویس به پایان رسید شما با استفاد ه از متد sendBroadcast() یک intent را به طور سراسر ارسال می کنید (broadcast) می کنید
protected void onHandleIntent(Intent intent) {
try {
int result = DownloadFile(new URL(
"http://www.amazon.com/somefile.pdf"));
Log.d("IntentService", "Downloaded " + result + " bytes");
// ---send a broadcast to inform the activity
// that the file has been downloaded---
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("FILE_DOWNLOADED_ACTION");
getBaseContext().sendBroadcast(broadcastIntent);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
در این اینجا intent ی که شما broadcast کردید به FILE_DOWNLOADED_ACTION” تنظیم شده است که به این معنی می باشد که acivity که در حال گوش دادن به این intent میباشد باید invoked.شود از این رو فایل mainActivity.java . شما به intent گوش میدهد با متدregisterReceiver() از کلاس IntentFilter
@Override
public void onResume() {
super.onResume();
// ---intent to filter for file downloaded intent---
intentFilter = new IntentFilter();
intentFilter.addAction("FILE_DOWNLOADED_ACTION");
// ---register the receiver---
registerReceiver(intentReceiver, intentFilter);
}
زمانی که intent دریافت شد یک نمونه از کلاس BroadcastReceiverکه تعریف کردهایم invokes (فرخوانی) می شود
private BroadcastReceiver intentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(getBaseContext(), "File downloaded!",
Toast.LENGTH_LONG).show();
}
};
در اینجا شماFile downloaded!” نمایش دادید اگر شما بخواهید یک سری داده را به acivity ارسال کنید ر اینجا شما باید ازشی Intent استفاده کنید.که در قسمت بعدی آن را شرح می دهیم
Bind کردن activity به سرویس ها
اینجا شما یادگرفتید که چگونه می توان سرویس ها راه اندازی و متوفق کردو شما در استفاده از سرویس ها از آن های به طور ساده اسفتاده کردید به عنوان مثال شما یکcounter ر ا اضافه کردید یا چند فایل ثابت را دانلود کردید. که در جهان واقعی معمولا سرویس ها بسیار کارامد تر و پرکاربر تر می باشند
در سرویس های که قبل تر شرح دادیم شما فایل های را از اینترنت دانلود می کردید.
حال می خواهیم اطلاعات را از Activity به سرویس ها ارسال کنیم.
در ابتدا شما به صورت زیر یک شی intent ایجاد می کردید وا توسط آن سرویس را فراخوانی می کردید.
public void startService(View view) {
Intent intent = new Intent(getBaseContext(), MyService.class);
}
شما می توانید توسط متد putExtra()در شی Intent شی ها ی Url را به به سرویس ارسال کنید.
public void startService(View view) {
Intent intent = new Intent(getBaseContext(), MyService.class);
try {
URL[] urls = new URL[] {
new URL("http://www.amazon.com/somefiles.pdf"),
new URL("http://www.wrox.com/somefiles.pdf"),
new URL("http://www.google.com/somefiles.pdf"),
new URL("http://www.learn2develop.net/somefiles.pdf")};
intent.putExtra("URLs", urls);
} catch (MalformedURLException e) {
e.printStackTrace();
}
startService(intent);
}
باید توجه کنید که شی های Url به عنوان آرایه ای از object ها به intent داده شده است
در آخر هم ما نیاز داریم تا اطلاعات ارسال شده توسط شی intent در onStartCommand باز یابی کنید
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();
Object[] objUrls = (Object[]) intent.getExtras().get("URLs");
URL[] urls = new URL[objUrls.length];
for (int i=0; i<objUrls.length-1; i++) {
urls[i] = (URL) objUrls[i];
}
new DoBackgroundTask().execute(urls);
return START_STICKY;
}
در بالا داده های را با متد getExtrs() داد های را بازیابی و استفاده کردید. که در ایتجا برای داده های پیچیده کار سخت می شود باید مطمئن بشوید که داده های به درستی به نوع اولیه تبدیل می شوند واین کار را پیچیده می کند.
که ادامه راه حلی دیگری را ارائه میدهیم.
به پروژه قبل بروید و فایل MyService به صورت زیر تغییر دهید
import android.os.Binder;
import android.os.IBinder;
public class MyService extends Service {
int counter = 0;
URL[] urls;
static final int UPDATE_INTERVAL = 1000;
private Timer timer = new Timer();
private final IBinder binder = new MyBinder();
public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}
@Override
public IBinder onBind(Intent arg0) {
return binder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
Toast.makeText(this, “Service Started”, Toast.LENGTH_LONG).show();
new DoBackgroundTask().execute(urls);
return START_STICKY;
}
private void doSomethingRepeatedly() { … }
private int DownloadFile(URL url) { ... }
private class DoBackgroundTask extends AsyncTask<URL, Integer, Long> { ... }
@Override
public void onDestroy() { ... }
}
در mainActivity به صورت زیر تغییر دهید
import android.content.ComponentName;
import android.os.IBinder;
import android.content.ServiceConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class ServicesActivity extends Activity {
IntentFilter intentFilter;
MyService serviceBinder;
Intent i;
private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(
ComponentName className, IBinder service) {
//—-called when the connection is made—-
serviceBinder = ((MyService.MyBinder)service).getService();
try {
URL[] urls = new URL[] {
new URL("http://www.amazon.com/somefiles.pdf"),
new URL("http://www.wrox.com/somefiles.pdf"),
new URL("http://www.google.com/somefiles.pdf"),
new URL("http://www.learn2develop.net/somefiles.pdf")};
//---assign the URLs to the service through the
// serviceBinder object---
serviceBinder.urls = urls;
} catch (MalformedURLException e) {
e.printStackTrace();
}
startService(i);
}
public void onServiceDisconnected(ComponentName className) {
//---called when the service disconnects---
serviceBinder = null;
}
};
public void startService(View view) {
i = new Intent(ServicesActivity.this, MyService.class);
bindService(i, connection, Context.BIND_AUTO_CREATE);
}
@Override
public void onCreate(Bundle savedInstanceState) { ... }
@Override
public void onResume() { ... }
@Override
public void onPause() { ... }
public void stopService(View view) { ... }
private BroadcastReceiver intentReceiver = new BroadcastReceiver() {
...
};
}
توضیحات
برای bind کردن یک Activity به یک Service شما ابتدا شما یک کلاس داخلی که از کلاس پایه Binder مشتق شده تعریف می کنید.
public class MyBinder extends Binder {
MyService getService() {
return MyService.this;
}
}
درون اون متد getService() تعریف می کنید که یک نمونه از سرویس را بر می گرداند.
سپس یک نمونه از MyBinder ایجاد می کنید
private final IBinder binder = new MyBinder();
شما همچنین یک متد onBind() را غییر دادید که یک نمونه از MyBinder بر می گرداند
در متد onStartCommand() شما متد execute() را فراخوانی کردید که یک آرایه از Url را می گیرد که از آرایه تعریف شده در Service که به صورت Public می باشد استفاده می کند.
public class MyService extends Service {
int counter = 0;
URL[] urls;
...
...
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
Toast.makeText(this, “Service Started”, Toast.LENGTH_LONG).show();
new DoBackgroundTask().execute(urls);
return START_STICKY;
}
}
که این آرایه به صورت مستقیم از Activity مقدار دهی می شود که در ادامه می بینید
در ServicesActivity.java یک شی Intent تعریف کردید
MyService serviceBinder;
Intent i;
شی serviceBinder به عنوان دسترسی مستقیم به سرویس واشاره به آن استفاده می شود.
سپس شما یک نمونه از ServiceConnection ایجاد کردید که وضعیت سرویس را مانیتور می کنید.
private ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(
ComponentName className, IBinder service) {
//---called when the connection is made---
serviceBinder = ((MyService.MyBinder)service).getService();
try {
URL[] urls = new URL[] {
new URL(“http://www.amazon.com/somefiles.pdf”),
new URL(“http://www.wrox.com/somefiles.pdf”),
new URL(“http://www.google.com/somefiles.pdf”),
new URL(“http://www.learn2develop.net/somefiles.pdf”)};
//---assign the URLs to the service through the
// serviceBinder object---
serviceBinder.urls = urls;
} catch (MalformedURLException e) {
e.printStackTrace();
}
startService(i);
}
public void onServiceDisconnected(ComponentName className) {
//---called when the service disconnects---
serviceBinder = null;
}
};
شما باید دو متد onServiceConnected() and onServiceDisconnected(). به کار بگیرید زمانی که Activity به سرویس connect می شود متد onServiceConnected() فراخوانی میشود و متد onServiceDisconnected() زمانی که Activity و سرویس DisConnect می شوند اجرا می شوند.
فهمیدن Thread ها
برای شروع شما یک پروژه جدید به نام Threading. ایجاد کنید وفایلmain.xml به صورت زیر تغیر دهید
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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/btnStartCounter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startCounter"
android:text="Start" />
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView" />
</LinearLayout>
هدف ما در ایتنجا این است که اعداد 1 تا 1000 را نمایش دهیم. در فایل ThreadingActivity دستورات زیر را وارد نمایید
package com.MehrdadJavidi.Threading;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class ThreadingActivity extends Activity {
TextView txtView1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
txtView1 = (TextView) findViewById(R.id.textView1);
}
public void startCounter(View view) {
for (int i=0; i<=1000; i++) {
txtView1.setText(String.valueOf(i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d("Threading", e.getLocalizedMessage());
}
}
}
}
برنامه را اجرا کنید و بر روی دکمه Start buttonکلیک کنید برنام به حاتfrozen,می رود و بعد از مدتی پیغام زیر را مشاهده می کنید
برنامه به حالت froze می رود
برای رفع این مشکل شما باید از قطعه کد زیر که از کلاس های Thread and Runnable استفاده می کنید به کار بگیرید.
public void startCounter(View view) {
new Thread(new Runnable() {
public void run() {
for (int i=0; i<=1000; i++) {
txtView1.setText(String.valueOf(i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d("Threading", e.getLocalizedMessage());
}
}
}
}).start();
}
با این حال قطعه کدبالا کار نمی کند . اگر برنامه اجرا کنید برنامه با خطا روبرو می شود .به دلیل این که شما می خو هدی ui را از یک thread دیگر به روز رسانی کنید
برای حل این مسئله شما باید از متد Post مربوط به View استفاده کنید که یک Runnable ایجاد می کنید
public void startCounter(View view) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<=1000; i++) {
final int valueOfi = i;
//---update UI---
txtView1.post(new Runnable() {
public void run() {
//---UI thread for updating---
txtView1.setText(String.valueOf(valueOfi));
}
});
//---insert a delay
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d("Threading", e.getLocalizedMessage());
}
}
}
}).start();
}
یک روش دیگر برای به روز رسانی Ui در یک Thread دیگر استفاده از کلاس Handler میباشد
Handler شما را قاد ر می سازد تا پیام ها یا پروسس های را همانند post برای View ها انجام دهید . که در زیر نحوه ای انجام این کار را بیان می کنیم.
//---used for updating the UI on the main activity---
static Handler UIupdater = new Handler() {
@Override
public void handleMessage(Message msg) {
byte[] buffer = (byte[]) msg.obj;
//---convert the entire byte array to string---
String strReceived = new String(buffer);
//---display the text received on the TextView---
txtView1.setText(strReceived);
Log.d("Threading", "running");
}
};
public void startCounter(View view) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<=1000; i++) {
//---update the main activity UI---
ThreadingActivity.UIupdater.obtainMessage(
0, String.valueOf(i).getBytes() ).sendToTarget();
//---insert a delay
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d("Threading", e.getLocalizedMessage());
}
}
}
}).start();
}
تا ایجا به شما update کردن یک UI در یک thrad جداگانه بیان کردیم.
یک راه حل ساده استفاده از کلاس AsyncTask می باشد که شما می توانید کد را به صورت زیر تغییر دهید.
private class DoCountingTask extends AsyncTask<Void, Integer, Void> {
protected Void doInBackground(Void... params) {
for (int i = 0; i < 1000; i++) {
//---report its progress---
publishProgress(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d("Threading", e.getLocalizedMessage());
}
}
return null;
}
protected void onProgressUpdate(Integer... progress) {
txtView1.setText(progress[0].toString());
Log.d("Threading", "updating...");
}
}
public void startCounter(View view) {
new DoCountingTask().execute();
}
در کد بالا ما Update کردن Ui را را انجام داد ه ایم .اما در مورد Stop کردن Thread چیزی نگفتیم.
در کد بالا زمانی که بر روی دکمه Start کلیک می کنیم Counter از صفر شروع به نمایش دادن می شود با این حال اگر دکمه Back را بزنیم و از برنامه خارج شویم و Activity نابود شود هنوز هم thread در حال اجرا می باشد. برای Stop کردن Thread کد را مانند زیر تغییر می دهیم.
public class ThreadingActivity extends Activity {
static TextView txtView1;
DoCountingTask task;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
txtView1 = (TextView) findViewById(R.id.textView1);
}
public void startCounter(View view) {
task = (DoCountingTask) new DoCountingTask().execute();
}
public void stopCounter(View view) {
task.cancel(true);
}
private class DoCountingTask extends AsyncTask<Void, Integer, Void> {
protected Void doInBackground(Void... params) {
for (int i = 0; i < 1000; i++) {
//---report its progress---
publishProgress(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d("Threading", e.getLocalizedMessage());
}
if (isCancelled()) break;
}
return null;
}
protected void onProgressUpdate(Integer... progress) {
txtView1.setText(progress[0].toString());
Log.d("Threading", "updating...");
}
}
@Override
protected void onPause() {
super.onPause();
stopCounter(txtView1);
}
}