2013年4月24日水曜日

AndroidのServiceについて調べてみた

Serviceについて調べてみたのでまとめ。

Serviceを起動前にBindしても起動できる
bindService()でServiceにBindすると一緒にServiceがCreateされる。ただし、bindService()で起動されたServiceはunbindService()された時に殺される。unBindした後も起動しておいて欲しい場合はstartService()後にbindService()してあげる事でunbindService()したとしても殺されない。bindしたActivityがunbindせずに死ぬと例外吐くからちゃんとonDestoryとかでunbindしてあげましょう。

Serviceはメモリがなくなったら殺される
Backgroundで起動して動いている間は死なないものだと思っていたけど、Serviceはメモリがなくなったら死ぬ。メモリがなくなった場合はonDestroyも通らずに強制的にKillされるらしい。なので、常駐させる場合はstartForeground()で今起動中だということを教えてあげると表示中のActivityと同じぐらい優先度が上がってそうそう殺されなくなる。ただメモリがどうしてもなくなったときは殺されるらしいが、それは気にしなくていい程度の頻度だそうで。startForeground()で起動中にした場合は、stopForeground()を呼んであげる事で死んでも大丈夫だよと教えることができる。

startForeground()でNotificationを出したくない場合
ユーザに起動中というのを教えるためにService起動中はNotificationを出したほうがいいとは思うけど、どうしても出したくない場合は引数に渡すnotificationのiconを0で渡せばNotificationになにも表示されなくなる。最も手軽な方法は以下だと思う。

1
startForeground(NOTIFICATION_ID, new Notification())
aidlでActivity→Serviceの連携
普通にaidlファイルを作ってonBind()の戻り値として返してあげるとbindService()の引数で渡すServiceConnection#onServiceConnectedで取得することができる。取得した後はaidlで定義されたInterfaceを使って自由に色々やりましょうって感じ。後、AndroidManifest.xmlにactionとしてaidlを設定してあげないと通信できないらしい。

Binderを継承してActivity→Serviceの連携
aidlを使用した連携とほぼ同じ。ただし、ActivityとServiceが同一プロセスで動いている場合じゃないとだめ。こっちのほうがお手軽にできるからActivityとServiceが同一プロセス上で動く保証があるならこっち使ったほうがいいんじゃないかと。ってか、プロセスが違う場合ってServiceが起動している状態でActivityが再起動した時とかかだと思うからそういうことが起こりそうならaidl使って連携したほうがいいはず。

BroadcastReceiverでService→Activityの連携
Service側からBroadCastでメッセージ投げてActivityで受け取るだけ。bindしてんだから関数で呼び出せないものかと思ったけど、Messageで通信するのがいいっぽい。receiverはandroid:exported="false"を設定してあげて同一アプリ無いじゃないと受け取らないようにしておいたほうがセキュリティ上いいと思う。

サンプル
ServiceSample.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.choilabo.activityservicebindsample;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;

public class ServiceSample extends Service {

private IServiceSample.Stub serviceSampleInf = new IServiceSample.Stub() {
@Override
public void makeToast(String value) throws RemoteException {
Intent intent = new Intent("param");
sendBroadcast(intent);
}
};

@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
Toast.makeText(this, "onRebind ServiceSample", Toast.LENGTH_SHORT).show();
}

@Override
public boolean onUnbind(Intent intent) {
super.onUnbind(intent);
Toast.makeText(this, "onUnbind ServiceSample", Toast.LENGTH_SHORT).show();
return true;
}

@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "onCreate ServiceSample", Toast.LENGTH_SHORT).show();
}

@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, "onDestroy ServiceSample", Toast.LENGTH_SHORT).show();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "onStartCommand ServiceSample", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Toast.makeText(this, "onStart ServiceSample", Toast.LENGTH_SHORT).show();
}

@Override
public IBinder onBind(Intent intent) {
Toast.makeText(this, "onBind ServiceSample", Toast.LENGTH_SHORT).show();
return serviceSampleInf;
}

public void makeToast() {
Toast.makeText(this, "This is service message", Toast.LENGTH_SHORT).show();
}
}
MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.choilabo.activityservicebindsample;

import com.choilabo.activityservicebindsample.R;

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

private Button makeToast;
private Button btnBind;
private Button btnUnBind;
private Button btnStart;
private Button btnStop;

private ServiceConnection mConnection;
private IServiceSample connectionService;
private BroadcastReceiverSample receiver = new BroadcastReceiverSample();

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

makeToast = (Button)findViewById(R.id.btnMakeToast);
makeToast.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(connectionService != null) {
try {
connectionService.makeToast(null);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});

btnBind = (Button)findViewById(R.id.btnBind);
btnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Toast.makeText(MainActivity.this, "onServiceConnected MainActivity", Toast.LENGTH_SHORT).show();
connectionService = IServiceSample.Stub.asInterface(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {
Toast.makeText(MainActivity.this, "onServiceDisconnected MainActivity", Toast.LENGTH_SHORT).show();
}
};

Intent intent = new Intent(MainActivity.this, ServiceSample.class);
bindService(intent, mConnection, Service.BIND_AUTO_CREATE);
}
});

btnUnBind = (Button)findViewById(R.id.btnUnBind);
btnUnBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mConnection != null) {
unbindService(mConnection);
mConnection = null;
}
}
});

btnStart = (Button)findViewById(R.id.btnStart);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, ServiceSample.class);
startService(intent);
}
});

btnStop = (Button)findViewById(R.id.btnStop);
btnStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, ServiceSample.class);
stopService(intent);
}
});
}

@Override
protected void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter("param");
registerReceiver(receiver, filter);
}

@Override
protected void onStop() {
super.onStop();
unregisterReceiver(receiver);
}

@Override
protected void onDestroy() {
super.onDestroy();
if(mConnection != null) {
unbindService(mConnection);
mConnection = null;
}
}
}
IServiceSample.aidl

1
2
3
4
5
package com.choilabo.activityservicebindsample;

interface IServiceSample {
void makeToast(String value);
}
BroadcastReceiverSample.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.choilabo.activityservicebindsample;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class BroadcastReceiverSample extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "BroadcastReceiverSample get broadcast", Toast.LENGTH_SHORT).show();
}
}
AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.choilabo.activityservicebindsample"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="5"
android:targetSdkVersion="15" />

<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<receiver
android:name=".BroadcastReceiver"
android:exported="false"
/>

<service android:name=".ServiceSample">
<intent-filter>
<action android:name="IServiceSample"></action>
</intent-filter>
</service>

</application>

</manifest>
まとめ
Servieなのに殺されるってどういうことだっていう。スマホはメモリが少ないからしょうがないっちゃしょうがないのかなぁ。startForeground()はNotificationを出すからそうそう殺されないっていう理屈のはずなのにicon消せるってのもどうかと思う。まぁ、やっと少しService周りを理解できた気がする。

0 件のコメント:

コメントを投稿