为什么用IntentService
我们知道服务是在后台运行的,但是它本身存在两个问题:
- Service不会专门去启动一个新的进程,它与所在的应用属于同一个进程
Service也不会专门去启动一个新的线程,它运行在主线程之中
但是有的时候Service需要执行一些耗时操作,比如下载等,由于Service是在主线程中运行的,我们知道,如果主线程堵塞,会引发ANR,因此我们可以在Service中开启一个新的线程来运行,并且在运行结束后调用stopSelf或者stopService来停止该服务,这样可行,但是自己去管理Service的生命周期和子线程并不是一个好的选择,因此android提供了IntentService来处理这些工作。
IntentService简介
IntentService会采取队列的方式处理请求的Intent,每当客户端通过Intent请求IntentService时,IntentService便会将该Intent请求放在队列中,并开启一个新的worker线程来处理该Intent。对于异步的startService请求,IntentService会按照次序处理请求的Intent,该线程保证同一时刻只处理一个Intent,由于是在新的worker线程中处理,因此不会发生阻塞主线程的情况。
IntentService的特征
- IntentService会开启新的worker线程来处理所有的Intent请求
- IntentService会开启新的worker线程来处理onHandleIntent()方法实现的代码,因此开发者无需处理多线程问题
- 在所有请求处理完成后,IntentService会自动停止,开发者无需调用stopSelf()方法来停止
- IntentService为onBind()方法提供了默认的实现,默认返回null
- IntentService为onStartCommand()方法提供了默认的实现,会将请求的Intent加入到请求队列中
开发实例,模拟下载
一些常量的设置
//开启IntentService的action值
public static final String ACTION_UPLOAD_IMG="com.xu.intentservice.action.UPLOAD_IMG";
//传递path的KEY值
public static final String EXTRA_IMG_PATH="com.xu.intentsrevice.extra.IMG_PATH";
//广播Intent的action
public static final String UPLOAD_RESULT="com.xu.intentservice.UPLOAD_RESULT";
主界面有一个Add Task按钮,每点击一下就会开始一个下载任务,并且将需要下载的文件地址传进去,并且在主界面添加一个TextView,为它设置唯一标识为path
mAddTaskButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//构建下载的虚拟路径
String path="/sdcard/imgs/"+(++i)+".jpg";
//构建intent用于开启Service
Intent intent=new Intent(MainActivity.this,UpLoadingService.class);
//设置action
intent.setAction(ACTION_UPLOAD_IMG);
//将地址传过去
intent.putExtra(EXTRA_IMG_PATH, path);
//开启IntentService
startService(intent);
//新建一个TextView
TextView tv=new TextView(MainActivity.this);
mContainerLinear.addView(tv);
tv.setText(path+"---is loading......");
tv.setTextSize(20);
//将path设置为TextView的TAG,唯一标识
tv.setTag(path);
}
});
在UpLoadingService类中重写了onHandleIntent方法,开启Service,并且接收到传进来的Intent附带的EXTRA即path,然后自定义方法handleUpLoading方法通过传递进来的path属性来处理下载,当然这里只是让线程休眠了3秒来模拟下载,下载完成后,发送一条广播,并且将path值传递进去。
@Override
protected void onHandleIntent(Intent intent) {
if(intent!=null){
String action=intent.getAction();
//判断intent的action
if(MainActivity.ACTION_UPLOAD_IMG.equals(action)){
//获取到intent携带的path
String path=intent.getStringExtra(MainActivity.EXTRA_IMG_PATH);
//处理下载
handleUploadImg(path);
Log.i(TAG,"---------"+Thread.currentThread().getId());
}
}
}
处理下载,发送广播
private void handleUploadImg(String path){
try {
//线程休眠3秒模拟下载时间
Thread.sleep(3000);
//设置将要发送的广播的值的intent
Intent intent=new Intent(MainActivity.UPLOAD_RESULT);
//将路径带进去
intent.putExtra(MainActivity.EXTRA_IMG_PATH, path);
//发送广播
sendBroadcast(intent);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
在MainActivity中建立广播接收器,接收到广播意味着下载完成,并且通过传递进来的path值通过findViewWithTag找到刚才的TextView,设置为下载完成。
private BroadcastReceiver uploadImgReceive=new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//接受这个广播
String action=intent.getAction();
if(action.equals(UPLOAD_RESULT));
//得到path
String path=intent.getStringExtra(EXTRA_IMG_PATH);
//处理下载好以后的处理
handleResult(path);
}
};
//处理下载结果
public void handleResult(String path){
//通过刚才设置的TAG值得到TextView
mShowTextView=(TextView) mContainerLinear.findViewWithTag(path);
mShowTextView.setText(path+"upload succeed----");
}
注册广播接收器
public void registerReceiver(){
//设置要接受的广播
IntentFilter filter=new IntentFilter();
filter.addAction(UPLOAD_RESULT);
//注册
registerReceiver(uploadImgReceive,filter);
}
我在IntentService的onHandleIntent方法中打印出了当前线程的Id,可以看到不是使用的主线程,并且按照顺序处理Intent请求,且使用的同一个worker线程。