2016年3月27日

Service in Android

使用者看不見,或是要開放給其他 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 的生命週期

服務有兩種

  1. 啟動 context.startService(Intent)傳送啟動 request 給 Service,被啟動的服務要實作 onStartCommand,回傳值有三種:STARTSTICKY(服務會被重新啟動)、STARTNOTSTICKY(服務只有在被終止時,有未完成的啟動request 才會重新啟動)、STARTREDELIVER_INTENT(服務會被重新啟動,並重送先前的啟動 Intent)。

  2. 繫結 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);
    }
}

Reference

Android 高效能多執行緒