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>