Translate

2014년 2월 5일 수요일

[Android] Immortal Service (좀비 서비스 or 죽지않는 서비스) 구현하기




안드로이드 공부 겸 회사에서 App 실행횟수를 카운팅 하는 프로그램을 만들어 보라고 하셨는데 요구사항중에 서비스를 강제로 종료하더라도 LogCounting Service 가 자동으로 재실행 되어야 한다는게 있어서 찾아봤었다.


구글도 많이 검색하고 했는데 그나마 가장 나은 소스가 다음과 같았다.
네이버블로그(Immortal Service (좀비 서비스 or 죽지않는 서비스) 구현하기 링크)
포스팅 제목을 어떻게 쓸까 많이 고민했지만 링크에 나와있는 제목외에 딱히 좋은 이름이 생각나지 않아 링크의 제목을 그대로 사용했다. ㅠ
1004lucifer

결론은 Service Class의 onDestroy() 에서 BR(BroadcastReceiver)을 띄워서 BR에서 다시 Service 를 띄운다는 건데 막상 테스트를 해보니 Process를 강제로 Kill 시키는 경우 onDestroy() 함수가 실행되지 않는 문제가 있었다.


하지만 onStart() or onStartCommand() 에서 return START_STICKY 를 주었기 때문에 안드로이드 시스템에서 짧은 시간안에 다시 Service를 구동을 시켰다.

좀더 많이 테스트를 하다보니 해당 Process 를 강제로 Kill 시키는경우 시스템에서 해당 Service를 구동하는 간격(interval)이 점점 길어졌다.
나중에는 5분정도나 뒤에 구동을 시킨다고 하고..


Logcat (카카오톡의 경우.. 57초 후에 다시 서비스를 시작한다고 로그에 나왔다.)
I/ActivityManager(  960): Killing 3469:com.kakao.talk/u0a174 (adj 5): kill background
W/ActivityManager(  960): Scheduling restart of crashed service com.kakao.talk/.vox.VoxService in 57832ms







1004lucifer
결국 다음과 같은 방법을 사용했다.

ServiceMonitor.java

package com.example.appcounter;

import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;

import java.util.List;

/**
 * User: huhwook
 * Date: 2014. 2. 4.
 * Time: 오후 3:09
 */
public class ServiceMonitor {

    private static ServiceMonitor instance;
    private AlarmManager am;
    private Intent intent;
    private PendingIntent sender;
    private long interval = 5000;

    private ServiceMonitor() {}
    public static synchronized ServiceMonitor getInstance() {
        if (instance == null) {
            instance = new ServiceMonitor();
        }
        return instance;
    }

    public static class MonitorBR extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (isRunningService(context, AppCounterService.class) == false) {
                context.startService(new Intent(context, AppCounterService.class));
            }
        }
    }

    public void setInterval(long interval) {
        this.interval = interval;
    }

    public void startMonitoring(Context context) {
        am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        intent = new Intent(context, MonitorBR.class);
        sender = PendingIntent.getBroadcast(context, 0, intent, 0);
        am.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), interval, sender);
    }

    public void stopMonitoring(Context context) {
        am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        intent = new Intent(context, MonitorBR.class);
        sender = PendingIntent.getBroadcast(context, 0, intent, 0);
        am.cancel(sender);
        am = null;
        sender = null;
    }

    public boolean isMonitoring() {
        return (AppCounterService.mThread == null || AppCounterService.mThread.isAlive() == false) ? false : true;
    }

    private static boolean isRunningService(Context context, Class<?> cls) {
        boolean isRunning = false;

        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> info = activityManager.getRunningServices(Integer.MAX_VALUE);

        if (info != null) {
            for(ActivityManager.RunningServiceInfo serviceInfo : info) {
                ComponentName compName = serviceInfo.service;
                String className = compName.getClassName();

                if(className.equals(cls.getName())) {
                    isRunning = true;
                    break;
                }
            }
        }
        return isRunning;
    }
}




1004lucifer
AppCounter (activity)

package com.example.appcounter;

import android.app.Activity;
import android.os.Bundle;

public class AppCounter extends Activity {

    private ServiceMonitor serviceMonitor = ServiceMonitor.getInstance();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        if (serviceMonitor.isMonitoring() == false) {
            serviceMonitor.startMonitoring(getApplicationContext());
        }

    }
}


1004lucifer
AppCounterService (Service)

