bindService直後のserviceインスタンス

ActivityにはServiceを呼び出すためのメソッドが用意されているが、AIDLを使用して互いにインタフェースを通じて通信を実施する場合は、まず最初にサービスをバインドするためにbindServiceメソッドを使用する。

IWebScrapingService service;

private ServiceConnection serviceConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
        service = IWebScrapingService.Stub.asInterface(binder);
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        service = null;
    }
};
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //IWebScrapingServiceを起動、既に起動されていればバインド
    Intent intent = new Intent(IWebScrapingService.class.getName());
    if ( this.bindService(intent, this.serviceConn, Activity.BIND_AUTO_CREATE) ) {
        try {
            //以下、serviceフィールドはまだnull
            this.service.scrape("http://d.hatena.ne.jp/Kazzz/", new Bundle());
        } catch (RemoteException e) {
            //例外発生
        }
    }
}

bindServiceメソッドが成功した場合(戻り値が真の場合)、バインドされたIWebScrapingServiceはその直後から使えると思ったのだが、何故か例外が発生する。

E/AndroidRuntime(  427): java.lang.RuntimeException: Unable to start activity ComponentInfo{../..}: java.lang.NullPointerException
E/AndroidRuntime(  427):        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2401)
E/AndroidRuntime(  427):        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2417)
E/AndroidRuntime(  427):        at android.app.ActivityThread.access$2100(ActivityThread.java:116)
E/AndroidRuntime(  427):        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1794)
E/AndroidRuntime(  427):        at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(  427):        at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime(  427):        at android.app.ActivityThread.main(ActivityThread.java:4203)
E/AndroidRuntime(  427):        at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(  427):        at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime(  427):        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
E/AndroidRuntime(  427):        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:549)
E/AndroidRuntime(  427):        at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(  427): Caused by: java.lang.NullPointerException
:

なんと、bindService直後のthis.serviceはnullなのだった。
どうしてメソッドは真を返したのにserviceがバインドされていない? 最初は訝しげに思ったのだが、Activityの起動時のスタックトレースを見ることで納得した。

スレッド [<3> main] (中断中 (CalendarActivity の 61 行にブレークポイント))    
    CalendarActivity.onCreate(Bundle) 行: 61    
    Instrumentation.callActivityOnCreate(Activity, Bundle) 行: 1123    
    ActivityThread.performLaunchActivity(ActivityThread$ActivityRecord, Intent) 行: 2364    
    ActivityThread.handleLaunchActivity(ActivityThread$ActivityRecord, Intent) 行: 2417    
    ActivityThread.access$2100(ActivityThread, ActivityThread$ActivityRecord, Intent) 行: 116    
    ActivityThread$H.handleMessage(Message) 行: 1794    
    ActivityThread$H(Handler).dispatchMessage(Message) 行: 99    
    Looper.loop() 行: 123    
    ActivityThread.main(String) 行: 4203    
    Method.invokeNative(Object, Object, Class, Class, Class, int, boolean) 行: 使用不可 [ネイティブ・メソッド]    
    Method.invoke(Object, Object...) 行: 521    
    ZygoteInit$MethodAndArgsCaller.run() 行: 791    
    ZygoteInit.main(String) 行: 549    
    NativeStart.main(String[]) 行: 使用不可 [ネイティブ・メソッド]    

Instrumentationによって起動されたonCreateメソッドが完了するまで、Activityは未だ「起動中」であり本来のActivityの動作が出来る状態になっていない、ということなのだろう。

逃げ方はいろいろあると思うが、取りあえずは以下のように修正することでサービスのバインドを確認できた。

IWebScrapingService service;

private ServiceConnection serviceConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
        service = IWebScrapingService.Stub.asInterface(binder);
        try {
            this.service.scrape("http://d.hatena.ne.jp/Kazzz/", new Bundle());
        } catch (RemoteException e) {
            //例外発生
        }
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        service = null;
    }
};
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //IWebScrapingServiceを起動、既に起動されていればバインド
    Intent intent = new Intent(IWebScrapingService.class.getName());
    this.bindService(intent, this.serviceConn, Activity.BIND_AUTO_CREATE);
}

Activityの場合、onCreateもコンストラクタの一部であると理解しておいたほうが良さそうだ。