Android Service開発

Reference: http://developer.android.com/reference/android/app/Service.html

AIDLを使った方式の場合、(OuterProcess Communicationという意味で当たり前のような気もしますが)
COMにとてもよく似ていますね。 coClassを作る必要がないのが、とても楽です。。

Serviceを起動する2つの方法

  1. Context.startService() (Service::onCreate() -> Service::onStart()の順で呼び出し stopService(), Service::stopSelf()で終了)
  2. Context.bindService() (Service::onCreate(), Service::onStart()は呼び出されない)

1の方法

Serviceを継承したCustomServiceを実装 (onStart()で必要な処理)
ApplicationからstartService(), stopService()で制御

2の方法
AIDL (Android Interface Definition Language)の定義 (プロセス間通信となるため、COMのIDLのようなもの)
Reference: http://developer.android.com/guide/developing/tools/aidl.html

  1. IEasyService.aidlの定義
  2. Serviceを継承したクラスを作成 (e.g EasyService)
  3. EasyServiceの中でIEasyService.Stubに定義されたメソッドを実装
  4. EasyService::onBindで3で実装したクラスを返す

  1. bindService()の呼出
  2. Service作成後にServiceConnection::onServiceConnected()が呼ばれる
  3. onServiceConnectedの中のIBinderをserviceにキャスト (IEasyService.Stub.asInterface(binder))として保存
  4. 必要な時に、3で保存したInterface経由でのメソッド呼び出し

ServerからClientを呼び出す方法

今までとはServer, Clientの関係が逆になる。
Client側でaidlで定義したInterfaceの実装が必要になる。


SampleCode

/layouts/main.xml














IEasyService.aidl

package com.makotof.android.servicetest;
interface IEasyService {
String display(String message);
}

IEasyService2.aidl (Server<->Client通信Interface)

package com.makotof.android.servicetest;

//import this interface to use this aidl.
import com.makotof.android.servicetest.IEasyService2Callback;

interface IEasyService2 {
void registerCallback(IEasyService2Callback cb);
void unregisterCallback(IEasyService2Callback cb);
}

IEasyService2Callback.aidl (Client側Interface)

package com.makotof.android.servicetest;

//set oneway not to block server from client response
oneway interface IEasyService2Callback {
void invokeFromServer(int value);
}

EasyService.java (Server側コード)

package com.makotof.android.servicetest;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.widget.Toast;

public class EasyService extends Service
{
private static final int REPORT_MSG = 1;
int mValue = 0;
final RemoteCallbackList mCallbacks
= new RemoteCallbackList();

private final IEasyService.Stub easyService = new IEasyService.Stub()
{
//show message to Statusbar
public String display(String message)
{
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.stat_sample, message, System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(EasyService.this, 0,
new Intent(EasyService.this, EasyTestActivity.class), 0);
notification.setLatestEventInfo(EasyService.this, "ここがタイトルになります", "ここがコンテンツになります", contentIntent);
manager.notify(R.string.app_name, notification);
return message;
}
};

private final IEasyService2.Stub easyService2 = new IEasyService2.Stub()
{
@Override
//register callbackinterface
public void registerCallback(IEasyService2Callback cb)
throws RemoteException {
if (cb != null) mCallbacks.register(cb);
}

@Override
//unregister callback
public void unregisterCallback(IEasyService2Callback cb)
throws RemoteException {
if (cb != null) mCallbacks.unregister(cb);
}

};

//bind 2 interface
public IBinder onBind(Intent intent)
{
if (IEasyService.class.getName().equals(intent.getAction())) {
return easyService;
}
if (IEasyService2.class.getName().equals(intent.getAction())) {
return easyService2;
}
return null;
}

private final Handler mHandler = new Handler() {
public void handleMessage(Message msg)
{
switch (msg.what)
{
case REPORT_MSG:
{
++mValue;
int N = mCallbacks.beginBroadcast();
for (int i = 0; i < N; i++)
{
try
{
mCallbacks.getBroadcastItem(i).invokeFromServer(mValue);
}
catch (RemoteException e)
{
}
}
mCallbacks.finishBroadcast();

//again, send message to itself..
sendMessageDelayed(obtainMessage(REPORT_MSG), 1*1000);
}
break;
default:
super.handleMessage(msg);
}
}
};

public void onCreate()
{
mHandler.sendEmptyMessage(REPORT_MSG);
}
public void onDestroy()
{
mCallbacks.kill();
mHandler.removeMessages(REPORT_MSG);
}
}

