顯示具有 android 標籤的文章。 顯示所有文章
顯示具有 android 標籤的文章。 顯示所有文章

2021/04/29

Android Service

Android Service

 

Android Service

簡述

  • Service可以在背景不斷的工作,直到停止或是系統無法提供資源為止。
  • Service 需要透過某Activity 或者其他Context 物件來啟動。
  • Service不需要和 user 互動,所以沒有操作介面。
  • 生命週期與Activity是各自獨立的,Activity就算關閉,Service仍然可以繼續執行。
  • 類似 BroadcastReceiver,需要定義一個繼承 Service 的類別,並覆寫其中的生命週期函數,最後在AndroidManifest.xml中宣告才能使用
  • Service可以同時支援 StartedBind 兩種模式。在這種情況下,Service 需要等到兩種模式都被關閉才會觸發onDestroy()事件。
  • Service 只有第一次被啟動時,會執行onCreate(),若重複啟動則不會執行onCreate()
  • Service的運作優先權相當的高,一般來說除非系統資源耗盡,否則 Android 不會主動關閉一個已被啟動的Service。一旦系統有足夠的資源,被 Android 關閉的Service也會被重新啟動。
  • 兩者都需要在AndroidManifest.xml宣告

Service 生命週期

根據執行方式的不同,啟動Service分為兩種,注意只有黃色區塊不一樣: 圖左- Started 模式;圖右- Bind 模式,並根據不同模式複寫白色框框裡的函式。

Started 模式

啟動此模式的 Service,即便退出 Activity 也不會影響 Service 的運行,且Activity無法調用 Service 的方法。

  • 啟動 Service 的方式

    • Client side (Activity):Context.startService(intent)
  • 關閉 Service 的方式

    • Client side (Activity):Context.stopService()
    • Service inside:stopSelf()
  • onStartCommand() 最後需要回傳一個常數,這些常數定義在 Service 類別中。該回傳值是用在如果這個 Service 被 Android 作業系統終止後的行為:

    • Service.START_STICKY Service 如果被中止的話會自動重啟。用在onStartCommand()方法中不需要依賴Intent傳入的資料就可以執行的時候(重新啟動時重新傳入的Intent會是null)。這也是預設使用super.onStartCommnad()的回傳值。
    • Service.START_NOT_STICKY Servcie 如果被中止的話不重新啟動,用在onStartCommand()方法中所執行的工作需要依賴Intent物件內帶進來的參數。
    • Service.START_REDELIVER_INTENT START_STICKY 差不多,但 Android 會在中止 Service 之前將Intent保留下來,等待重新啟動時再將原本的Intent物件交還給onStartCommand()事件。

 

Bind 模式

啟動此模式的 Service,會伴隨著與調用者(Client)一起存活或是退出,當Activity退出Service的運行也會一起終止。

  • 啟動 Service 的方式

    • Client side (Activity):Context.bindService(intent, mServiceConnection, int flags)。其中第二個參數為ServiceConnection 物件,當bindService()綁定成功後,會呼叫此物件內的 onServiceConnected 函式,此函式會接收到由 Service 內的 onBind() 所丟出來的 IBinder 物件來直接操作 Service 內各個 public 的 method。 (ServiceConnection物件實作方式參考下方連結)
  • 關閉 Service 的方式

    • Service inside: unbindService(mServiceConnection);

 

解析 startService

透過以下範例了解startService運作流程,若需詳細程式碼請參考

測試角色

Service 端

  • TestService

Client 端

  • MainActive

結果

这里写图片描述

 

生命週期流程

这里写图片描述

 

