-
안드로이드 서비스(2)안드로이드 2020. 5. 21. 17:22728x90
2020/05/21 - [Android & Kotlin] - 안드로이드 서비스
이전 글에 이어서 씁니다.
스타티드 서비스를 만들어서 작업스레드를 돌리는것까지 해보았는데요
안타깝게도 서비스도 stopService()가 아닌 시스템 메모리 부족으로 인해 강제 종료될 수 있습니다.
하지만 안드로이드도 마구잡이로 앱을 죽이는것은 아니니 안심하시길 바랍니다!
안드로이드에서 메모리가 부족할 때 다른 앱 프로세스를 죽이는 녀석을 LMK(Low Memory Killer)라고 합니다.
LMK 는 정해진 우선순위에 따라 우선순위가 낮은 프로세스들을 위주로 죽입니다.
LMK 가 먼저 죽이는 순서대로 설명 드릴게요
죽은 앱
죽은 앱을 다시 죽인다는게 무슨 말일까요? 프로세스를 새로 생성하는건 되게 높은 비용을 요구하기에 안드로이드는 앱이 죽어도 바로 없애지 않고 재사용 할 수 있게 저장해둬요. 그러다가 메모리가 모자르면 LMK에 의해 가장 먼저 삭제된답니다.
뒤에 안보이는 앱
홈 키 옆에 프로세스 목록 보기 버튼이 있을거에요 이걸 누르면 이 전에 썼던 앱들 목록이 쭉 뜨잖아요
이렇게 직접 화면에 보이지 않고 뒤에서 대기하는 앱들이 2순위로 삭제됩니다.
서비스 앱
3순위로 서비스 동작 앱이 삭제됩니다.
요즘 휴대폰들은 성능이 굉장히 좋아서 서비스까지 삭제되는 일이 거의 없어요.
그래서 백그라운드 작업을 서비스로 하는 것입니다.
하지만 그래도 삭제되는 일이 있긴하니까 대비는 해야해요. 이거는 조금 있다가 알아볼게요
중요한 것은 서비스 컴포넌트가 죽는게 아니라 서비스를 사용하는 앱 프로세스 자체가 죽는거에요
지각되는 앱 (Perceptible app)
음악 재생이나 위젯으로 보이는 앱, 상단 스크롤을 내리면 현재 동작중입니다~ 표시되는 앱들이 있잖아요
날씨 앱 같은것들이나 푸시알림 온 앱들이 여기에 속합니다.
지각되는 앱들부터는 정말 거의 삭제될 일이 없다고 보시면 됩니다.
뒤에 보이는 앱
뒤에 보이는 앱은 요즘 코로나 때문에 난리인데 앱을 쓰다가 긴급 알림이 오면 우리 앱이 잠깐 뒤로 밀려나잖아요
하지만 화면에서 완전히 사라지는게 아니라 일부만 가려지는 경우. 이런 경우가 뒤에 보이는 앱이에요
Foreground app
화면 전면에 보이는, 현재 사용 중인 앱이 삭제되구요
시스템 앱
메시지, 전화 같이 휴대폰 본연의 역할을 하는 앱들이 여기에 속합니다.
시스템
안드로이드 OS 자체가 죽게됩니다.
서비스 생존 방법
서비스를 쓰는 앱들이 굉장히 많은데 이런 앱들 사이에서도 어떤 서비스가 먼저 죽을지도 결정이 돼요
서비스가 시작된지 30분이 안된 서비스는 이 서비스들 중에서도 가장 마지막에 삭제됩니다.
onStartCommand() 리턴값
public class CountService extends Service { public CountService() { } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } }
상수명 설명 START_STICKY LMK가 서비스앱 프로세스를 강제 종료한 이후 가용 메모리가 확보되면 다시 서비스를 실행시켜줍니다. 서비스의 생존 유지가 중요하면 이 값을 반환해야 합니다. START_NOT_STICKY 가용 메모리가 확보되어도 서비스를 다시 실행시켜주지 않습니다. START_REDELIVER_INTENT START_STICKY와 똑같지만 살아 있을 때 Intent 를 다시 살려내줍니다. 다시 살아났을 때는 onStartCommand() 메소드의 flags의 값이 Service.START_FLAG_REDELIVERY 로 넘어옵니다.
Foreground Service
Foreground service 를 사용하면 지각되는 앱으로 분류되기 때문에 서비스가 종료될 일은 거의 없다고 보시면 됩니다.
Manifest
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
추가
< MainActivity.java >
package kr.co.sample; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.Button; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { Button startBtn, endBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startBtn = findViewById(R.id.start_btn); endBtn = findViewById(R.id.end_btn); startBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, CountService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(intent); } else { startService(intent); } } }); endBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, CountService.class); stopService(intent); } }); } }
< CountService.java >
package kr.co.sample; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.IBinder; import android.util.Log; import androidx.core.app.NotificationCompat; public class CountService extends Service { public CountService() { } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); Log.d("CountService", "onCreate"); Intent intent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder builder; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { String channelId = "CountChannel"; String channelName = "CountChannel"; NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT); NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.createNotificationChannel(channel); builder = new NotificationCompat.Builder(this, channelId); } else { builder = new NotificationCompat.Builder(this); } builder.setContentTitle("Count Service") .setContentText("카운트 서비스 실행 중") .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(pendingIntent); Notification notification = builder.build(); startForeground(634562, notification); } @Override public void onDestroy() { super.onDestroy(); Log.d("CountService", "onDestroy"); } }
결과
노티피케이션이 떠있는게 보이시나요? 이 상태부터는 지각 가능한 앱으로 분류되기 때문에 앱이 살아남기 쉽답니다.
'안드로이드' 카테고리의 다른 글
안드로이드 서비스 (3) (0) 2020.05.21 안드로이드 서비스 (0) 2020.05.21 안드로이드 HandlerThread (0) 2020.05.13 안드로이드 CountDownTimer (0) 2020.05.13 Android AsyncTask (0) 2020.05.10