EasyServiceActivity.java (Client側コード)

package com.makotof.android.servicetest;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class EasyTestActivity extends Activity {
private Button btnBind, btnUnbind, btnInvoke;
private Button btnBind2, bnUnbind2;
private Button btnStart, btnStop;
private TextView txtResult;
private IEasyService2Callback cb;

private IEasyService service;
private ServiceConnection conn = new ServiceConnection(){
public void onServiceConnected(ComponentName componentName, IBinder binder)
{
service = IEasyService.Stub.asInterface(binder);

}
public void onServiceDisconnected(ComponentName componentName)
{
//TODO
}
};

private IEasyService2 service2;
private static final int CALLBACK_MESSAGE = 1;
private Handler handler = new Handler() {
public void dispatchMessage(Message msg)
{
if (msg.what == CALLBACK_MESSAGE)
{
txtResult.setText(msg.obj.toString());
}
else
{
super.dispatchMessage(msg);
}
}
};

private IEasyService2Callback listener = new IEasyService2Callback.Stub()
{
public void invokeFromServer(int value) throws RemoteException
{
handler.sendMessage(handler.obtainMessage(CALLBACK_MESSAGE, value));
}
};
private ServiceConnection conn2 = new ServiceConnection(){
public void onServiceConnected(ComponentName componentName, IBinder binder)
{
service2 = IEasyService2.Stub.asInterface(binder);
try {
service2.registerCallback(listener);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName componentName)
{
//TODO
}
};

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


btnBind = (Button)this.findViewById(R.id.btnBind);
btnUnbind = (Button)this.findViewById(R.id.btnUnbind);
btnInvoke = (Button)this.findViewById(R.id.btnInvoke);

btnBind.setOnClickListener(new OnClickListener(){
public void onClick(View view)
{
Intent intent = new Intent(IEasyService.class.getName());
bindService(intent, conn, BIND_AUTO_CREATE);

btnBind.setEnabled(false);
btnUnbind.setEnabled(true);
btnInvoke.setEnabled(true);
}
});

btnUnbind.setOnClickListener(new OnClickListener(){
public void onClick(View view)
{
unbindService(conn);

btnBind.setEnabled(true);
btnUnbind.setEnabled(false);
btnInvoke.setEnabled(false);
}
});

btnInvoke.setOnClickListener(new OnClickListener(){
public void onClick(View view)
{
try
{
service.display("Hello from EasyService");
}
catch (RemoteException e)
{
Toast.makeText(EasyTestActivity.this, "Error occured!", Toast.LENGTH_SHORT);
}
}
});

btnUnbind.setEnabled(false);
btnInvoke.setEnabled(false);

//EasyService2
btnBind2 = (Button)this.findViewById(R.id.btnBind2);
bnUnbind2 = (Button)this.findViewById(R.id.btnUnbind2);
bnUnbind2.setEnabled(false);
txtResult = (TextView)this.findViewById(R.id.result);

btnBind2.setOnClickListener(new OnClickListener(){
public void onClick(View view)
{
Intent intent = new Intent(IEasyService2.class.getName());
bindService(intent, conn2, BIND_AUTO_CREATE);

btnBind2.setEnabled(false);
bnUnbind2.setEnabled(true);
}
});

bnUnbind2.setOnClickListener(new OnClickListener(){
public void onClick(View view)
{
unbindService(conn2);
btnBind2.setEnabled(true);
bnUnbind2.setEnabled(false);
}
});
}

protected void onPause()
{
unbindService(conn);
unbindService(conn2);
}
}

androidmanifest.xml