再次分析onStartCommand() 的回傳值

  • START_NOT_STICKY

    • 表示當 Service 運行的 process 被 Android 系統強制殺掉之後,不會重新創建該 Service,當然如果在其被殺掉之後一段時間又調用了 startService,那麼該 Service 又將被實例化。
    • Ex:某個 Service 需要定時從 server 獲取最新數據:通過一個定時器每隔指定的N分鐘讓定時器啟動 Service 去獲取 server 的最新數據。當執行到 ServiceonStartCommand 時,在該方法內再規劃一個N分鐘後的定時器用於再次啟動 Service 並開闢一個新的執行緒去執行網絡操作。假設 Service 在從 server 獲取最新數據的過程中被 Android 系統強制殺掉,Service 不會再重新創建,這也沒關係,因為再過N分鐘定時器就會再次啟動該 Service 並重新獲取數據。
  • START_STICKY

    • 表示 Service 運行的 process 被 Android 系統強制殺掉之後,Android 系統會將該 Service 依然設置為 started 狀態(即運行狀態),但是不再保存 onStartCommand 方法傳入的 intent 對象,然後 Android 系統會嘗試再次重新創建該 Service,並執行 onStartCommand 回調方法,但是 onStartCommand 回調方法的 Intent 參數為 null,也就是 onStartCommand 方法雖然會執行但是獲取不到 intent 信息。
    • Ex:如果 Service 可以在任意時刻運行或結束都沒什麼問題,而且不需要 intent 信息,那麼就可以在 onStartCommand 方法中返回 START_STICKY,比如一個用來播放背景音樂功能的 Service 就適合返回該值
  • START_REDELIVER_INTENT

    • 表示 Service 運行的 process 被 Android 系統強制殺掉之後,與返回 START_STICKY 的情況類似,Android 系統會將再次重新創建該 Service,並執行 onStartCommand 回調方法,但是不同的是,Android 系統會再次將 Service 在被殺掉之前最後一次傳入 onStartCommand 方法中的 Intent 再次保留下來,並再次傳入到重新創建後的 ServiceonStartCommand 方法中,這樣我們就能讀取到 intent 參數。
    • Ex:如果 Service 需要依賴具體的 Intent 才能運行(需要從 Intent 中讀取相關數據信息等),並且在強制銷毀後有必要重新創建運行,那麼這樣的 Service 就適合。

解析 bindService

Client 與 Service 在同一個 App 中

透過以下幾個範例了解 bindService 的運作流程,若需詳細程式碼請參考

測試角色

Service 端

  • TestService

Client 端

  • 兩個 Activity,分別為 Active AActive B
  1. Activity A
  2. Activity B

測試流程

測試一

步驟
  • Activity A

    • click bindService
    • click unbindService
結果

測試二

步驟
  • Activity A

    • click bindService
    • click Finish
結果

測試三

步驟
  • Activity A

    • click bindService
    • click start Activity B
  • Activity B

    • click bindService
    • click unbindService
    • click Finish
  • Activity A

    • click unbindService
結果

生命週期流程

Client 與 Service 在同一個 App 中

參考

淺談 Service v.s. thread

  • 看似都是在背景運作,但實際上ServiceThread 沒有任何關係。

  • Service 是運行於主執行緒(Main Thread)上,故若在此執行太耗時的任務,依然會出現 ANR。

  • Thread 是用於開啟一個子執行緒(Child thread),去執行一些耗時作不會阻塞主執行緒的運行。

  • Activity 很難對 Thread 進行控制:

    • Activity 被銷毀後卻沒有主動停止 thread,就會造成没有辦法可以再重新獲取到之前建立的 thread 的實例。
    • 在某 Activity 中建立的 thread,另一個 Activity 無法對其進行操作。
  • Service 方便取得控制權:

    • Activity 被銷毀,其他的 Activity 都可以與 Service 重新進行關聯,就又能夠獲取到原有 Service 中 Binder 的實例。
    • 使用 Service 來處理後台任務,Activity 可以放心地 finish,完全不需要擔心無法對後台任務進行控制的情況。
  • 如果在Service內部做一些很耗時的任務,可以在Service內部建立一個執行緒來處理。因為Service是跑在主執行緒中,會影響到 UI 操作或是阻擋主執行緒的運行

    • ex:在後台播放多媒體檔案會需要檢查SD、在後台紀錄 gps 資訊的變換等等

Ref.

Service背景執行程式

《Android》『Service』- 背景執行服務的基本用法

Android中startService的使用及Service生命周期

Android中bindService的使用及Service生命周期

Android Service和Thread区别

 

2017/08/28

Firebase Cloud Messaging FCM

GCM 已經被 Firebase Cloud Messaging FCM 取代,以下記錄測試 FCM 的過程。

