2010년 8월 3일 화요일

안드로이드 서비스를 부팅시 시작시키기 (Start Service at Boot Time)

일부 서비스는 안드로이드가 부팅될 때 자동으로 실행될 필요가 있다.



안드로이드 폰은 부팅이 끝나면 액션이 'android.intent.action.BOOT_COMPLETED'인 인텐트를 브로드캐스트 한다. 그러므로 이 인텐트 브로트캐스트를 받을 수 있는 BroadcastReceiver가 필요하다.

public class GPSLoggerServiceManager extends BroadcastReceiver {
  @Override
  public void onReceive(Context ctx, Intent intent) {
   if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
     ComponentName cName = new ComponentName(ctx.getPackageName(), GPSLogger);
     ComponentName svcName = ctx.startService(new Intent().setComponent(cName);
     if (svc == null) {
       Log.e(TAG, "Could not start service " + cName.toString());
     }
   } else {
     Log.e(TAG, "Received unexpected intent " + intent.toString());
   }
  }
}

여기서 가장 핵심은 onReceive() 메소드이다. 원하는 인텐트가 브로드캐스트 되면 onReceive() 메소드가 호출된다.

그리고 리시버는 manifest 파일에 선언되어 있어야 한다.

<receiver android:name=".LocationLoggerServiceManager"
   android:enabled="true"
   android:exported="false"
   android:label="LocationLoggerServiceManager" >
  <intent-filter>
   <action android:name="android.intent.action.BOOT_COMPLETED" />
  </intent-filter>
</receiver>

또한 이 클래스는 보안 설정에 선언할 필요가 있는 특정 이벤트 브로드캐스트를 들어야 하기 때문에 manifest 파일에 RECEIVE_BOOT_COMPLETED 퍼미션이 있어야 한다.

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

위와 같이 추가해주면 부팅이 끝나고 서비스가 자동으로 실행되게 된다.

-------
* 리플을 보고 좀 더 이해하기 쉽게 전체적인 코드의 프레임웍을 첨부한다.

/src/.../BootSvc.java

package app.arsviator;
...
public class BootSvc extends Service {
  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }
   
  @Override
  public void onCreate() {
    super.onCreate();
       
    Log.i("BOOTSVC", "Service started at the BOOT_COMPLETED.");
  }
}



/src/.../BRcvr.java

package app.arsviator;
...
public class BRcvr extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {   
    if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
      Log.i("BOOTSVC", "Intent received");   

      ComponentName cn = new ComponentName(context.getPackageName(), BootSvc.class.getName());
      ComponentName svcName = context.startService(new Intent().setComponent(cn));
      if (svcName == null)
        Log.e("BOOTSVC", "Could not start service " + cn.toString());
    }
  }
}


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="app.nautes"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
      <service android:name="BootSvc"></service>
      <receiver android:name=".BRcvr"
         android:enabled="true"
         android:exported="false"
         android:label="Broadcast Receiver" >
        <intent-filter>
          <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
      </receiver>
    </application>
</manifest>

이 프로그램을 에뮬레이터에 설치한 다음 에뮬레이터를 종료했다가 AVD Manager에서 다시 시작시키고 부팅이 끝난 다음 logcat을 보면 다음과 같다.

