Communicate with service in android

 

یکی از راه های ارتباط با سرویس استفاده از Bound Service ها هست. تعریفی که از Bound Service در داکیومنت اندروید امده بشرح زیر است.

A bound service is the server in a client-server interface. It allows components (such as activities) to bind to the service, send requests, receive responses, and perform interprocess communication (IPC). A bound service typically lives only while it serves another application component and does not run in the background indefinitely.

همانطور که در متن بالا نوشته شده با استفاده از Bound Service، کامپوننت ها مثل اکتیویتی میتونن به سرویس bind بشن و درخواست های خودشون رو به سرویس بفرستن و یا response های مورد نظر خودشون رو دریافت کنند.

نکته مهمی که ذکر شده اینه که Bound Service ها تا زمانی که کامپوننتی بهش متصل باشه به حیات خودش ادامه میده و بصورت نامحدود در background اجرا نمیشه.

خب در اینجا فرضا بخوایم یک پخش کننده موسیقی بسازیم نیاز هست که سرویس ما در background در حال اجرا باشه و وقتی که کاربر در اپلیکیشن هست اکتیویتی یا هرکامپوننتی بتونن با سرویس تبادل اطلاعات کنن مثلا بتونن برای نمایش Seek bar، اطلاعات مکان زمانی فایل صوتی رو بگیرن و نمایش بدن. یعنی نیاز داریم به یک سرویس از نوع STICKY. که این امکان به ما داده شده که هم سرویس ما از نوع Sticky باشه و هم Bound Service باشه.

برای ادامه به ادامه مطلب مراجعه کنید

As discussed in the Services document, you can create a service that is both started and bound. That is, you can start a service by calling startService(), which allows the service to run indefinitely, and you can also allow a client to bind to the service by calling bindService().

که در داکیومنت متن بالا اورده شده که برای اینکه سرویس بتونه نامحدود در پس زمینه اجرا بشه از startService استفاده کنیم و هرجا کامپوننتی خواست با سرویس ارتباط برقرار کنه و bind بشه از bindService استفاده کنیم.

نکته مهمی که هست وقتی سرویس ما Sticky و Bound باشه و سرویس رو از طریق startService اجرا بکنیم، باید خودمون سرویس رو stop بکنیم و حتی اگه همه کامپوننت هایی که به سرویس متصل هستن unbind بشن همچنان سرویس به حیات خودش ادامه میده.

If you do allow your service to be started and bound, then when the service has been started, the system does not destroy the service when all clients unbind. Instead, you must explicitly stop the service by calling stopSelf() or stopService().

 در ادامه در داکیومنت گفته شده که برای اینکه کامپوننتی رو به سرویس bind کنیم باید ServiceConnection رو پیاده سازی کنیم که در ادامه در قالب یک مثال اینکار رو میکنیم.

تا اینجا تا حدودی با مقدمات کار آشنا شدیم و حالا در قالب یک مثال کوچک کار رو جلو میبریم. فرض میکنیم میخواهیم یک سرویس ایجاد کنیم که مسئولیت پخش یک فایل صوتی رو از روی موبایل داره.اسم این سرویس رو PlayerService میگذارم و فرایند پخش صوت و ایجاد Notification رو قرار ندادم که بتونیم به اصل موضوع بپردازیم.

public class PlayerService extends Service{
    private static final String EXTRA_MUSIC_INDEX = "music_index";
    private final IBinder mBinder = new PlayerBinder();
    private int mMusicIndex;
	
    public class PlayerBinder extends Binder {
        public PlayerService getService() {
            // Return this instance of PlayerService so clients can call public methods
            return PlayerService.this;
        }
    }
    @Override
    public void onCreate() {
	super.onCreate();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
	mMusicIndex = intent.getIntExtra(EXTRA_MUSIC_INDEX, 0);
	play();
	return START_STICKY;
    }

    private void play() {
        //Play Music 
	//and
	//show notification, because 'START_STICKY'
    }
	
    @Override
    public IBinder onBind(Intent intent) {
	return mBinder;
    }
    public int getCurrentPosition() {
        //این یک متد تست هست که میخواهیم کامپوننت ها بتونن این متد رو کال بکنن و مقدار مکان زمانی فایل صوتی رو دریافت کنن
        //return MediaPlayer CurrentPosition
	//for example 
	//MediaPlayer mMediaPlayer;
	//...
	//return mMediaPlayer.getCurrentPosition();
    }

    public static Intent newIntent(Context context, int musicIndex) {
        Intent i = new Intent(context, PlayerService.class);
        i.putExtra(EXTRA_MUSIC_INDEX, musicIndex);
        return i;
    }
}