註冊 Firebase

firebase 的頁面登入 google 帳號,登入後點擊右上方的 "Console" 即可進入控制台,第一件事是建立Firebase 專案,進入主控台後,請按下「CREATE NEW PROJECT」建立一個新專案,專案名稱填 alarm,國家/地區 填台灣。

接下來建立一個 android 應用程式,套件名稱的部分,要跟 Android APP 一樣,我們填 tw.com.maxkit.alarm

建立 Android project

用 Android Studio 建立一個新的 project: Alarm,package name 為 tw.com.maxkit.alarm。

將剛剛新增的 firebase android 應用程式裡面下載的 "google-services.json" 這個檔案,放到 Alarm/app 這個目錄裡面。

修改以下的設定檔

  • Alarm/build.gradle

增加一行 classpath 'com.google.gms:google-services:3.1.0'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.2'

        classpath 'com.google.gms:google-services:3.1.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
  • Alarm/app/build.gradle

增加這三個部分

compile 'com.google.firebase:firebase-messaging:10.2.6'
compile 'com.firebase:firebase-jobdispatcher:0.5.2'

apply plugin: 'com.google.gms.google-services'

完整的內容

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "tw.com.maxkit.alarm"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'

    compile 'com.google.firebase:firebase-messaging:10.2.6'
    compile 'com.firebase:firebase-jobdispatcher:0.5.2'
}

apply plugin: 'com.google.gms.google-services'
  • Alarm/app/src/main/AndroidManifest.xml

增加兩個 meta-data,以及 MyFirebaseMessagingService 這個 service

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="tw.com.maxkit.alarm">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <!-- 收到通知時, 到狀態列要顯示的 icon -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_icon"
            android:resource="@drawable/ic_stat_announcement" />
        <!-- 收到通知的背景色 -->
        <meta-data
            android:name="com.google.firebase.messaging.default_notification_color"
            android:resource="@color/black" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".MyFirebaseMessagingService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT"/>
            </intent-filter>
        </service>
    </application>

</manifest>

這裡用到了自訂的 color 跟 icon

Alarm/app/src/main/res/values/colors.xml

要增加 black

<color name="black">#00000000</color>
  • MainActivity.java

處理 notification 的部分,由系統列點擊 notification 會進入 MainActivity,在這裡取得該 notification 的資訊。

package tw.com.maxkit.alarm;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import com.google.firebase.messaging.FirebaseMessaging;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

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

        //這一行是要註冊 alarms 這個 topic,如果不需要,就把這行刪除
        FirebaseMessaging.getInstance().subscribeToTopic("alarms");

        Log.d(TAG, "onCreate ..");

        Intent intent = getIntent();
        String msg = intent.getStringExtra("msg");

        if (msg!=null)
            Log.d(TAG, "msg:"+msg);

        if (getIntent().getExtras() != null) {
            for (String key : getIntent().getExtras().keySet()) {
                Object value = getIntent().getExtras().get(key);
                Log.d(TAG, "Key: " + key + " Value: " + value);
            }
        }
    }

}
  • MyFirebaseMessagingService.java

處理訊息通知的 code,主要是 onMessageReceived

package tw.com.maxkit.alarm;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class MyFirebaseMessagingService extends FirebaseMessagingService{

    private static final String TAG = "MyFirebaseMsgService";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Log.d(TAG, "onMessageReceived:"+remoteMessage+", from:"+remoteMessage.getFrom()+", data="+remoteMessage.getData());

        if (remoteMessage.getData().size() > 0) {
            Log.d(TAG, "Message data notifytitle: "+ remoteMessage.getData().get("notifytitle"));
            Log.d(TAG, "Message data notifybody: "+ remoteMessage.getData().get("notifybody"));
            Log.d(TAG, "Message data payload: " + remoteMessage.getData());
        }

        if (remoteMessage.getNotification() != null) {
            Log.d(TAG, "Message Notification title: "+ remoteMessage.getData().get("title"));
            Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
        }

        //Display notification in notification bar
        sendNotification(remoteMessage.getData().get("notifytitle"), remoteMessage.getData().get("notifybody"));
    }

    private void sendNotification(String notifytitle, String notifybody) {
        Intent intent = new Intent(this, MainActivity.class);

        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);

        Uri defaultSoundUri=
                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_stat_access_alarms)
                .setContentTitle( notifytitle )
                .setContentText( notifybody )
                //.setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }
}