09-07 09:31:30.631: INFO/SurfaceFlinger(52): Boot is finished (22889 ms)
09-07 09:31:30.661: INFO/ARMAssembler(52): generated scanline__00000177:03515104_00000A01_00000000 [ 55 ipp] (79 ins) at [0x481508:0x481644] in 5894000 ns
09-07 09:31:30.750: INFO/ActivityManager(52): Start proc com.android.email for broadcast com.android.email/com.android.exchange.BootReceiver: pid=165 uid=10008 gids={3003, 1015}
09-07 09:31:30.860: DEBUG/HomeLoaders(105):   ----> items cloned, ready to refresh UI
09-07 09:31:31.230: DEBUG/ddm-heap(165): Got feature list request
09-07 09:31:32.790: DEBUG/dalvikvm(52): GC freed 14273 objects / 744904 bytes in 444ms
09-07 09:31:33.130: INFO/ActivityThread(165): Publishing provider com.android.email.provider: com.android.email.provider.EmailProvider
09-07 09:31:33.191: INFO/ActivityThread(165): Publishing provider com.android.email.attachmentprovider: com.android.email.provider.AttachmentProvider
09-07 09:31:33.300: DEBUG/Exchange(165): BootReceiver onReceive
09-07 09:31:33.360: DEBUG/EAS SyncManager(165): !!! EAS SyncManager, onCreate
09-07 09:31:33.430: INFO/ActivityManager(52): Start proc com.android.mms for broadcast com.android.mms/.transaction.MmsSystemEventReceiver: pid=176 uid=10013 gids={3003, 1015}
09-07 09:31:33.730: DEBUG/ddm-heap(176): Got feature list request
09-07 09:31:33.810: DEBUG/EAS SyncManager(165): !!! EAS SyncManager, onStartCommand
09-07 09:31:33.870: DEBUG/EAS SyncManager(165): !!! EAS SyncManager, stopping self
09-07 09:31:33.930: DEBUG/Eas Debug(165): Logging:
09-07 09:31:34.031: DEBUG/EAS SyncManager(165): !!! EAS SyncManager, onDestroy
09-07 09:31:34.410: DEBUG/MediaScannerService(149): start scanning volume internal
09-07 09:31:35.750: INFO/ActivityManager(52): Start proc app.nautes for broadcast app.nautes/.BRcvr: pid=195 uid=10078 gids={1015}
09-07 09:31:36.280: DEBUG/ddm-heap(195): Got feature list request
09-07 09:31:36.290: DEBUG/dalvikvm(29): GC freed 280 objects / 10704 bytes in 526ms
09-07 09:31:36.590: DEBUG/dalvikvm(29): GC freed 50 objects / 2224 bytes in 224ms
09-07 09:31:36.650: INFO/BOOTSVC(195): Intent received
09-07 09:31:36.740: INFO/BOOTSVC(195): Service started at the BOOT_COMPLETED.
09-07 09:31:36.770: DEBUG/dalvikvm(29): GC freed 2 objects / 48 bytes in 174ms
09-07 09:31:37.931: DEBUG/dalvikvm(105): GC freed 4158 objects / 281904 bytes in 199ms
09-07 09:31:38.360: DEBUG/MediaScanner(149): opendir /system/media/ failed, errno: 2
09-07 09:31:38.360: DEBUG/MediaScanner(149):  prescan time: 1474ms
09-07 09:31:38.360: DEBUG/MediaScanner(149):     scan time: 55ms
09-07 09:31:38.360: DEBUG/MediaScanner(149): postscan time: 1ms
09-07 09:31:38.360: DEBUG/MediaScanner(149):    total time: 1530ms
09-07 09:31:38.390: DEBUG/MediaScannerService(149): done scanning volume internal
09-07 09:31:39.180: DEBUG/dalvikvm(105): GC freed 3308 objects / 168792 bytes in 169ms


로그를 보면 부팅이 끝난 다음 서비스가 시작된걸 확인할 수 있다.


