2010년 8월 30일 월요일

안드로이드 WebView에서 커스텀 scheme 사용하기 (Use custom scheme in WebView)


안드로이드에서는 WebView에서 링크에 tel: 같은 태그(<a href="tel:01034567890">)가 먹지 않는다. 이런 식으로 WebView가 지원하지 않는 커스텀 scheme을 지원하려면 WebViewClient를 사용하면 된다.

    private class CustomScheme extends WebViewClient {
       public boolean shouldOverrideUrlLoading(WebView view, String url) {
          ...
       }
   }

이런식으로 WebViewClient 오브젝트를 정의하고 WebView에 setWebViewClient() 메소드를 사용해 등록해 주면 된다. 그러면 WebView에 있는 링크가 클릭될 때 마다 WebViewClient 오브젝트의 shouldOverrideUrlLoading() 메소드가 호출된다.

  WebView wv = (WebView)findViewById(R.id.myWebView);
  wv.setWebViewClient(new CustomScheme());

shouldOverrideUrlLoading() 메소드가 호출될 때 클릭된 링크의 url 스트링이 argument로 넘겨지기 때문에 url중에서 scheme 부분을 비교해서 원하는 동작을 하도록 해 주면 된다.

        if (url.startsWith("tel:")) {
         Intent i = new Intent(Intent.ACTION_CALL, Uri.parse(url));
          startActivity(i);
          return true;
        }
        return false;






2010년 8월 17일 화요일

안드로이드 센서 시뮬레이터 (Sensor Simulator in Android)

안드로이드에서 센서를 사용하는 어플리케이션을 개발할 때 에뮬레이터는 센서를 지원하지 않기 때문에 동작을 테스트 할 방법이 없다. 그래서 인터넷을 뒤져 본 결과 에뮬레이터에서 센서 입력을 시뮬레이션 해 줄수있는 툴을 발견했다. OpenIntents가 개발한 SensorSimulator로 별도 프로그램에서 에뮬레이터에서 실행되고 있는 어플리케이션에 센서 값을 보내줄 수 있다.


먼저 SensorSimulator 를 다운받는다. 다운받은 압축을 풀어보면 다음과 같은 파일이 들어있다.


이 중 bin 디렉토리를 보면 다음과 같은 파일이 들어있다.


이 파일중 SensorSimulatorSettings.apk 파일은 에뮬레이터에 인스톨 해 줘야 하고 sensorsimulator.jar는 pc에서 실행할 바이너리 파일이다.


먼저 커맨드 라인에서 adb를 사용해서 SensorSimulatorSettings.apk를 에뮬레이터에 설치해준다.


설치가 끝났으면 PC에서 sensorsimulator.jar를 실행시킨다. 프로그램이 실행되면 위와 같은 화면이 나온다.


에뮬레이터를 실행해보면 SensorSimulatorSettings가 설치된 걸 볼 수 있다. 에뮬레이터에 설치된 앱을 실행한다.


sensorsimulator 화면을 보고 적절한 IP주소와 포트번호를 에뮬레이터의 앱에 넣어준다.


에뮬레이터에서 Testing 탭을 선택한 다음 Connect 버튼을 누른다.


IP 주소와 포트번호가 제대로 설정되었다면 sensorsimulator에 연결되었다는 메시지가 나오고 에뮬레이터에도 위와 같은 화면이 나온다.


초기 연결되었을 때는 모든 센서가 disable상태이기 때문에 에뮬레이터에서 테스트하려는 센서를 선택해서 활성화 시켜주면 된다. 그리고 sensorsimulator의 휴대폰 그림을 클릭하고 움직여보면 그에 따른 센서값의 변화가 에뮬레이터로 전달되어 센서 값이 업데이트 되는걸 볼 수 있다.

-----------------------------------------------------------------------

자신의 앱을 센서 시뮬레이터를 사용해서 테스트 하는 법

1. SensorSimulatorSettings를 실행해서 위와 같이 필요한 설정을 해 준다.

2. 프로젝트 루트에 lib 디렉토리를 만든다.


3. sensorsimulator-xxx/lib/sensorsimulator-lib.jar 파일을 lib 디렉토리에 넣어 준다.