package com.example.appcounter;

import android.app.ActivityManager;
import android.app.Service;
import android.content.*;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;

import java.util.List;

/**
 * User: huhwook
 * Date: 2014. 1. 27.
 * Time: 오후 6:02
 */
public class AppCounterService extends Service {

    private final String LOG_NAME = AppCounterService.class.getSimpleName();

    public static Thread mThread;

    private ComponentName recentComponentName;
    private ActivityManager mActivityManager;

    private boolean serviceRunning = false;

    @Override
    public void onCreate() {
        super.onCreate();

        mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        serviceRunning = true;
    }

    @Override
    public void onDestroy() {
        serviceRunning = false;
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (mThread == null) {
            mThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (serviceRunning) {
                        List<ActivityManager.RecentTaskInfo> info = mActivityManager.getRecentTasks(1, Intent.FLAG_ACTIVITY_NEW_TASK);
                        if (info != null) {
                            ActivityManager.RecentTaskInfo recent = info.get(0);
                            Intent mIntent = recent.baseIntent;
                            ComponentName name = mIntent.getComponent();

                            if (name.equals(recentComponentName)) {
                                Log.d(LOG_NAME, "== pre App, recent App is same App");
                            } else {
                                recentComponentName = name;
                                Log.d(LOG_NAME, "== Application is catched: " + name);
                            }
                        }
                        SystemClock.sleep(2000);
                    }
                }
            });

            mThread.start();
        } else if (mThread.isAlive() == false) {
            mThread.start();
        }

        return START_STICKY;
    }

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

}



AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.appcounter"
          android:versionCode="1"
          android:versionName="1.0">

    <uses-sdk android:minSdkVersion="18"/>
    <uses-permission android:name="android.permission.GET_TASKS" />
    <application
        android:label="@string/app_name"
        android:icon="@drawable/ic_launcher">
        <activity
            android:name="AppCounter"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service android:name=".AppCounterService" />
        <receiver android:name=".ServiceMonitor$MonitorBR" />

    </application>
</manifest>




PS.
AppCounterService.java 의 경우 문제되는 부분이 있어서 나중에 수정을 했지만
해당 Service 를 다시 띄우는 다른 소스의 경우 포스팅 의도에 벗어나지 않아서
Service 소스를 다시 반영하지는 않았다.






보충설명
- Background Process 를 Kill 했을 시 안드로이드 시스템에서 자동으로 서비스를 구동을 시켜주었다.
1004lucifer
- Background Process 를 Kill 한 후 환경설정-애플리케이션-실행중인앱 에서 해당 서비스를 중지할 경우 서비스가 다시 올라오지 않는 문제가 있었다. (AlarmManager 는 계속 구동이 되어 Service 를 다시 띄울 수는 있었다.)

== 추가 ==
- LG G2 기종을 사용중인데 Android ver 4.4.1 에서는 설정-실행중인앱-서비스를 중지할 경우에 onDestroy() 메소드는 실행되지 않았었는데 어제(2014.02.09) Android 4.4.2로 업데이트를 하니 서비스 중지 시 무조건 onDestroy() 메소드가 실행이 되었었다.
따라서 onDestroy() 메소드에 다시 띄우는 코드를 넣을 시 서비스가 올라오게 되었다.
단지 언제 올라올지 모를뿐..;; 10분!? 20분 후에 올라올 수도 있다.
========

1004lucifer
- 환경설정-애플리케이션-다운로드된앱 에서 해당 App 을 강제종료를 한경우 AlarmManager 자체도 종료가 되어 다시 서비스를 구동할 수는 없었다.




결론
어느정도는 서비스가 다시 띄울 수 있게 제작이 가능은 하지만 아직까지 단일어플로 완벽하게 서비스를 종료하는 어플을 만드는게 힘들지 않을까 싶다.
어플을 두개 만들어서 각각 서로의 어플이 정상적으로 띄워져 있는지 확인 후 죽어있다면 다시 살려주는 기능을 만드는게 가장 정확하지 않을까 싶다.



== 추가 ==
해당 기술을 이용해 어플을 구동시 Log를 남기는 어플을 만들어 보았다.
[Android] 구동한 앱을 카운트하는 프로그램 (AppCounter)
========



개발관련 지적 환영합니다.
잘못된 부분이 있다면 지적해 주세요. :)



댓글 없음 :

댓글 쓰기