댓글 18개:

  1. trackback from: 안드롬니아 (Andromia build)
    Andromnia builds First you will need haret. Put haret anywhere on your phone. Then download image.zip, unzip it an put it in the same folder. Then create a sd card formated with ext2 or ext3 (best) and unzip the rootfs.zip file onto the sd card. Here yo..

    답글삭제
  2. trackback from: [안드로이드] 코코챗 ( 랜덤채팅 )
    안드로이드 마켓에 코코챗 이라고 랜덤 1:1 채팅하는 프로그램이 떳네요.. 굉장히 단순하고, 아기자기한 디자인인데.. 모르는 사람과 1:1로 채팅할 수 있습니다. 심심할 때, 알바하거나, 누군가 기다릴 때, 기차나, 버스안에서 DMB는 안나오고 심심할 때 가끔 랜덤채팅 즐기는 것도 괜찬은거 같네요 ㅎ 무엇 보다도 무료 어플인게 맘에 듭니다 ㅎ 아래는 QR 코드에요! 모두들 즐 챗 ㅎㅎ

    답글삭제
  3. registerReceiver()는 안해줘도 되는건가요?

    답글삭제
  4. @Robin - 2010/09/06 08:04
    브로드캐스트 리시버를 등록하는 방법이 두가지가 있는데 여기서는 인텐트 필터를 사용하기 때문에 registerReceiver()는 필요없습니다.

    답글삭제
  5. 그렇군요.



    ComponentName cName = new ComponentName(ctx.getPackageName(), GPSLogger);



    여기에서 GPSLogger는 정의한 문자열인가요?

    답글삭제
  6. @Robin - 2010/09/06 13:01
    실제로 시작시킬 서비스의 클래스 이름이죠.

    답글삭제
  7. 제가 왜 질문을 했냐면요. 제가 만든 서비스 클래스명이 AppStarter인데... 그냥 저렇게 써주면 에러가 나더라구요.



    AppStarter cannot be resolved



    그래서 "AppStarter"로 해봤는데... 서비스를 찾지 못했다고 나오더군요.



    package kr.skku.smartescort;



    import android.content.BroadcastReceiver;

    import android.content.ComponentName;

    import android.content.Context;

    import android.content.Intent;

    import android.util.Log;



    public class SEReceiver extends BroadcastReceiver {

    String TAG = "SEReceiver";



    @Override

    public void onReceive(Context context, Intent intent) {

    // TODO Auto-generated method stub



    if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {

    ComponentName comp_name = new ComponentName(context.getPackageName(), AppStarter);

    ComponentName service_name = context.startService(new Intent().setComponent(comp_name));



    if (service_name == null)

    Log.e(TAG, "Could not start service " + comp_name.toString());

    }

    }



    }



    AppStarter는 매니페스트 파일에서 등록되어 있구요. GPSLogger 부분이 String 형인데... AppStarter 클래스 명을 어떻게 적어줘야 하나 헷갈리네요.

    답글삭제
  8. @Robin - 2010/09/06 14:32
    저 경우라면 "kr.skku.smartescort.AppStarter"로 해주면 됩니다.

    아니면 그냥 AppStarter.class.getName()으로 해 주는게 더 깔끔하겠죠.

    답글삭제
  9. 안녕하세요.

    포스트하신 내용을 보고 개발중인 앱에 적용을 해봤습니다.

    하지만 앱이 실행될때

    '프로그램이 예상치 않게 중지되었습니다.'

    라는 메세지가 나오며 실행 되지 않습니다.

    위 내용을 적용하기 전에는 잘 되었던 건데요.



    소스 첨부하니 확인좀 부탁드리겠습니다.





    Exec.java



    public class Exec extends BroadcastReceiver {

    @Override

    public void onReceive(Context context, Intent intent) {

    if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {

    Log.i("BOOTSVC", "Intent received");



    ComponentName cn = new ComponentName(context.getPackageName(), IdbellMain.class.getName());

    ComponentName svcName = context.startService(new Intent().setComponent(cn));



    if (svcName == null) {

    Log.e("BOOTSVC", "Could not start service " + cn.toString());

    }

    }

    }

    }







    mainfest.xml



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

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



    <application android:icon="@drawable/icon" android:label="@string/app_name">

    <activity android:name=".Exec"

    android:label="@string/app_name">

    <intent-filter>

    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />

    </intent-filter>

    </activity>



    <receiver android:name=".Idbell"

    android:enabled="true"

    android:exported="false"

    android:label="Broadcast Receiver" >

    <intent-filter>

    <action android:name="android.intent.action.BOOT_COMPLETED" />

    </intent-filter>

    </receiver>



    <activity android:name=".ExecMain" />



    </application>



    감사함니다

    답글삭제
  10. 안녕하세요. 좋은 자료 감사합니다. reboot해보니 작성한 service가 잘 작동하는 것을 확인했습니다. 다시한번 감사드립니다.

    답글삭제
  11. 시작은 잘되는데 설정후 다시 자동 시작을 멈추게 하는건 어떻게 해야 하나요.

    답글삭제
  12. GPSLoggerServiceManager.java 에서
    GPSLogger, svc, TAG 이런 곳에 에러가 나는데 뭐가 잘못 된건지 도대체 모르겠습니다. 답변 부탁드릴게요ㅜㅜ

    답글삭제
  13. 안녕하세요^^

    현재 안드로이드 프로그래밍을 처음 공부하는 학생입니다.

    갑자기 안드로이드폰(혹은 테블릿) 부팅후 자동으로 동영상이 재생되도록 해야하는 일이 생겼는데... java 는 조금 공부를 해왔지만 안드로이드 프로그래밍에대해서는 처음이어서요 ㅠㅠ

    저 코딩을 어떻게 사용해야하는지 잘 모르겠네요(sdk 는 컴퓨터에 설치했습니다.)

    친절히 설명해주셨는데 죄송하지만 저코딩을 어느툴을 키고 사용해야하는건가요?

    그리고 핸드폰엔 어떻게 익스포트(?) 시키는지도 궁금합니다.

    답글삭제
  14. 저같은 경우는
    메니페스트 상에서 한줄 차이로 동작 여부가 결정 됩니다
    영역 내에

    를 써주느냐 안써주느냐에 따라 동작되고 안되고 하는데요
    이 원인이 참 궁금합니다~~
    오늘 이것 땜에 맨땅을 너무 많이 파서요 ㅠㅠ

    답글삭제
  15. 감사합니다

    자료 정말 잘 봤습니다.

    그런데

    저는 메니페스트에서 한줄 차이로 동작 여부가 결정됩니다.

    intent-filter 영역에

    한줄을 추가해 주느냐 않느냐에 따라 동작 되고 안되고가 결정되는데

    이 이유 좀 알려주시면 감사하겠습니다.ㅠㅠ

    오늘 하루 종일 이것땜에 맨땅을 너무 많이 파서 ㅠㅠ

    부탁드릴게요~
    좋은 하루 되세요~

    답글삭제
    답글
    1. 게시글을 잘읽어보시면 intent-filter에 써줘야하는 이유가 적혀있습니다.

      삭제
    2. intent-filter 가 없을 경우 자동으로 export = false 가 됩니다.
      있으면 export true 가 되지요.

      삭제
  16. 재부팅 후 다시 어플을 시작할 때 서비스가 시작이 안되서 지금 이 코드로 좀 고쳐보려고 하는데요 다시 서비스가 연동되야 하는 액티비티 안에 filter를 쓰면 되는 건가요??

    답글삭제