4. 프로젝트의 Properties 메뉴를 선택한다.


5. Java Build Path -> Libraries -> Add JARs...를 선택한다.


6. 방금전에 넣어 준 sensorsimulator-lib.jar를 선택해 추가해준다.


7. sensorsimulator-lib.jar가 Java Build Path의 Libraries에 추가된 걸 확인할 수 있다.


8. Java 소스코드에서 SensorManager를 SensorManagerSimulator로 변경해줘야 한다. 아래와 같이 SensorManager 인스턴스를 가져오는 방법을 변경하고 시뮬레이터에 연결하도록 변경해 주면 된다.


9. 센서 시뮬레이터가 앱에 시뮬레이션 되는 센서 데이터는 소켓을 통해 통신하기 때문에 INTERNET permission이 필요하다. AndroidManifest.xml에 아래와 같이 추가해 주면 된다.


10. PC에 sensorsimulator를 실행한 다음 에뮬레이터에서 앱을 실행하면 앱이 시뮬레이터에 자동으로 연결된다.


11. 시뮬레이터의 휴대폰 그림을 움직이면 에뮬레이터 화면에서 센서값이 변화되는걸 볼 수 있다.


* 센서 인터페이스가 API level 3(Android SDK 1.5)부터 SensorListener에서 SensorEventListener로 바뀌면서 센서 관련 API가 변경되었는데 아쉽게도 SensorSimulator는 SensorListener를 사용하는 API만 지원한다. 그래서 이전 포스트의 코드를 바로 사용할 수는 없고 이전 API에 맞게 조금 변형해 줘야 한다. res/layout/main.xml은 동일한 코드를 사용하면 된다.

/src/.../SensorSimTest.java

...
import org.openintents.sensorsimulator.hardware.SensorManagerSimulator;
...
public class SensorSimTest extends Activity {
    SensorManagerSimulator sm;
    SensorListener sl;
    TextView ax, ay, az;
    TextView ox, oy, oz;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        sm = SensorManagerSimulator.getSystemService(this, SENSOR_SERVICE);
        sm.connectSimulator();

        sl = new SL();
       
        ax = (TextView)findViewById(R.id.acc_x);
        ay = (TextView)findViewById(R.id.acc_y);
        az = (TextView)findViewById(R.id.acc_z);
        ox = (TextView)findViewById(R.id.ori_x);
        oy = (TextView)findViewById(R.id.ori_y);
        oz = (TextView)findViewById(R.id.ori_z);
    }
   
    @Override
    public void onResume() {
        super.onResume();
       
        sm.registerListener(sl, SensorManager.SENSOR_ACCELEROMETER | SensorManager.SENSOR_ORIENTATION,
                SensorManager.SENSOR_DELAY_NORMAL);
    }
   
    @Override
    public void onPause() {
        super.onPause();
       
        sm.unregisterListener(sl);
    }
   
    private class SL implements SensorListener {
        public void onSensorChanged(int sensor, float[] values) {
            if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
                ax.setText(Float.toString(values[0]));
                ay.setText(Float.toString(values[1]));
                az.setText(Float.toString(values[2]));
            } else if (sensor == SensorManager.SENSOR_ORIENTATION) {
                ox.setText(Float.toString(values[0]));
                oy.setText(Float.toString(values[1]));
                oz.setText(Float.toString(values[2]));
            }
        }
       
        public void onAccuracyChanged(int sensor, int accuracy) {
        }
    }

}





2010년 8월 16일 월요일

안드로이드 센서 사용하기(Using Sensors in Android)



안드로이드 SDK는 하드웨어에 있는 다양한 센서를 억세스 할 수 있게 해 준다. 현재 기본적으로 지원하는 센서는 다음과 같다.



안드로이드 어플리케이션에서 센서값을 억세스 하려면 SensorManager와 SensorEventListener를 사용한다.

SensorManager sm = (SensorManager)getSystemService(SENSOR_SERVICE);
sm.registerListener(gSensorEventListener, gSensorType, gDelay);

getSystemService(SENSOR_SERVICE)를 사용해 SensorManager 인스턴스를 가져온 다음 SensorEventListener 오브젝트를 등록해주면 센서값을 억세스 할 수 있다.