用 Firebase 網頁測試

在左側點擊 Notifications 的部分,填寫以下的訊息,就會以 notification 的方式,收到訊息。意思就是說,當 APP 在背景時,會直接在系統列裡面收到 notification。

用 curl 測試

根據 Firebase Cloud Messaging HTTP Protocol 的說明,可以直接用 http 的方式發送訊息。

在專案設定 -> Cloud Messaging 的地方,可以找到 Authorization Key 伺服器金鑰

以下是 curl 的測試,"Authorization: key=" 後面要換成剛剛取得的 伺服器金鑰。

data 的部分可以參考 Firebase Cloud Messaging HTTP Protocol 的說明,以下我們設定了 to alarms 這個 topic,另外只傳送了 data 區塊,在 android APP 不管是前景或背景,都會在 onMessageReceived 收到訊息,我們可在那邊發送 local notification 把資料顯示在系統列上。

curl -X POST -H "Content-Type: application/json" \
    -H "Authorization: key=yourkey" \
    https://fcm.googleapis.com/fcm/send \
    -d '{"to":"/topics/alarms", "priority":"high", "data":{"notifytitle":"測試title", "notifybody":"測試body", "訊息 key": "訊息 Topic!", "key2": "value2"}}'

也可以加上 notification 的區塊,這部分就會跟在 Firebase 頁面測試的結果一樣,Android APP 會直接收到 notification。

curl -X POST -H "Content-Type: application/json" \
    -H "Authorization: key= yourkey" \
    https://fcm.googleapis.com/fcm/send \
    -d '{"to":"/topics/alarms", "priority":"high", "notification":{"title" : "title","icon" : "new","body" : "訊息 body"}, "data":{"訊息 key": "訊息 Topic!", "key2": "value2"}}'

References

Firebase 心得(Cloud Messaging)

在Android中使用2016新版Firebase加快開發過程(一)

Firebase雲端訊息-發送測試通知至Android APP中

Firebase Cloud Messaging (FCM) 入門到進階應用(1) --- 開發環境建立

【Android Studio】從 GCM 移植到 FCM

Android: 利用Firebase實作使用者間的產品分享機制

Send Messages to Multiple Devices

Firebase push Notification using curl command — Devoid Backend


for ios

Push Notification教學:如何使用Firebase在iOS實現推播功能

[教學] 實作Google Firebase的Notification 使用Objective-C

2016/10/04

Android Studio Import Eclipse Android Project


Android 在 2015 / 6 / 26 發表的 An update on Eclipse Android Developer Tools 文章中有提到,將在 2015 年底停止更新 Eclipse 的 ADT plug-in。 但以往大部分的 Android 專案都是使用 eclipse 這個 IDE,該如何把舊的專案從 eclipse 中移植到 android studio 呢?以下就來簡單介紹一下流程

步驟一:將專案從 eclipse 匯出

  1. 將專案從 eclipse 匯出

  2. 選擇 Generate Gradle build files

  3. Import Instead 內容可以看看,下一步

  4. 選擇要匯出的專案

  5. Finish

  6. 打開專案目錄可以看到多了 gradle 相關檔案

步驟二:將專案匯入 Android Studio

  1. Import Project

  2. 切到你的專案跟目錄並開啟專案

  3. 設定 android sdk
    1. Project Structure

    2. 設定 Compile SDK / Min SDK / Target SDK



    3. Import lib ( 如果有需要,eclipse 有 link lib 的話 )



    ※ 在 libs 目錄下的 jar 不需要另外 import,因為 build.gradle 文件中已經定義好
    
    dependencies {
        compile fileTree(include: '*.jar', dir: 'libs')
    }
    

Error

  1. Android Gradle plug-in version unsupport


    sol:直接點選 Fix plugin version and sync project

References

  1. An update on Eclipse Android Developer Tools
  2. ADT Plugin Release Notes
  3. Migrate to Android Studio from Eclipse

2016/03/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 高效能多執行緒