Service 的onStartCommand 或是 onStart(2.1版本之前)是使用調用它的Android元件(通常是Activity)同一個Thread來執行的,對應Activity來說,這個Thread通常是UI Thread,當我們需要在Service當中進行一些耗時的操作,比如網絡通訊,比如資料庫操作,這個時候就會阻塞UI線程。
是以通常需要單獨開啟一個線程來執行這些耗時的操作。
Android.os 的Handler, HandlerThread, Loop, Message 常用于Service中,用于用戶端與服務端的通訊和互動。
Android 中每個Thread都可以有一Message Queue,并通過Looper實作對消息的管理,但除UI Thread外,因為UI線程中自身就帶有一個Message Queue和一個Looper。
Thread預設情況下不帶Message Queue, 要為一個Thread 建立一個Message Queue,Looper.prepare()用來建立一個Message Queue, Looper.loop() 處理消息直到Loop停止。 在Thread在建立的Handler将會和Thread的Message Queue關聯。Handler的handleMessage用來處理消息,其類型為Message類。
HandlerThread繼承了Thread,它是一個特殊的Thread,因為它本身就帶有Looper,可以讓我們友善地線上程中使用handler來處理用戶端傳遞過來的資訊。
我們現在來簡單梳理一下這個demo的基本思路:
1.在用戶端啟動服務時通過Intent的putExtr()方法将服務端需要的參數傳遞到服務端。
2.在服務端的onStrartCommand()方法中将Intent中的資料取出。
構造一條Message發送給Handler,讓它去處理這條消息。
3.在消息的進行中采用使程序停頓5秒的方式來模拟耗時操作。
另外還需要注意的一點是:我們可以使用KillProcess來結束服務的程序,但這個程序并沒有被徹底殺死,當需要使用到服務時系統會自動重新啟動這個程序。
在本例中,通過KillProcess來模拟程序意外終止的情況,但由于在結束程序時,服務還有任務沒有處理完,是以程序結束後會馬上重新啟動。服務會被重新建立,重新開始沒有完成的那部分任務。無論我們在onStartCommand方法中是否傳回的是START_REDELIVER_INTENT。是以當我們點選Strart failuer delivery按鈕時,執行完onStartCommand方法中的前半部分後,程序結束,但馬上程序又會被重新啟動,重新建立服務,重新将未完成的Intent傳入,并繼續執行完畢。
當我們點選Strart three w/redeliver按鈕,然後再點選Strart failuer delivery時服務被重新啟動後首先恢複的是three w/redeliver的Intent,執行完這個Intent後再去執行未完成任務的failure的Intent。
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/hello_world" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/start_one"
android:text="Start one no deliver"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/start_two"
android:text="Start two with redeliver"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/start_failure"
android:text="Start failed deliver"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/kill_progress"
android:text="Kill Progress"/>
</LinearLayout>
ServiceStartArgument.java
public class ServiceStartArgument extends Service {
private NotificationManager mNM;
/*
* Volatile修飾的成員變量在每次被線程通路時,都強迫從共享記憶體中重讀該成員變量的值。
* 而且,當成員變量發生變化時,強迫線程将變化值回寫到共享記憶體。 這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。
*/
private volatile Looper mServiceLooper;
private volatile ServiceHandler mHandler;
/*
* 建構一個處理消息的類,這個類繼承Handler,并且用作一個獨立的線程當中
* 是以必須要有一個Looper用來建立和管理Message隊列,同時這個looper也
* 用來溝通Handler和Thread,讓handler與thread一一對應起來,明确目前的handler是 用來處理哪個線程中的消息的。
*/
private class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = (Bundle) msg.obj;
String txt = bundle.getString("name");
// 列印一下日志
Log.i("ServiceStartArguments",
"Message: " + msg + ", " + bundle.getString("name"));
// 區分一下Intent是否是程序意外終止後又重新傳入的
if ((msg.arg2 & START_FLAG_REDELIVERY) == ) {
txt = "New cmd #" + msg.arg1 + ": " + txt;
} else {
txt = "Re-delivered #" + msg.arg1 + ": " + txt;
}
// 顯示Notification
showNotification(txt);
// 停頓5秒模拟耗時操作
synchronized (this) {
try {
Thread.sleep( * );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
hideNotification();
// 列印日志
Log.i("ServiceStartArguments", "Done with #" + msg.arg1);
// 結束服務
stopSelf(msg.arg1);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void hideNotification() {
mNM.cancel(R.string.hello_world);
}
public void showNotification(String txt) {
PendingIntent contentIntent = PendingIntent.getActivity(this, ,
new Intent(this, MainActivity.class), );
Notification.Builder builder=new Builder(this);
builder.setSmallIcon(R.drawable.ic_launcher)
.setTicker(txt)
.setContentIntent(contentIntent)
.setContentTitle("Notification")
.setContentText("Notification info")
.setWhen(System.currentTimeMillis());
Notification noti=builder.build();
mNM.notify(R.string.hello_world, noti);
}
@Override
public void onCreate() {
super.onCreate();
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// HandlerThread繼承了Thread,它是一個特殊的Thread,因為它本身就帶有Looper,
// 可以讓我們友善地線上程中使用handler來處理用戶端傳遞過來的資訊。
HandlerThread thread = new HandlerThread("ServiceStartArgument",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mHandler = new ServiceHandler(mServiceLooper);
}
/*
* 當開啟并onCreate()服務後調用此方法,我們可以在這裡完成一些操作。 該方法帶有一個整形的傳回值,通常有以下幾個常量:
* START_STICKY:如果service程序被kill掉,保留service的狀态為開始狀态,
* 但不保留遞送的intent對象。随後系統會嘗試重新建立service,由于服務狀态為開始狀态,
* 是以建立服務後一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有
* 任何啟動指令被傳遞到service,那麼參數Intent将為null。
* START_NOT_STICKY:“非粘性的”。使用這個傳回值時,如果在執行完onStartCommand後,
* 服務被異常kill掉,系統将會把它置為started狀态,系統不會自動重新開機該服務, 直到startService(Intent
* intent)方法再次被調用;
* START_REDELIVER_INTENT:重傳Intent。使用這個傳回值時,如果在執行完onStartCommand後,
* 服務被異常kill掉,系統會自動重新開機該服務,并将Intent的值傳入。
* START_STICKY_COMPATIBILITY:START_STICKY的相容版本,但不保證服務被kill後一定能重新開機。
* 該方法的第二個參數和傳回值相關,flags值和onStartCommand()的傳回值有着直接的關系。
* 如果Service被系統意外終止,重新開機的時候,傳入的flags。 flags有3種取值,0, START_FLAG_REDELIVERY, or
* START_FLAG_RETRY. 當傳回值為START_REDELIVER_INTENT時服務被意外終止,重新開機時則傳入
* START_FLAG_REDELIVERY, 将重新傳入傳回START_REDELIVER_INTENT時的Intent。
* START_FLAG_RETRY表示啟動服務的Intent沒有被OnStrartCommand接收到或傳回, 是以需要一直重新嘗試。
* 第三個參數表示目前啟動的Id,用來辨別目前的啟動,同時這個值也表示啟動的次數。
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("ServiceStartArgument",
"Starting #" + startId + ": " + intent.getExtras());
// 根據用戶端傳遞過來的資料構造消息
Message msg = mHandler.obtainMessage();
msg.arg1 = startId;
msg.arg2 = flags;
msg.obj = intent.getExtras();
mHandler.sendMessage(msg);
Log.i("ServiceStartArguments", "Sending: " + msg);
/*
* 當我們點選"Start failed deliver"按鈕時來模拟意外終止程序的情況。
* 另外還需要注意的一點是:我們可以使用KillProcess來結束服務的程序,
* 但這個程序并沒有被徹底殺死,當需要使用到服務時系統會自動重新啟動這個程序。
* 在本例中,通過KillProcess來模拟程序意外終止的情況,但由于在結束程序時,
* 服務還有任務沒有處理完,是以程序結束後會馬上重新啟動。服務會被重新建立,
* 重新開始沒有完成的那部分任務。無論我們在onStartCommand方法中是否傳回的是 START_REDELIVER_INTENT。
* 是以當我們點選Strart failuer delivery按鈕時,執行完onStartCommand方法中
* 的前半部分後,程序結束,但馬上程序又會被重新啟動,重新建立服務,重新将未完成的Intent傳入, 并繼續執行完畢。 當我們點選Strart
* three w/redeliver按鈕,然後再點選Strart failuer delivery 時服務被重新啟動後首先恢複的是three
* w/redeliver的Intent,執行完這個Intent後再去執 行未完成任務的failure的Intent。
*/
if (intent.getBooleanExtra("fail", false)) {
if ((flags & START_FLAG_RETRY) == ) {
// 如果flags的值不是START_FLAG_RETRY我們就結束掉程序
// 如果是START_FLAG_RETRY因為啟動的意圖一直都沒有傳遞進來是以會一直retry
// 在這種情況下我們不需要關心,因為系統會放棄啟動服務,如果Intent一直無法傳遞進來的話
Process.killProcess(Process.myPid());
}
}
// 判斷一下如果用戶端傳遞過來的參數是redeliver則将傳回值設定為START_REDELIVER_INTENT
// 否則設定為:START_NOT_STICKY
return intent.getBooleanExtra("redeliver", false) ? START_REDELIVER_INTENT
: START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
//退出looper
mServiceLooper.quit();
hideNotification();
Toast.makeText(this, "服務已停止!", ).show();
}
public static class MainActivity extends Activity implements
OnClickListener {
private Button start_one, start_two, start_failure, kill_progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start_one = (Button) findViewById(R.id.start_one);
start_two = (Button) findViewById(R.id.start_two);
start_failure = (Button) findViewById(R.id.start_failure);
kill_progress = (Button) findViewById(R.id.kill_progress);
start_one.setOnClickListener(this);
start_two.setOnClickListener(this);
start_failure.setOnClickListener(this);
kill_progress.setOnClickListener(this);
}
// 開啟服務的同時向服務端傳遞參數
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,
ServiceStartArgument.class);
switch (v.getId()) {
case R.id.start_one:
intent.putExtra("name", "one");
startService(intent);
break;
case R.id.start_two:
intent.putExtra("name", "two");
intent.putExtra("redeliver", true);
startService(intent);
break;
case R.id.start_failure:
intent.putExtra("name", "failure");
intent.putExtra("fail", true);
startService(intent);
break;
case R.id.kill_progress:
Process.killProcess(Process.myPid());
break;
}
}
}
}
配置檔案
<activity
android:name="com.fishtosky.servicestartargument.ServiceStartArgument$MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.fishtosky.servicestartargument.ServiceStartArgument"></service>