private void AccSensorEventListener implements SensorEventListener {
  public void onSensorChanged() {
    ...
  }
  public void onAccuracyChanged() {
    ...
  }
}

SensorEventListener 오브젝트에는 onSensorChanged()와 onAccuracyChanged() 메소드를 구현해 줘야 한다. 센서 값이 바뀔때마다 리스너 오브젝트의 onSensorChanged() 메소드가 호출된다.





/src/.../SensorTest.java

...
public class SensorTest extends Activity {
    SensorManager sm;
    SensorEventListener accL;
    SensorEventListener oriL;   
    Sensor oriSensor;
    Sensor accSensor;
    TextView ax, ay, az;
    TextView ox, oy, oz;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
      sm = (SensorManager)getSystemService(SENSOR_SERVICE);    // SensorManager 인스턴스를 가져옴           
        oriSensor = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);    // 방향 센서
        accSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);    // 가속도 센서
        oriL = new oriListener();        // 방향 센서 리스너 인스턴스
        accL = new accListener();       // 가속도 센서 리스너 인스턴스
        ax = (TextView)findViewById(R.id.acc_x);
        ay = (TextView)findViewById(R.id.acc_y);
        az = (TextView)findViewById(R.id.acc_z);
        ox = (TextView)findViewById(R.id.ori_x);
        oy = (TextView)findViewById(R.id.ori_y);
        oz = (TextView)findViewById(R.id.ori_z);
    }
   
    @Override
    public void onResume() {
        super.onResume();
               
        sm.registerListener(accL, accSensor, SensorManager.SENSOR_DELAY_NORMAL);    // 가속도 센서 리스너 오브젝트를 등록
        sm.registerListener(oriL, oriSensor, SensorManager.SENSOR_DELAY_NORMAL);    // 방향 센서 리스너 오브젝트를 등록
    }
   
    @Override
    public void onPause() {
        super.onPause();
       
       sm.unregisterListener(oriL);    // unregister acceleration listener
       sm.unregisterListener(accL);    // unregister orientation listener
    }
   
   
    private class accListener implements SensorEventListener {
        public void onSensorChanged(SensorEvent event) {  // 가속도 센서 값이 바뀔때마다 호출됨
            ax.setText(Float.toString(event.values[0]));
            ay.setText(Float.toString(event.values[1]));
            az.setText(Float.toString(event.values[2]));
            Log.i("SENSOR", "Acceleration changed.");
            Log.i("SENSOR", "  Acceleration X: " + event.values[0]
                          + ", Acceleration Y: " + event.values[1]
                           + ", Acceleration Z: " + event.values[2]);
        }
       
        public void onAccuracyChanged(Sensor sensor, int accuracy) {   
        }       
    }
   
    private class oriListener implements SensorEventListener {
        public void onSensorChanged(SensorEvent event) {  // 방향 센서 값이 바뀔때마다 호출됨
            ox.setText(Float.toString(event.values[0]));
            oy.setText(Float.toString(event.values[1]));
            oz.setText(Float.toString(event.values[2]));
            Log.i("SENSOR", "Orientation changed.");
            Log.i("SENSOR", "  Orientation X: " + event.values[0]
                          + ", Orientation Y: " + event.values[1]
                          + ", Orientation Z: " + event.values[2]);
        }
       
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
           
        }
    }
}




/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView  android:text="Sensor Values" android:gravity="center" android:textSize="18sp"
        android:layout_width="fill_parent" android:layout_height="wrap_content" />
    <LinearLayout android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
        <TextView android:text="Acceleration(X-axis): "
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView android:id="@+id/acc_x"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />   
    </LinearLayout>
    <LinearLayout android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
        <TextView android:text="Acceleration(Y-axis): "
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView android:id="@+id/acc_y"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />   
    </LinearLayout>
    <LinearLayout android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
        <TextView android:text="Acceleration(Z-axis): "
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView android:id="@+id/acc_z"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />   
    </LinearLayout>
    <View android:layout_width="fill_parent" android:layout_height="4dp" android:background="#ff0000" />
    <LinearLayout android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
        <TextView android:text="Orientation(X-axis): "
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView android:id="@+id/ori_x"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />   
    </LinearLayout>
    <LinearLayout android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
        <TextView android:text="Orientation(Y-axis): "
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView android:id="@+id/ori_y"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />   
    </LinearLayout>
    <LinearLayout android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
        <TextView android:text="Orientation(Z-axis): "
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView android:id="@+id/ori_z"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />   
    </LinearLayout>
   
