노블의 개발이야기

[안드로이드 개발 레벨업 교과서] 3.3 Service로 백그라운드 처리를 구현하자 본문

Books

[안드로이드 개발 레벨업 교과서] 3.3 Service로 백그라운드 처리를 구현하자

더플러스 2017. 7. 31. 16:20

Service

액티비니와 프래그먼트는 화면에 표시되는 동안은 생존하지만 표시되지 않게 되면 onStop이나 onDestroy가 호출되어 폐기될 가능성이 있습니다.

백그라운드 처리를 위해 준비된 컴포넌트가 Service 입니다.

3.3.1 Service의 종류와 수명주기를 이해하자

Service의 종류

1. 백그라운드에서 동작하는 Service

Context.startService()를 호출해 시작되는 서비스입니다.

2. Binder 를 통해 바인드하는 Service

Context.bindService()를 호출해서 Service에 바인드하는 종류의 서비스입니다.

3. AIDL로 앱을 연계할 수 있는 Service

AIDL(Android Interface Definition Language: 안드로이드 인터페이스 정의 언어)을 이용하는 서비스입니다.

수명주기와 콜백




  • Service 수명주기에 따라 콜백되는 메서드
메서드명 내용
onCreate Service가 생성된 뒤에 콜백된다.
onStartCommand Service가 시작된 뒤에 콜백된다.
onBind Context.bindService()를 통해 Service가 바인드되는 경우에 호출된다.
또한 바인드 후, 서비스에 접속할 때는 ServiceConnection.onServiceConnected가 콜백된다.
onRebind 이 Service가 언바인드된 다음, 다시 접속했을 때 콜백된다.
onUnbind 이 Service가 언바인드 될 때 콜백된다.
onDestroy Service가 폐기되기 직전에 콜백된다.
  • 바인드된 Service는 모든 클라이언트로부터 언바인드됐을 때 폐기된다.
  • Service가 바인드되지 않은 채 startService로 시작된 경우에는 명시적으로 Service.stopSelf()로 Service 자신이 스스로 종료하거나 다른 컴포넌트에서 Context.stopService()를 호출해 Service를 종료했을 때 폐기됩니다.
  • Service가 바인드되고 startService로 시작된 경우 모든 클라이언트로부터 언바인드되고 또한 명시적으로 Service.stopSelf()로 Service 자신이 스스로 종료하거나 다른 컴포넌트에서 Context.stopService()를 호출해 Service를 종료했을 때 폐기됩니다.

3.3.2 상주 서비스를 만들자

Music Player Service를 만들어 보자!

서비스 만들기

  • 앱이 시작되면 서비스에 바인드하고, 현재 음악이 재생중인지 확인한다.
  • 음악 정지 상태에서 액티비티가 정지한 경우 서비스도 정지한다.
  • 음악 재생 상태에서 액티비티가 정지해도 서비스는 상주한 채로 두고 계속 음악을 재생한다.

AndroidManifest.xml

...
<application>
    ...
    <service android:name=".MusicPlayerService"
        android:enabled="true"
        android:exported="false" />
</application>

<uses-permission android:name="android.permission.WAKE_LOCK"/>

AndroidManifest.xml에 service와 permission을 설정합니다.

MainActivity.java

public class MainActivity extends AppCompatActivity {
    ...
    private MusicPlayerService mMusicPlayerService;

    private ServiceConnection mMusicPlayerServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            // 서비스에 연결되었습니다.
            mMusicPlayerService = ((MusicPlayerService.MusicPlayerBinder)binder).getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 서비스가 비정상적으로 종료되었습니다.
            mMusicPlayerService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ...
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mMusicPlayerService == null) {
            onBindService();
        }
        startService(new Intent(getApplicationContext(), MusicPlayerService.class));
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (mMusicPlayerService != null) {
            if (!mMusicPlayerService.isPlaying()) {
                // 음악이 정지 중일 경우는 서비스를 계속 실행할 필요가 없으므로 정지한다.
                mMusicPlayerService.stopSelf();
            }
            unbindService(mMusicPlayerServiceConnection);
            mMusicPlayerService = null;
        }
    }

    private void onBindService() {
        Intent intent = new Intent(this, MusicPlayerService.class);
        bindService(intent, mMusicPlayerServiceConnection, Context.BIND_AUTO_CREATE);
    }
}

바인드하면 ServiceConnection으로 구현한 콜백이 호출됩니다.

서비스에 연결하면 onServiceConnected가 콜백됩니다.

두 번째 인수로 전달되는 IBinder (바인더 인터페이스)로 부터 서비스의 인터페이스를 가져옵니다.

서비스가 정지하거나 unbindService()를 호출해 언바인드하면 서비스에서 벗어납니다.

서비스가 비정상적으로 종료해서 서비스에서 벗어난 경우에는 onServiceDisconnected()가 호출됩니다.

MusicPlayerService.java

public class MusicPlayerService extends Service {
    private static final String TAG = MusicPlayerService.class.getSimpleName();

    private final IBinder mBinder = new MusicPlayerBinder();
    private MediaPlayer mPlayer;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return mBinder;
    }

    public boolean isPlaying() {
        return (mPlayer != null && mPlayer.isPlaying());
    }
    
    public void play() {
        Log.d(TAG, "play");

        mPlayer = MediaPlayer.create(this, R.raw.bensound_clearday);
        mPlayer.setLooping(true);
        mPlayer.setVolume(100, 100);
        mPlayer.start();
    }

    public void stop() {
        Log.d(TAG, "stop");
        if (mPlayer != null && mPlayer.isPlaying()) {
            mPlayer.stop();
            mPlayer.release();
            mPlayer = null;
        }
    }

    public class MusicPlayerBinder extends Binder {
        MusicPlayerService getService() {
            return MusicPlayerService.this;
        }
    }
}

상주 서비스는 설정 화면에서 확인할 수 있습니다. 음악 재생중에 [설정] -> [앱] 또는 [설정] -> [개발자 옵션] -> [실행중인 서비스]

3.3.3 IntentService를 활용하자

액티비티와 프래그먼트 수명주기에 의존하기 않고 백그라운드에서 처리하고 싶은 경우 IntentService를 활용합니다.

Android Studio 메뉴에서 생성하면 AndroidManifest.xml 등록은 자동으로 생성됩니다.

[File] -> [New] -> [Service] -> [Service (IntentService)]

<application>
        ...
        <service
            android:name=".FibService"
            android:exported="false"></service>
    </application>

IntentService.onHandleIntent(Intent)는 워커 스레드(메인 스레드가 아닌)에서 실행되므로 이 안에서 작업을 처리합니다.

@Override
protected void onHandleIntent(Intent intent) {
    Log.d(TAG, "onHandleIntent");
    if (intent != null) {
        final String action = intent.getAction();
        if (ACTION_CALC.equals(action)) {
            long start = System.nanoTime();
            int result = fib(N); // 피보나치 수열 계산
            long end = System.nanoTime();
            Intent resultIntent = new Intent(ACTION_CALC_DONE);
            // 결과를 Intent에 부여
            resultIntent.putExtra(KEY_CALC_RESULT, result);
            resultIntent.putExtra(KEY_CALC_MILLISECONDS, (end - start) / 1000 / 1000);
            LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(resultIntent);
        }
    }
}

Notice: 이 글은 [안드로이드 개발 레벨업 교과서]를 스터디하면서 정리한 내용입니다.


Comments