服务 IntentService 工作线程 stopself MD
目录
介绍
测试
测试代码
IntentService 源码
MyIntentService
Activity
测试情形
启动一次
启动一次后立即结束
连续启动两次
连续启动两次后立即结束
Service 的 stopself 方法详解
介绍
Google 为方便开发者使用,提高开发效率,封装了 IntentService 和 HandlerThread。HandlerThread 继承自 Thread,内部封装了 Looper。
IntentService 是继承自 Service 并处理异步请求的一个类,在 IntentService 内有一个工作线程来处理耗时操作,当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,执行完自动结束。
同一个IntentService只会新建一个线程,因为其采用的就是类似主线程的机制。
IntentService 中使用的 Handler、Looper、MessageQueue 机制把消息发送到线程中去执行的,所以多次启动 IntentService 不会重新创建新的服务和新的线程,只是把消息加入消息队列中等待执行,而如果服务停止,会清除消息队列中的消息,后续的事件得不到执行。
IntentService 类
IntentService是Service的基类,可根据需要处理异步请求(表示为Intents)。客户端通过Context#startService(Intent)
调用发送请求; 根据需要启动服务,使用工作线程依次处理每个Intent,并在工作完成时自行停止。
这种“工作队列处理器”模式通常用于从应用程序的主线程卸载任务。IntentService类的存在就是为了简化此模式并处理机制。要使用它,请扩展IntentService并实现onHandleIntent(Intent)。 IntentService将接收Intents,启动一个工作线程,并根据需要停止服务。
所有请求都在一个工作线程上处理 - 它们可能需要运行很长时间(并且不会阻止应用程序的主循环),但一次只能处理一个请求。
onHandleIntent 方法
在工作线程上调用此方法并请求处理。一次只处理一个Intent,但处理过程发生在独立于其他应用程序逻辑运行的工作线程上。因此,如果此代码需要很长时间,它将阻止对同一个IntentService的其他请求,但它不会阻止其他任何内容。处理完所有请求后,IntentService会自行停止,因此您不应该调用 stopSelf。
测试
最佳应用场景:后台队列按顺序异步下载、下载完毕后自动结束服务!
测试代码
IntentService 源码
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
Log.i("bqt", "开始onHandleIntent");
onHandleIntent((Intent) msg.obj);
Log.i("bqt", "结束onHandleIntent,尝试stopSelf");
stopSelf(msg.arg1);//处理完毕后自动调用stopSelf结束服务,注意如果此时还有未执行完的message则不会结束!
}
}
public IntentService(String name) {
super();
mName = name; //Used to name the worker thread, important only for debugging.
}
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock 如果有一个部分唤醒锁的选项会更好
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId); //You should not override this method for your IntentService. Instead, override onHandleIntent
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onStart(Intent intent, int startId) {
//每启动一次onStart方法,就会把消息数据发给mServiceHandler,相当于发送了一次Message消息给HandlerThread的消息队列
//mServiceHandler会把数据传给onHandleIntent方法,所以每次onStart之后都会调用我们重写的onHandleIntent方法去处理数据
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
public IBinder onBind(Intent intent) {
return null; //Unless you provide binding for your service, you don't need to implement this method
}
protected abstract void onHandleIntent(Intent intent);
}
MyIntentService
public class MyIntentService extends IntentService {
public MyIntentService() {
super("包青天的工作线程");
}
@Override
protected void onHandleIntent(Intent intent) {//注意,因为这里是异步操作,所以这里不能直接使用Toast。
//所有请求的Intent记录会按顺序加入到【队列】中并按顺序【异步】执行,并且每次只会执行【一个】工作线程
//当所有任务执行完后IntentService会【自动】停止
Log.i("bqt", "onHandleIntent");
try {
int count = 0;
sendThreadStatus("线程启动", count);
Thread.sleep(500);
while (count <= 100) {
count++;
Thread.sleep(30);
sendThreadStatus("线程运行中...", count);
}
sendThreadStatus("线程结束", count);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onCreate() {
super.onCreate();
Log.i("bqt", "onCreate");
sendServiceStatus("服务启动");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("bqt", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("bqt", "onDestroy");
sendServiceStatus("服务结束");
}
// 发送服务状态信息
private void sendServiceStatus(String status) {
Intent intent = new Intent(MainActivity.ACTION_TYPE_SERVICE);
intent.putExtra("status", status);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
// 发送线程状态信息
private void sendThreadStatus(String status, int progress) {
Intent intent = new Intent(MainActivity.ACTION_TYPE_THREAD);
intent.putExtra("status", status);
intent.putExtra("progress", progress);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
Activity
public class MainActivity extends ListActivity {
public static final String ACTION_TYPE_SERVICE = "com.bqt.type.service";
public static final String ACTION_TYPE_THREAD = "com.bqt.type.thread";
ProgressBar progressBar;
TextView tvServiceStatus;
TextView tvThreadStatus;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] array = {"启动 IntentService",
"结束 IntentService",
"",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
tvServiceStatus = new TextView(this);
tvThreadStatus = new TextView(this);
progressBar = new ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal);
progressBar.setIndeterminate(false);
progressBar.setMax(100);
getListView().addFooterView(tvServiceStatus);
getListView().addFooterView(tvThreadStatus);
getListView().addFooterView(progressBar);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_TYPE_SERVICE);
intentFilter.addAction(ACTION_TYPE_THREAD);
LocalBroadcastManager.getInstance(this).registerReceiver(new MyBroadcastReceiver(), intentFilter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position) {
case 0:
startService(new Intent(this, MyIntentService.class));// 像启动 Service 那样启动 IntentService
break;
case 1:
stopService(new Intent(this, MyIntentService.class));
break;
case 2:
break;
}
}
class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case ACTION_TYPE_SERVICE:
tvServiceStatus.setText("服务状态:" + intent.getStringExtra("status"));
break;
case ACTION_TYPE_THREAD:
int progress = intent.getIntExtra("progress", 0);
tvThreadStatus.setText("线程状态:" + intent.getStringExtra("status"));
progressBar.setProgress(progress);
break;
}
}
}
}
测试情形
启动一次
点击[启动 IntentService],服务运行,线程计数完成后,服务和线程都结束。
15:18:45.488: onCreate
15:18:45.489: onStartCommand
15:18:45.489: onStart
15:18:45.489: 开始onHandleIntent
15:18:45.489: onHandleIntent //【开始在工作线程执行任务】
15:18:49.651: 结束onHandleIntent,尝试stopSelf //【线程执行完毕后调用stopSelf结束任务】
15:18:49.659: onDestroy //【服务成功被结束】
启动一次后立即结束
点击[启动 IntentService],服务运行,线程计数完成前,点击[停止 IntentService],服务结束,线程计数完成后线程结束。
15:43:17.569: onCreate
15:43:17.570: onStartCommand
15:43:17.570: onStart
15:43:17.570: 开始onHandleIntent
15:43:17.570: onHandleIntent
15:43:18.895: onDestroy //【调用stopService会立即结束服务,但工作线程并不会结束,而会正常的执行】
15:43:21.724: 结束onHandleIntent,尝试stopSelf //【线程正常执行完毕,此前服务已经结束了】
连续启动两次
点击两次[启动 IntentService],服务运行,第一次线程计数完成后,进行第二次线程计数,两次完成后,服务和线程都结束。
14:36:30.794: onCreate
14:36:30.795: onStartCommand
14:36:30.795: 开始onHandleIntent
14:36:30.796: onHandleIntent
14:36:32.218: onStartCommand //【在执行第一个任务时启动第二个任务】
14:36:34.986: 结束onHandleIntent,尝试stopSelf //【因为此startId不是最新的startId,所以服务结束失败】
-----------------------------------------
14:36:34.993: 开始onHandleIntent
14:36:34.993: onHandleIntent
14:36:39.178: 结束onHandleIntent,尝试stopSelf //【第二个任务完成时的startId是最新的startId,所以能成功结束服务】
-----------------------------------------
14:36:39.188: onDestroy //【服务结束】
连续启动两次后立即结束
点击两次[启动 IntentService],服务运行,在第一次线程计数完成前,点击[停止 IntentService],服务结束,第一次线程计数结束后不进行第二次计数。
16:34:05.052: onCreate
16:34:05.054: onStartCommand
16:34:05.054: onStart
16:34:05.055: 开始onHandleIntent
16:34:05.055: onHandleIntent
16:34:05.463: onStartCommand
16:34:05.463: onStart
16:34:05.985: onDestroy //【调用stopService会立即结束服务,但工作线程并不会结束,而会正常的执行】
16:34:09.237: 结束onHandleIntent,尝试stopSelf //【因为服务已经结束,所以不会再执行未开启的任务】
Service 的 stopself 方法详解
结论
stopSelf()
:直接停止服务,和调用Context#stopService
的效果完全一致。stopSelf(int startId)
:在其参数 startId 跟最后启动该 service 时生成的 ID 相等时才会执行停止服务。stopSelfResult(int startId)
:和stopSelf(int startId)
的区别仅仅是有返回值,当成功停止服务时返回 true,否则返回 false。- 每次调用 startService 时 Service 都会重新赋值 startId 值,从1开始,如果 Service 没有被关闭,那这个值会顺序增加。
- IntentService 在调用
stopSelf(int startId)
结束自己时,总是拿最新的 startId 和上一次传入的 startId 作比较,如果相等则结束自己,如果不相等则不结束,而IntentService 中维护着一个消息队列,要想结束自己必须等到消息队列没有消息了。
使用场景
如果同时有多个服务启动请求发送到onStartCommand()
,不应该在处理完一个请求后调用stopSelf(),因为在调用此函数销毁service之前,可能 service 又接收到新的启动请求,如果此时 service 被销毁,新的请求将得不到处理。此情况应该调用stopSelf(int startId)
,例如 IntentService 中,在执行完异步任务后,就是通过调用stopSelf(int startId)
来结束服务的。
源码
先看看 onStartCommand
方法。
每次通过 startService 方式启动服务时,都会调用 onStartCommand 方法,此方法中有一个 startId,这个 startId 代表启动服务的次数,也代表着某一特定启动请求。
文档中明确说明了,这个 startId 其实就是在 stopSelfResult(int) 中用的。
//startId:A unique integer representing this specific request to start. Use with stopSelfResult(int).
public int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
再看看 stopSelf()
方法,可以看到其只是调用了stopSelf(-1)
。
//Stop the service, if it was previously先前 started.
//This is the same as calling Context#stopService for this particular特定 service.
public final void stopSelf() {
stopSelf(-1);
}
再看看 stopSelf(int startId)
方法,文档中没有什么解释,只说是旧版本中stopSelfResult
的无返回值形式。
//Old version of stopSelfResult that doesn't return a result.
public final void stopSelf(int startId) {
if (mActivityManager == null) return;
try {
mActivityManager.stopServiceToken(new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
}
public final boolean stopSelfResult(int startId) {
if (mActivityManager == null) return false; //停止失败
try {
return mActivityManager.stopServiceToken(new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
return false; //停止失败
}
以下是文档中的详细描述:
如果最近启动服务时的ID就是此startId,则停止服务。
这与对此特定服务调用Context#stopService的效果相同,但如果有一个来自客户端的启动请求(在执行此方法时这个请求还在onStart中,所以你没发现这个请求),则允许您安全地避免停止服务。
要小心调用此函数后的顺序。
如果在为之前收到的ID调用之前使用最近收到的ID调用此函数,则无论如何都会立即停止该服务。
如果您最终可能无序处理ID(例如通过在不同的线程上调度它们),那么您有责任按照收到它们的顺序停止它们。
参数与返回值:
- startId:The most recent start identifier received in #onStart. onStart中收到的最新启动标识符
- Returns true if the startId matches the last start request and the service will be stopped, else false.
2019-2-13