</LinearLayout>


2010년 8월 8일 일요일

안드로이드에 한글 키보드 설치(Install HangulKeyboard to emulator)

안드로이드 에뮬레이터를 실행해 보면 기본적으로 한글 키보드는 빠져 있습니다.
한글 키보드를 사용하려면 apk 파일을 다운받아 따로 설치해 줘야 합니다.


위의 주소에서 HangulKeyboard.apk 파일을 다운받은 다음 Windows+R 을 눌러 나온 팝업창에 'cmd'를 입력해서 명령어 창을 열어줍니다.
파일을 다운받은 디렉토리로 간 다음 아래와 같이 입력해 줍니다. (물론 에뮬레이터는 미리 실행되어 있어야 합니다.)

c:\android-sdk\tools> adb devices
List of devices attached
emulator-5554    device
emulator-5556    device

c:\android-sdk\tools> adb -s emulator-5554 install HangulKeyboard.apk
954 KB/s (61114 bytes in 0.062s)
   pkg: /data/local/tmp/HangulKeyboard.apk
Success

c:\android-sdk\tools>

이런식으로 각각의 에뮬레이터에 한글 키보드를 설치해주면 됩니다. 현재 실행되어 있는 에뮬레이터가 하나밖에 없는 경우는 위의 명령에서 "-s emulator-5554" 부분은 빼고 쳐 줘도 됩니다.


홈 화면에서 설정을 선택합니다.


언어 및 키보드를 선택해 줍니다.


Hangul Keyboard 체크박스를 눌러 체크가 되도록 해 줍니다.


선택되면 위와 같이 주의사항이 나오고 그래도 한글 입력기를 사용할건지 확인합니다. 여기서 OK를 선택해주면 됩니다. 그리고 한글 키보드 이외의 키보드는 다 체크박스가 체크되지 않도록 해 주면 됩니다.

2010년 8월 4일 수요일

안드로이드 서비스 디버깅 하기 (Attach debugger for service in Android)

서비스만 있는 어플리케이션을 디버깅 할 때 서비스에는 브레이크 포인트를 잡아 놔도 실제로 그 부분이 실행될 때 브레이크가 걸리지 않는다.




액티비티를 debug run 하면 어플리케이션이 시작될 때 화면에 Waiting for Debugger라는 메시지가 잠시 나타났다 사라지고 이클립스에 Debug perspective가 열리게 된다.


액티비티의 브레이크 포인트에서 실행이 멈추고 개발자의 다음 동작을 기다리게 된다.


하지만 서비스만 있는 어플리케이션의 경우 debug run을 해도 위와 같은 화면이 나오지도 않고 서비스에 잡아 놓은 브레이크 포인트는 멈추지 않고 그냥 지나쳐 버린다. 해결책은 명식적으로 서비스를 디버거에 붙이도록 선언해 주는 것이다. android.os.Debug.waitForDebugger(); 를 호출해 주면 된다.

SoftKeyboard 튜토리얼에 디버거를 사용하려면 아래와 같이 수정해주면 된다.

public class SoftKeyboard extends InputMethodService implements KeyboardView.OnKeyboardActionListener {
  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    Log.d("SoftKeyboard", "onConfigurationChanged()");

    android.os.Debug.waitForDebugger();

    super.onConfigurationChanged(newConfig);

    // do something useful....

  }

onConfigurationChanged의 두번째 줄에서 서비스를 명시적으로 디버거에 연결시켜주기 때문에 브레이크 포인트에서 실행이 멈추게 된다.

* 어플리케이션에 액티비티와 서비스가 같이 들어있는 경우에는 액티비티가 먼저 실행되면서 디버거에 연결하기 때문에 서비스에 따로 waitForDebugger()를 호출하지 않아도 서비스에도 브레이크 포인트를 바로 사용할 수 있다.

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


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