[컴][안드로이드] service 에 bind 하기 - local service


Service 의 종류

bind 는 service 에 연결하는 작업이라고 생각하면 된다. service 는 activity 와 같은 process 내에서 존재하는 local service 와 activity 와 service 가 각각의 process 로 존재하는 remote service 가 존재한다.
  • 서비스 종류
    1. local service
    2. remote service
참고 : What is service ? [see also. 4]


Bound Services

Bound Service 는 Service 를 구현한 것인데, 자신에게 Bind 를 할 수 있게 해주는 Service 이다. 이 Bind 를 하면, Service 와 interact 할 수 있는 것이다. 쉽게 얘기하면, bind 를 통해서 Service 객체의 reference 를 가져올 수 있다는 이야기이다.(이 부분은 see also. 6. 에서 IPC, RPC 의 개념으로 잘 설명해 주고 있다.)

참고로, 일반적인 Service 를 사용해도 Intent 를 이용해서 간단한 수준의 정보는 주고 받을 수 있다. 아래 예제에서 Controller 가 그냥 단순한 수준의 Service 이다. 하지만 service 의 method 등을 호출하거나 할 수는 없다.(?)

Bound Service 는 onBind 를 구현(implement) 해야만 bindService() 로 service 에 bind 할 수 있다.

connection 이 맺어지면 ServiceConnection 의 onServiceConnected() 를 호출하면서 IBinder 를 전달해 준다.

여러 client 가 service 에 접속할 수 있지만, service 의 onBind() method 는 딱 한번만 호출하는데, 처음 client 가 bind 할 때 한번만 onBind() 를 호출한다.

그리고 마지막 client 가 service 를 unbind 하면 시스템이 그 service 를 destroy 한다.


이 onBind 를 만드는 방법이 3가지 인데,
  • IBinder 를 extends 하거나 : local application 에서만 쓰이는 service 를 만들 때 사용한다. 흔히 알고 있는 music player 같은 것들을 만들 때 사용할 수 있다.
  • Messenger 를 이용하거나
  • AIDL 을 이용하는 것
이다. 여기서는 첫번째 방법인 IBinder 를 extends 해서 사용하는 방법을 알아 볼 것이다.

자세한 사항은 ref. 3 을 참고하자.



Service Life Cycle

서비스를 시작하는 방법은 아래 2가지 이다.

  1. startService() 를 이용하는 방법
  2. bindService() 를 이용하는 방법

이렇게 시작방법이 다른 녀석들은 다른 life cycle 을 갖는다. ref.7 에 보면 그림을 확인할 수 있다.

startService() 로 시작된 service 는 혼자서 잘 돌다가 stopSelf() 를 호출해서 스스로 멈춰야만 한다. 다른 녀석이 stopService() 를 이용해서 이 service 를 멈출 수도 있다. 여하튼 이렇게 멈추게 된 service 는 system 이 destroy 하게 된다.

그런데 bindService() 를 통해서 생성된 service 는 조금 다르다. 보통 이 bindService() 를 호출하는 녀석을 client 라 부르는데, 이 client 와 service 는 IBinder interface 를 통해서 통신하게 된다. client 와 service 간의 통신은 unbindService() 로 끝낼 수 있다. 이렇게 bindService 를 이용해서 여러 client 가 하나의 service 에 bind 해서 사용할 수 있다. 그렇기 때문에 unbindService() 를 호출하는 것만으로는 system 이 service 를 destroy 하지 않으며, 모든 bindService() 했던 client 가 unbind되어야만 비로서 service 를 destroy 하게 된다.
참고
startService 와 bindService 의 종료 순간

그런데 이녀석들은 완전하게 분리된 개념이 아니다. startService() 로 이미 시작된 service 를 bindService 를 통해서 service 에 bind 할 수 있다. 이렇게 bindService() 로 service 에 bind 하는 녀석이 생기면 service 는 bindService() 로 생성된 service 의 life cycle 을 따르게 된다.[ref. 7]

bindService  와 startService 를 같이 쓰는 예제는 ref. 8 에서, 그것과 관련된 설명은 ref. 9에서 확인할 수 있다.



startService