برای اینکه بتونیم Bound Service ایجاد کنیم باید اینترفیس IBinder رو بطریقی ایجاد کنیم. در داکیومنت سه روش برای اینکار ذکر کرده که من روش اول رو توضیح میدم.

Extending the Binder class

If your service is private to your own application and runs in the same process as the client (which is common), you should create your interface by extending the Binder class and returning an instance of it from onBind(). The client receives the Binder and can use it to directly access public methods available in either the Binder implementation or the Service.

This is the preferred technique when your service is merely a background worker for your own application. The only reason you would not create your interface this way is because your service is used by other applications or across separate processes.

همانطور که در بالا نوشته شده اگه سرویس ما local هست و قراره فقط اپلیکیشن خودمون ازش استفاده کنه کافیه یک کلاس ایجاد کنیم که از Binder ارث برده باشه  و یک متد public هم داخلش قرار میدیم که خود سرویس رو return میکنه.(return PlayerService.this) و بعد از طریق متد onBind یک نمونه ازین کلاس ساخته شده رو برمیگردونیم.

در کد کلاسی که از Binder ارث برده PlayerBinder هست و یک شی بصورت instance variable از IBinder  در کلاس سرویس بنام mBinder ساختیم که از طریق کلاس PlayerBinder نمونه سازی کردیم. سپس در متد onBind نمونه ساخته شده رو یعنی mBinder رو return میکنیم.

تا اینجا کار سرویس ما به اتمام رسید و حالا باید بریم سراغ کامپوننتی که میخوایم به این سرویس متصل بشه و این سرویس رو اجرا کنه.

یک Activity به نام PlayerActivity ساختم که کدش بصورت زیر است.

public class PlayerActivity extends AppCompatActivity{
    private static final String TAG = PlayerActivity.class.getSimpleName();
    private static final String EXTRA_MUSIC_INDEX = "ir.amirshahroudi.musicplayer.music_index";
    private PlayerService mPlayerService;
    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,IBinder service) {
            PlayerService.PlayerBinder binder = (PlayerService.PlayerBinder) service;
            mPlayerService = binder.getService();
			Log.i(TAG, "onServiceConnected: ");
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
			Log.i(TAG, "onServiceDisconnected: ");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_player);
        mMusicIndex = getIntent().getIntExtra(EXTRA_MUSIC_INDEX, 0);
	startAndBindService();
	//Now we can call this mPlayerService.getCurrentPosition()
    }
    private void startAndBindService() {
        Intent playerIntent = PlayerService.newIntent(this, mMusicIndex);
        startService(playerIntent);
        bindService(playerIntent, connection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onStop() {
        super.onStop();
        unbindService(connection);
    }
    public static Intent newIntent(Context packageContext, int musicIndex) {
        Intent intent = new Intent(packageContext, PlayerActivity.class);
        intent.putExtra(EXTRA_MUSIC_INDEX, musicIndex);
        return intent;
    }
}خ

خب در ابتدا یکسری متغیر String تعریف میکنیم و بعد ازون یک متغیر از نوع PlayerService ایجاد میکنیم تا بتونیم از طریق این متغیر متد getCurrentPosition رو صدا بزنیم.

همانطور که در اول بحث گفته شد باید با استفاده از ServiceConnection اکتیویتیمون رو به PlayerService متصل کنیم. برای همین یک نمونه ازش مثل بالا میسازیم و بعد از cast کردن متد getService رو کال میکنیم تا نمونه PlayerService رو بگیریم و داخل mPlayerService میریزیم.

برای وصل شدن اکتیویتی به سرویس از  bindService استفاده میکنیم. نکته حایز اهمیت پارامتر سوم این متد هست که اگر Context.BIND_AUTO_CREATE باشه متد onCreate سرویسمون فراخونده میشه درغیر اینصورت این متد کال نمیشه.

برای اینکه سرویسمون نامحدود باشه و از نوع STICKY، از متد startService استفاده میکنیم.توجه کنیم که وقتی به تنهایی از متد bindService استفاده کنیم، onStartCommand سرویسمون اجرا نمیشه.

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

بطور مثال اگر بخوایم مکان زمانی فایل صوتی رو از سرویس بگیریم میتونیم براحتی متد getCurrentPosition سرویسمون رو کال کنیم.

mPlayerService.getCurrentPosition();

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

getCurrentPosition یک متد کلاس MediaPlayer هست که من از اصطلاح مکان زمانی فایل صوتی استفاده کردم اگر معادل فارسی بهتری سراغ دارین لطفا با من به اشتراک بزارید.

 

یاعلی