Android

[android] 백그라운드 서비스를 유지시킬 수 있는 방법이 있는가???도즈(Doze)모드에 진입되면 백그라운드 서비스는 동작을 멈추게 된다. 배터리 최적화에서 벗어나기

안드로이드 운영체제가 버전업 할 때 마다 백그라운드 서비스에 대한 제약은 극히 심해졌다. 백그라운드 서비스가 항상 살아 있어야 앱이 제대로 동작을 하는데, 휴대폰 제조사 마다 백그라운드 서비스 유지 시키는 방법이 다 달라서 제대로 동작하지 않게되었다. 그나마 LG전자 휴대폰의 경우 절전 대상 아닌앱(화이트 리스트)에 앱을 추가하면 백그라운드서비스는 잘 유지 되었다. 안드로이드 10으로 업그레이드되는 폰들이 요즘 부쩍 늘어나면서, 앱이 동작하지 않는 다는 리뷰와 이메일이 많이 온다. 특히 삼성전자 폰, 화훼이폰, 오포폰, 샤오미폰…. 그외 다수의 안드로이드 10이 설치된 휴대폰에서 말이다. 나는 해결점을 찾아야한다. 앱의 백그라운드 서비스가 실행되고 있음을 사용자에게 알리기 위해 포그라운드 서비스를 사용하고 있다. 그럼에도 불구하고 서비스가 동작을 멈추는 휴대폰들이 있다는 것은 문제가 있어보인다. 휴대폰 제조사별로 파편화된 안드로이드 운영체제가 원망 스럽다.

 

안드로이드 운영체제가 버전 업 될 때 마다, 나는 백그라운드 서비스 유지에 혈안이되어 있었다. 지금도 마찬가지이다.

그래서 수시로 백그라운드 서비스 관련 글들을 찾아 읽고 있다. 매니패스트 파일에 배터리 옵티마이저 무시 권한을 요청하면 유지가 될까? 이런 권한이 있다는 것을 오늘 처음 알게되었다. 이 권한을 사용했음에도 유지시키는 방법을 물어보는 글들이 올라오는 것을 보면 , 이 것 또한 방법이 아닐 수 있지만, 시도해 볼만한 가치는 있다

<uses-permission android:name=”android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS” />

 

아래 코드 역시 시도해봐야겠다.

//disabling battery optimizations

Intent intent = new Intent(this,SeriesService.class);
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
    if (pm.isIgnoringBatteryOptimizations(packageName))
            intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
    else {      
            intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
            intent.setData(Uri.parse("package:" + packageName));
    }
    
startService(intent);

I tried this on my OnePlus 5 (running Android pie) and it works

와우…..

intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);

이 권한을 사용할 경우, 구글플레이 정책 위반에 해당한다고 안드로이드 스튜디오의 가이드가 알려준다. 이런 가이드 너무 좋다. 이 권한을 사용하려면 구글플레이에 요청을 해야한다. 허용 목록에 추가할 수 있는 사용 사례를 참고 하면 되는데… 구글에서 과연 허용해줄지는 알 수 없다. 구글플레이에서 삭제되는 상황이 발생할 수 있기에 사용할 수 없다. 

 

카카오톡 앱은 왜 이미 배터리 최적화에서 제외 되어있는가? 사용자에게 설정 변경을 요구하지도 않았다. 앱을 설치와 동시에 자동으로 제외되어 있다. 휴대폰 제조와의 협약일까? 아니면 구글과의 협의일까? 이유는 나도 잘 모르겠다. 

 

허용 목록에 추가할 수 있는 사용 사례

기타 사용 사례 지원

 

거의 모든 앱은 네트워크 연결, 알람, 작업 및 동기화를 제대로 관리하고 FCM 높은 우선순위 메시지를 사용하여 잠자기 모드를 지원할 수 있습니다. 하지만 일부 사용 사례에서는 이것만으로는 부족합니다. 부족한 경우 시스템은 잠자기 및 앱 대기 모드 최적화에서부분적으로 제외할 앱에 관한 허용 목록을 구성할 수 있도록 지원합니다.

허용 목록에 추가된 앱은 잠자기 및 앱 대기 모드 중 네트워크를 사용하고부분 wake lock을 보유할 수 있습니다. 하지만 허용 목록에 추가된 앱에도 다른 앱과 마찬가지로다른 제한사항은 여전히 적용됩니다. 예를 들어 API 레벨 23 이하에서는 허용 목록에 추가된 앱의 작업 및 동기화가 지연되며 일반AlarmManager경보가 실행되지 않습니다.isIgnoringBatteryOptimizations()를 호출하면 앱이 현재 제외 허용 목록에 포함되어 있는지 여부를 확인할 수 있습니다.

사용자는설정 > 배터리 > 배터리 최적화에서 허용 목록을 수동으로 구성할 수 있습니다. 앱에서 사용자에게 허용 목록 추가를 요청하는 방법을 시스템이 제공할 수도 있습니다.

허용 목록에 앱을 추가하도록 사용자에게 요청하기 전에 앱이 허용 목록에추가할 수 있는 사용 사례에 해당하는지 확인하세요.

참고:Google Play 정책에서는 앱의 핵심 기능에 악영향을 미치는 경우가 아니면 앱이 Android 6.0 이상의 전원 관리 기능(잠자기 및 앱 대기 모드)에서 직접 제외 요청을 하는 것을 금지하고 있습니다.

 

 

알람매니저(Alarm Manager)를 사용해보면 어떨까요?