사용법

  1. explicit Intent : startService(new Intent(Controller.this, LocalService.class));
  2. implicit intent
    1. startService(new Intent("mp3playerremote.REMOTE_SERVICE"));
    2. startService(new Intent(IRemoteService.class.getName()));
  3. // AndroidManifest.xml
    
    <service android:name=".mp3playerremote.RemoteService" android:process=":remote">
        <intent-filter>
            <!-- These are the interfaces supported by the service, which
                 you can bind to. -->
            <action android:name="com.example.samples.mp3playerremote.IRemoteService" />
            <action android:name=".mp3playerremote.ISecondary" />
            <!-- This is an action code you can use to select the service
                 without explicitly supplying the implementation class. -->
            <action android:name="mp3playerremote.REMOTE_SERVICE" />
        </intent-filter>
    </service>
    

flow of source codes

  1. startService()
  2. ContextImpl.startService()
  3. ContextImpl.startServiceAsUser()
  4. ActivityManagerProxy.startService()
  5. mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);
  6. public void handleMessage(Message msg) {
     switch (msg.what) {
      ...
      case CREATE_SERVICE:
         handleCreateService((CreateServiceData)msg.obj);
      ...
    
  7. handleCreateService()
  8. Service.onCreate()
  9. public void handleMessage(Message msg) {
     switch (msg.what) {
      ...
      case SERVICE_ARGS:
         handleServiceArgs((ServiceArgsData)msg.obj);
      ...
    
  10. handleServiceArgs()
  11. Service.onStartCommand()

  1. stopService()
  2. Service.onDestroy()





Bind Local service

아래 예제는 button 을 click 하면 local service 에 대한 bind 동작이 수행되게 되어있다. click 을 하고 나면 bind 는 아래와 같은 순서로 실행된다.
  1. LocalServiceActivities$Binding.java
    1. void doBindService()
    2. bindService(new Intent(Binding.this, LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
  2. LocalService.java
    1. private final IBinder mBinder = new LocalBinder();
    2. public IBinder onBind(Intent intent) { return mBinder; }
  3. LocalServiceActivities.java
    1. private ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
이렇게 해서 IBinder service 를 얻으면 이제 이 service 를 가지고 bind 된 서비스의 method 나 field 를 사용하면 되는 것이다.

보통 bindService 를 onStart() 에서 하고나서 unBindService 는 onStop() 에서 하면 된다. 코드는 ref. 3 을 참고하자.




아주 간단한 Service

아주 간단한 Service 이용방법은 See Also. 을 참조하도록 하자. 간단하게 Service 에 원하는 Intent 를 보내는 정도의 예제를 확인할 수 있다.



LocalServiceActivities.java

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.apis.app;

import com.example.android.apis.R;

import android.app.Activity;
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.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class LocalServiceActivities {
    /**
     * <p>Example of explicitly starting and stopping the local service.
     * This demonstrates the implementation of a service that runs in the same
     * process as the rest of the application, which is explicitly started and stopped
     * as desired.</p>
     * 
     * <p>Note that this is implemented as an inner class only keep the sample
     * all together; typically this code would appear in some separate class.
     */
    public static class Controller extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            setContentView(R.layout.local_service_controller);

            // Watch for button clicks.
            Button button = (Button)findViewById(R.id.start);
            button.setOnClickListener(mStartListener);
            button = (Button)findViewById(R.id.stop);
            button.setOnClickListener(mStopListener);
        }

        private OnClickListener mStartListener = new OnClickListener() {
            public void onClick(View v) {
                // Make sure the service is started.  It will continue running
                // until someone calls stopService().  The Intent we use to find
                // the service explicitly specifies our service component, because
                // we want it running in our own process and don't want other
                // applications to replace it.
                startService(new Intent(Controller.this,
                        LocalService.class));
            }
        };

        private OnClickListener mStopListener = new OnClickListener() {
            public void onClick(View v) {
                // Cancel a previous call to startService().  Note that the
                // service will not actually stop at this point if there are
                // still bound clients.
                stopService(new Intent(Controller.this,
                        LocalService.class));
            }
        };
    }

    // ----------------------------------------------------------------------

    /**
     * Example of binding and unbinding to the local service.
     * This demonstrates the implementation of a service which the client will
     * bind to, receiving an object through which it can communicate with the service.</p>
     * 
     * <p>Note that this is implemented as an inner class only keep the sample
     * all together; typically this code would appear in some separate class.
     */
    public static class Binding extends Activity {
        private boolean mIsBound;


        private LocalService mBoundService;
        
        private ServiceConnection mConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                // This is called when the connection with the service has been
                // established, giving us the service object we can use to
                // interact with the service.  Because we have bound to a explicit
                // service that we know is running in our own process, we can
                // cast its IBinder to a concrete class and directly access it.
                mBoundService = ((LocalService.LocalBinder)service).getService();
                
                // Tell the user about this for our demo.
                Toast.makeText(Binding.this, R.string.local_service_connected,
                        Toast.LENGTH_SHORT).show();
            }

            public void onServiceDisconnected(ComponentName className) {
                // This is called when the connection with the service has been
                // unexpectedly disconnected -- that is, its process crashed.
                // Because it is running in our same process, we should never
                // see this happen.
                mBoundService = null;
                Toast.makeText(Binding.this, R.string.local_service_disconnected,
                        Toast.LENGTH_SHORT).show();
            }
        };
        
        void doBindService() {
            // Establish a connection with the service.  We use an explicit
            // class name because we want a specific service implementation that
            // we know will be running in our own process (and thus won't be
            // supporting component replacement by other applications).
            bindService(new Intent(Binding.this, 
                    LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
        }
        
        void doUnbindService() {
            if (mIsBound) {
                // Detach our existing connection.
                unbindService(mConnection);
                mIsBound = false;
            }
        }
        
        @Override
        protected void onDestroy() {
            super.onDestroy();
            doUnbindService();
        }


        private OnClickListener mBindListener = new OnClickListener() {
            public void onClick(View v) {
                doBindService();
            }
        };

        private OnClickListener mUnbindListener = new OnClickListener() {
            public void onClick(View v) {
                doUnbindService();
            }
        };
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            setContentView(R.layout.local_service_binding);

            // Watch for button clicks.
            Button button = (Button)findViewById(R.id.bind);
            button.setOnClickListener(mBindListener);
            button = (Button)findViewById(R.id.unbind);
            button.setOnClickListener(mUnbindListener);
        }
    }
}

LocalService.java
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.apis.app;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

// Need the following import to get access to the app resources, since this
// class is in a sub-package.
import com.example.android.apis.R;

/**
 * This is an example of implementing an application service that runs locally
 * in the same process as the application.  The {@link LocalServiceActivities.Controller}
 * and {@link LocalServiceActivities.Binding} classes show how to interact with the
 * service.
 *
 * <p>Notice the use of the {@link NotificationManager} when interesting things
 * happen in the service.  This is generally how background services should
 * interact with the user, rather than doing something more disruptive such as
 * calling startActivity().
 */

public class LocalService extends Service {
    private NotificationManager mNM;

    // Unique Identification Number for the Notification.
    // We use it on Notification start, and to cancel it.
    private int NOTIFICATION = R.string.local_service_started;

    /**
     * Class for clients to access.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with
     * IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            return LocalService.this;
        }
    }
    
    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

        // Display a notification about us starting.  We put an icon in the status bar.
        showNotification();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("LocalService", "Received start id " + startId + ": " + intent);
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        // Cancel the persistent notification.
        mNM.cancel(NOTIFICATION);

        // Tell the user we stopped.
        Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    // This is the object that receives interactions from clients.  See
    // RemoteService for a more complete example.
    private final IBinder mBinder = new LocalBinder();

    /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText(R.string.local_service_started);

        // Set the icon, scrolling text and timestamp
        Notification notification = new Notification(R.drawable.stat_sample, text,
                System.currentTimeMillis());

        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, LocalServiceActivities.Controller.class), 0);

        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, getText(R.string.local_service_label),
                       text, contentIntent);

        // Send the notification.
        mNM.notify(NOTIFICATION, notification);
    }
}
 



Service 사용시 주의할 점

http://i5on9i.blogspot.kr/2013/03/service.html

참고로, Service 를 사용할 때 AndroidManifest.xml 에 아래 구문을 넣어줘야 한다.

<service
       android:enabled="true"
        android:name=".E2pAppWidgetProvider$UpdateService">
</service>

그리고, Service 에서도 Main Thread 가 사용되기 때문에, CPU intensive 작업은 다른 thread 를 이용해야 한다.

IntentService 같은 경우는 자신의 thread 를 가지고 있기 때문에, IntentService 를 사용하면 다른 thread 를 이용하는 효과를 가질 수 있다고 한다. 아래 글을 참고하자.
android.os.NetworkOnMainThreadException in a Service in a separate process


Activity 가 죽은 후에도 Service 는 살아있게 하려면

https://groups.google.com/forum/#!topic/android-developers/-ihI4xY2r3A
The docs for ContextWrapper.startService(Intent service) includes the
following line...
"Using startService() overrides the default service lifetime that is
managed by bindService(Intent, ServiceConnection, int): it requires
the service to remain running until stopService(Intent) is called,
regardless of whether any clients are connected to it."
This is the trick, simply run startService() before you attempt to
bind to it! So before, my onCreate contained:
        bindService(new Intent(MDService.class.getName()),
mConnection, Context.BIND_AUTO_CREATE);
Now this has been replaced by:
        Intent i = new Intent(MDService.class.getName());
        startService(i);
        bindService(i, mConnection, Context.BIND_AUTO_CREATE);
After that change, calling unbindService(mConnection) does NOT destroy
the service :) 

startForeground()

service 시작 시점에 notification 을 만들고, 이 notification 을 param 으로 주고
startForeground() 를 주면 된다. 참고로 startForeground() 를 실행하면 기본적으로 notification 이 status bar 에 등록된다. (참고 : ANDROID API 18에서 STARTFOREGROUND())

public class TestService extends Service{
    @Override
    public void start() {
        if (isInPlaybackState()) {
            ...

            if (!mIsNotificationShowing) {
                Notification noti = buildMp3PlayerNotification();
                startForeground(NOTIFICATION_ID, noti);
                mIsNotificationShowing = true;
            }
            ...
        }
    }

}

bindService() 를 하게 되면 service 에 onCreate() 가 호출된다. life cycle 은 ref. 7 에서 확인하자.



Service 가 살아있는지 알아보는 방법

http://stackoverflow.com/questions/600207/android-check-if-a-service-is-running

private boolean isMp3PlayerServiceRunning() {
    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
        if (Mp3PlayerService.class.getName().equals(service.service.getClassName())) {
            return true;
        }
    }
    return false;
}



See Also

  1. http://www.java2s.com/Code/Android/Media/Playmp3filewithinaservice.htm
  2. Android: Services, building your own MP3 player » cloud101
  3. https://github.com/commonsguy/cw-android/tree/master/Notifications/FakePlayer/src/com/commonsware/android/fakeplayerfg
  4. http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
  5. Bound Services | Android Developers
  6. 슈퍼드로이드 | 19. Service 에 대해서 - bindService 구현 (2013.02.13 갱신) - Daum 카페
  7. Managing the Lifecycle of a Service | Android Developers
  8. binding - bind/unbind service example (android) - Stack Overflow
  9. Bind service to activity in Android - Stack Overflow



Reference

  1. <android-sdk>\samples\android-13\ApiDemos\src\com\example\android\apis\app\LocalServiceActivities.java
  2. <android-sdk>\samples\android-13\ApiDemos\src\com\example\android\apis\app\LocalService.java
  3. http://developer.android.com/guide/components/bound-services.html#Binder

댓글 5개:

  1. 안녕하세요. 서비스 개념이 너무 어려워 구글 검색하다가 방문하게 되었습니다.
    코드와 적어주신 개념 좀 인용해서 제 블로그에 담을수 있을지요?


    답글삭제
    답글
    1. 네, 출처만 적어주신다면 얼마든지 사용하셔도 됩니다.

      삭제
  2. 안녕하세요. 좋은 글 잘읽어보았습니다.
    Service에 대해서 많이 깨닫고 갑니다~

    궁금한 것이 있는데.

    Service에서 네트워크를 접속할때 새로운 스레드를 생성하여 사용하는 것과,
    IntentService에서 자체로 처리하는 것의..차이가 있을까요?

    예를들어..성능이라던가..성능같은...

    감사합니다..^^ 정말 대단해요 너무좋은글이에요.

    답글삭제
    답글
    1. 해당 내용은 아래링크를 참조하심이...
      http://www.androidpub.com/102370

      삭제
  3. 잘 읽었습니다.

    답글삭제