使用者看不見,或是要開放給其他 APP 使用的功能,要用 Service 實作,Service 是在 UI Thread 裡面執行,雖然不跟 UI 互動,但還是會降低程式的回應速度。
Service 可降低 Thread 發生 memory leak 的狀況:BroadcastReceiver 跟 Activity 的生命週期與被境執行緒的執行沒關係,但Service 可在結束時,同時控制執行緒的銷毀。最好的實作方式,就是用 Activity 先啟動 Service,再由 Service 啟動執行緒。
Local, 私有遠端, 全域遠端 Service
- 區域 Service Service 執行在跟呼叫 Service 的元件的 process中,在同一個 UI Thread 裡面,共用相同的 Heap。因此,Service能夠跟 client component 共用 Java 物件。
<service
android:name="com.test.TestService">
</service>
- 私有遠端 Service Service 有自己的 process,只能被屬於相同應用程式的 client component 使用,因此,Service 不會干擾 client component 的 UI Thread。
<service
android:name="com.test.TestService"
android:process=":com.test.TestServiceProcess">
</service>
- 全域遠端 Service Service 開放給其他 APP 使用。但不能以 Service 類別名稱被參考,只能用 intent filter 的方式存取。
<service
android:name="com.test.TestService"
android:process="Com.test.TestServiceProcess">
<intent-filter>
<action android:name=".." />
<category android:name=".." />
</intent-filter>
</service>
Service 的生命週期
服務有兩種
啟動 context.startService(Intent)傳送啟動 request 給 Service,被啟動的服務要實作 onStartCommand,回傳值有三種:STARTSTICKY(服務會被重新啟動)、STARTNOTSTICKY(服務只有在被終止時,有未完成的啟動request 才會重新啟動)、STARTREDELIVER_INTENT(服務會被重新啟動,並重送先前的啟動 Intent)。
繫結 bind 在第一個 bind 的時候,會建立 Service,所有元件都 unbind 時會銷毀此 Service。
藍牙 sample
public class BluetoothActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bluetooth);
}
public void onStartListening(View v) {
Intent intent = new Intent(this, BluetoothService.class);
intent.putExtra(BluetoothService.COMMAND_KEY, BluetoothService.COMMAND_START_LISTENING);
startService(intent);
}
public void onStopListening(View v) {
Intent intent = new Intent(this, BluetoothService.class);
stopService(intent);
}
}
public class BluetoothService extends Service {
private static final String TAG = "BluetoothService";
public static final String COMMAND_KEY = "command_key";
public static final String COMMAND_START_LISTENING = "command_start_discovery";
private static final UUID MY_UUID = new UUID(323476234, 34587387);
private static final String SDP_NAME = "custom_sdp_name";
private BluetoothAdapter mAdapter;
private BluetoothServerSocket mServerSocket;
private boolean mListening = false;
private Thread listeningThread;
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
mAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mAdapter != null) {
// 控制執行緒的數量,一次只有一個配對操作
if (intent.getStringExtra(COMMAND_KEY).equals(COMMAND_START_LISTENING) && mListening == false) {
startListening();
}
}
// 臨時終止時,可重做配對工作
return START_REDELIVER_INTENT;
}
private void startListening() {
mListening = true;
listeningThread = new Thread(new Runnable() {
@Override
public void run() {
BluetoothSocket socket = null;
try {
mServerSocket = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SDP_NAME, MY_UUID);
// blocking
socket = mServerSocket.accept();
if (socket != null) {
// Handle BT connection
}
} catch (IOException e) {
Log.d(TAG, "Server socket closed");
}
}
});
listeningThread.start();
}
private void stopListening() {
mListening = false;
try {
if (mServerSocket != null) {
mServerSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
super.onDestroy();
stopListening();
}
}
Local Service
以下是使用 Local Service 的範例
public class BoundLocalActivity extends Activity {
private LocalServiceConnection mLocalServiceConnection = new LocalServiceConnection();
private BoundLocalService mBoundLocalService;
private boolean mIsBound;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bindService(new Intent(BoundLocalActivity.this, BoundLocalService.class),
mLocalServiceConnection,
Service.BIND_AUTO_CREATE);
mIsBound = true;
}
@Override
protected void onDestroy() { super.onDestroy();
if (mIsBound) {
try {
unbindService(mLocalServiceConnection);
mIsBound = false;
} catch (IllegalArgumentException e) {
// No bound service
}
}
}
private class LocalServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mBoundLocalService = ((BoundLocalService.ServiceBinder)iBinder).getService();
// At this point clients can invoke methods in the Service,
// i.e. publishedMethod1 and publishedMethod2.
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mBoundLocalService = null;
}
}
}
public class BoundLocalService extends Service {
private final ServiceBinder mBinder = new ServiceBinder();
public IBinder onBind(Intent intent) {
return mBinder;
}
public class ServiceBinder extends Binder {
public BoundLocalService getService() {
return BoundLocalService.this;
}
}
// Methods published to clients.
public void publishedMethod1() { /* TO IMPLEMENT */ }
public void publishedMethod2() { /* TO IMPLEMENT */ }
}
IntentService
IntentService 有 Service 的特性,又另外內建 HandlerThread 背景執行緒,可以循序處理任務。
但 Service 還是比 IntentService 常用,因為 Service 可由使用者自己控制,可以支援啟動多個執行緒,也可以賦予優先權,優先執行某些任務。
public class WebServiceActivity extends Activity {
private final static String TAG = WebServiceActivity.class.getSimpleName();
private final static String getUrl = "http://dn.se"; // Dummy
private final static String postUrl = "http://dn.se"; // Dummy
private ResultReceiver mReceiver;
public WebServiceActivity() {
// 產生要傳送給 IntentService 的 ResultReceiver
mReceiver = new ResultReceiver(new Handler()) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
int httpStatus = resultCode;
String jsonResult = null;
if (httpStatus == 200) { // OK
if (resultData != null) {
jsonResult= resultData.getString(WebService.BUNDLE_KEY_REQUEST_RESULT);
// Omitted: Handle response
}
}
else {
// Handle error
}
}
};
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_service);
}
public void doPost(View v) {
Intent intent = new Intent(this, WebService.class);
intent.setData(Uri.parse(postUrl));
intent.putExtra(WebService.INTENT_KEY_REQUEST_TYPE, WebService.POST);
intent.putExtra(WebService.INTENT_KEY_JSON, "{\"foo\":\"bar\"}");
intent.putExtra(WebService.INTENT_KEY_RECEIVER, mReceiver);
startService(intent);
}
public void doGet(View v) {
Intent intent = new Intent(this, WebService.class);
intent.setData(Uri.parse(getUrl));
intent.putExtra(WebService.INTENT_KEY_REQUEST_TYPE, WebService.GET);
intent.putExtra(WebService.INTENT_KEY_RECEIVER, mReceiver);
startService(intent);
}
}
public class WebService extends IntentService {
private static final String TAG = WebService.class.getName();
public static final int GET = 1;
public static final int POST = 2;
public static final String INTENT_KEY_REQUEST_TYPE = "com.eat.INTENT_KEY_REQUEST_TYPE";
public static final String INTENT_KEY_JSON = "com.eat.INTENT_KEY_JSON";
public static final String INTENT_KEY_RECEIVER = "com.eat.INTENT_KEY_RECEIVER";
public static final String BUNDLE_KEY_REQUEST_RESULT = "com.eat.BUNDLE_KEY_REQUEST_RESULT";
public WebService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
// 取得 Intent Data
Uri uri = intent.getData();
int requestType = intent.getIntExtra(INTENT_KEY_REQUEST_TYPE, 0);
String json = (String)intent.getSerializableExtra(INTENT_KEY_JSON);
ResultReceiver receiver = intent.getParcelableExtra(INTENT_KEY_RECEIVER);
try {
HttpRequestBase request = null;
switch (requestType) {
case GET: {
request = new HttpGet();
// Request setup omitted
break;
}
case POST: {
request = new HttpPost();
if (json != null) {
((HttpPost)request).setEntity(new StringEntity(json));
}
// Request setup omitted
break;
}
}
if (request != null) {
request.setURI(new URI(uri.toString()));
HttpResponse response = doRequest(request);
HttpEntity httpEntity = response.getEntity();
StatusLine responseStatus = response.getStatusLine();
int statusCode = responseStatus != null ? responseStatus.getStatusCode() : 0;
if (httpEntity != null) {
// 將結果回傳給 WebServiceActivity
Bundle resultBundle = new Bundle();
resultBundle.putString(BUNDLE_KEY_REQUEST_RESULT, EntityUtils.toString(httpEntity));
receiver.send(statusCode, resultBundle);
}
else {
receiver.send(statusCode, null);
}
}
else {
receiver.send(0, null);
}
}
catch (IOException e) {
receiver.send(0, null);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
private HttpResponse doRequest(HttpRequestBase request) throws IOException {
HttpClient client = new DefaultHttpClient();
// HttpClient configuration omitted
return client.execute(request);
}
}