화이트 리스트에 등록할 수 없는 대상의 앱이라면, 휴대폰이 도즈 모드 또는 대기모드에 들어가더라도 알람매니저는 동작을 합니다. 알람 매니저를 사용하여 원하는 시간에 앱을 깨우고 원하는 기능을 수행하도록 하는 방법이 있습니다.

 

메소드() 내용
setAndAllowWhileIdle() 
setExactAndAllowWhileIdle()
1. 도즈 상태는 유지됨.
2. 해당 앱만 도즈에서 잠깐 깨어남
3. 다른 앱들은 도즈 상태로 유지됨
4. 9분에 한 번씩만 사용 가능.
5. setExactAndAllowWhileIdle()메소드가 좀 더 시간적으로 정확함.
setAlarmClock() 1. 도즈 상태에서 모든 앱이 깨어남
2. 알람이 울리기 바로 전에 도즈에서 벗어남

 

리시버 클래스 생성

package smart.app;

import android.app.Activity;
import android.app.ActivityManager;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;

import android.content.Intent;

import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;

import androidx.core.app.NotificationCompat;
import android.util.Log; 
 

public class ServiceAlarmReceiver extends BroadcastReceiver
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        try
        {
            boolean isRunning = isServiceRunningCheck(context);
            

            if (!isRunning) { 
                 //Log.d(TAG, "############## BatteryManageService : 서비스 다시 시작");
 

                Intent in = new Intent(context, ManageStatusService.class);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    context.startForegroundService(in);
                } else {
                    context.startService(in);
                }

            }

            //alarmCancelAndReSetting(context);  //무한반복으로 Doze모드 및 백그라운서비스 제한 탈출
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

    }
 

    public boolean isServiceRunningCheck(Context context) {
        try {
            ActivityManager manager = (ActivityManager) context.getSystemService(Activity.ACTIVITY_SERVICE);
            for (ActivityManager.RunningServiceInfo rsi : manager.getRunningServices(Integer.MAX_VALUE)) {
                if ("smart.app.ManageStatusService".equals(rsi.service.getClassName())) {
                     return true;
                }

            }
            return false;
        }catch (Exception e){
            return false;
        }
    }
 


}

 

매니페스트 파일에 추가

<receiver android:name=".ServiceAlarmReceiver" />

3초 뒤 바로 알람매니저 실행

    private void  alarmManagerSetting(Context context, String CallLocation){
        //Log.e(TAG, "=================================== BootStartReceiver alarmManager Start");
        //Log.e(TAG, "=================================== CallLocation : " + CallLocation);
        final Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());

        calendar.add(Calendar.SECOND, 3);
        Intent intent = new Intent(context, ServiceAlarmReceiver.class);
        PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_CODE_SERVICE_ALARM_MANAGER, intent, 0);
        //long currentTime = System.currentTimeMillis();
        //int interval = 15000;
        //int interval = 3000;//3초
        //Log.e("TAG", "=================================== calendar.getTimeInMillis() : " + calendar.getTimeInMillis());

        AlarmManager alarmManager = (AlarmManager)  context.getSystemService(Context.ALARM_SERVICE);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) //api level 23 (Marshmallow 6.0)
            alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);
        else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
            alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);
        else
            alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);

    }

 

알람매니저 해제.

    private void  alarmManagerClear(Context context, String CallLocation){

        Intent intent = new Intent(context, ServiceAlarmReceiver.class);
        PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_CODE_SERVICE_ALARM_MANAGER, intent, 0);  
        AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
        am.cancel(sender);
    }

 

setAlarmClock()를 사용하는 방법

    private void  alarmManagerSettingsetAlarmClock(Context context){
        final Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());

        calendar.add(Calendar.SECOND, 3);
        Intent intent = new Intent(context, ServiceAlarmReceiver.class);
        PendingIntent sender = PendingIntent.getBroadcast(context, REQUEST_CODE_SERVICE_ALARM_MANAGER, intent, 0);
        AlarmManager alarmManager = (AlarmManager)  context.getSystemService(Context.ALARM_SERVICE);
        AlarmManager.AlarmClockInfo ac = new AlarmManager.AlarmClockInfo(calendar.getTimeInMillis(), sender);
        if(alarmManager != null)
            alarmManager.setAlarmClock(ac, sender);
    }

 

 

[참고]

https://github.com/alexstyl/Memento-Calendar/issues/151

https://stackoverflow.com/questions/33114063/how-do-i-properly-fire-action-request-ignore-battery-optimizations-intent/33114136

https://stackoverflow.com/questions/52617790/action-request-ignore-battery-optimizations-does-nothing

https://www.thetopsites.net/article/50194974.shtml

https://stackoverflow.com/questions/54457354/how-to-avoid-killing-background-service-even-app-is-killed-in-marshmallow

 

[관련 글]

개발자 문서:백그라운드 서비스 만들기

개발자 문서 : 서비스

https://www.codota.com/code/java/methods/android.os.PowerManager/isIgnoringBatteryOptimizations

https://stackoverflow.com/questions/57922145/how-to-make-continue-running-background-services-in-android-pie

https://www.androidpolice.com/2020/05/30/how-to-prevent-apps-sleeping-in-the-background-on-android/

https://www.freakyjolly.com/android-background-geolocation-service-without-any-kill/#.XviwfihKjDc

https://www.thetopsites.net/article/52317301.shtml

https://xiaomininja.com/2015/08/10/miui-tip-enable-autostart-to-never-miss-a-notification-again/

https://www.thetopsites.net/article/52425222.shtml

https://codechacha.com/ko/android-broadcast-receiver/

https://www.thetopsites.net/article/50194974.shtml

https://www.thetopsites.net/article/52623313.shtml

 

Leave a Reply

error: Content is protected !!